[Android] add HC++ platform view class. (#161829)
Part of the HC++ project for Android. This adds a current unused and unusable separate manager class for the new platform view strategy. Subsequent PRs will fill in the engine implementation as well as adding a mechanism to detect if support is available and provide a framework opt in.
This commit is contained in:
parent
5d705328dc
commit
a4927668cb
@ -41699,6 +41699,7 @@ ORIGIN: ../../../flutter/shell/platform/android/io/flutter/embedding/engine/syst
|
||||
ORIGIN: ../../../flutter/shell/platform/android/io/flutter/embedding/engine/systemchannels/NavigationChannel.java + ../../../flutter/LICENSE
|
||||
ORIGIN: ../../../flutter/shell/platform/android/io/flutter/embedding/engine/systemchannels/PlatformChannel.java + ../../../flutter/LICENSE
|
||||
ORIGIN: ../../../flutter/shell/platform/android/io/flutter/embedding/engine/systemchannels/PlatformViewsChannel.java + ../../../flutter/LICENSE
|
||||
ORIGIN: ../../../flutter/shell/platform/android/io/flutter/embedding/engine/systemchannels/PlatformViewsChannel2.java + ../../../flutter/LICENSE
|
||||
ORIGIN: ../../../flutter/shell/platform/android/io/flutter/embedding/engine/systemchannels/ProcessTextChannel.java + ../../../flutter/LICENSE
|
||||
ORIGIN: ../../../flutter/shell/platform/android/io/flutter/embedding/engine/systemchannels/RestorationChannel.java + ../../../flutter/LICENSE
|
||||
ORIGIN: ../../../flutter/shell/platform/android/io/flutter/embedding/engine/systemchannels/ScribeChannel.java + ../../../flutter/LICENSE
|
||||
@ -41742,6 +41743,7 @@ ORIGIN: ../../../flutter/shell/platform/android/io/flutter/plugin/platform/Platf
|
||||
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
|
||||
ORIGIN: ../../../flutter/shell/platform/android/io/flutter/plugin/platform/PlatformViewsController2.java + ../../../flutter/LICENSE
|
||||
ORIGIN: ../../../flutter/shell/platform/android/io/flutter/plugin/platform/SingleViewFakeWindowViewGroup.java + ../../../flutter/LICENSE
|
||||
ORIGIN: ../../../flutter/shell/platform/android/io/flutter/plugin/platform/SingleViewPresentation.java + ../../../flutter/LICENSE
|
||||
ORIGIN: ../../../flutter/shell/platform/android/io/flutter/plugin/platform/SingleViewWindowManager.java + ../../../flutter/LICENSE
|
||||
@ -44653,6 +44655,7 @@ FILE: ../../../flutter/shell/platform/android/io/flutter/embedding/engine/system
|
||||
FILE: ../../../flutter/shell/platform/android/io/flutter/embedding/engine/systemchannels/NavigationChannel.java
|
||||
FILE: ../../../flutter/shell/platform/android/io/flutter/embedding/engine/systemchannels/PlatformChannel.java
|
||||
FILE: ../../../flutter/shell/platform/android/io/flutter/embedding/engine/systemchannels/PlatformViewsChannel.java
|
||||
FILE: ../../../flutter/shell/platform/android/io/flutter/embedding/engine/systemchannels/PlatformViewsChannel2.java
|
||||
FILE: ../../../flutter/shell/platform/android/io/flutter/embedding/engine/systemchannels/ProcessTextChannel.java
|
||||
FILE: ../../../flutter/shell/platform/android/io/flutter/embedding/engine/systemchannels/RestorationChannel.java
|
||||
FILE: ../../../flutter/shell/platform/android/io/flutter/embedding/engine/systemchannels/ScribeChannel.java
|
||||
@ -44700,6 +44703,7 @@ FILE: ../../../flutter/shell/platform/android/io/flutter/plugin/platform/Platfor
|
||||
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/PlatformViewsController2.java
|
||||
FILE: ../../../flutter/shell/platform/android/io/flutter/plugin/platform/SingleViewFakeWindowViewGroup.java
|
||||
FILE: ../../../flutter/shell/platform/android/io/flutter/plugin/platform/SingleViewPresentation.java
|
||||
FILE: ../../../flutter/shell/platform/android/io/flutter/plugin/platform/SingleViewWindowManager.java
|
||||
|
@ -283,6 +283,7 @@ android_java_sources = [
|
||||
"io/flutter/embedding/engine/systemchannels/NavigationChannel.java",
|
||||
"io/flutter/embedding/engine/systemchannels/PlatformChannel.java",
|
||||
"io/flutter/embedding/engine/systemchannels/PlatformViewsChannel.java",
|
||||
"io/flutter/embedding/engine/systemchannels/PlatformViewsChannel2.java",
|
||||
"io/flutter/embedding/engine/systemchannels/ProcessTextChannel.java",
|
||||
"io/flutter/embedding/engine/systemchannels/RestorationChannel.java",
|
||||
"io/flutter/embedding/engine/systemchannels/ScribeChannel.java",
|
||||
@ -330,6 +331,7 @@ android_java_sources = [
|
||||
"io/flutter/plugin/platform/PlatformViewWrapper.java",
|
||||
"io/flutter/plugin/platform/PlatformViewsAccessibilityDelegate.java",
|
||||
"io/flutter/plugin/platform/PlatformViewsController.java",
|
||||
"io/flutter/plugin/platform/PlatformViewsController2.java",
|
||||
"io/flutter/plugin/platform/SingleViewFakeWindowViewGroup.java",
|
||||
"io/flutter/plugin/platform/SingleViewPresentation.java",
|
||||
"io/flutter/plugin/platform/SingleViewWindowManager.java",
|
||||
|
@ -1122,7 +1122,8 @@ public class FlutterView extends FrameLayout
|
||||
this,
|
||||
this.flutterEngine.getTextInputChannel(),
|
||||
this.flutterEngine.getScribeChannel(),
|
||||
this.flutterEngine.getPlatformViewsController());
|
||||
this.flutterEngine.getPlatformViewsController(),
|
||||
this.flutterEngine.getPlatformViewsController2());
|
||||
|
||||
try {
|
||||
textServicesManager =
|
||||
@ -1162,6 +1163,11 @@ public class FlutterView extends FrameLayout
|
||||
.getPlatformViewsController()
|
||||
.attachToFlutterRenderer(this.flutterEngine.getRenderer());
|
||||
|
||||
this.flutterEngine.getPlatformViewsController2().attachAccessibilityBridge(accessibilityBridge);
|
||||
this.flutterEngine
|
||||
.getPlatformViewsController2()
|
||||
.attachToFlutterRenderer(this.flutterEngine.getRenderer());
|
||||
|
||||
// Inform the Android framework that it should retrieve a new InputConnection
|
||||
// now that an engine is attached.
|
||||
// TODO(mattcarroll): once this is proven to work, move this line ot TextInputPlugin
|
||||
@ -1179,6 +1185,7 @@ public class FlutterView extends FrameLayout
|
||||
sendViewportMetricsToFlutter();
|
||||
|
||||
flutterEngine.getPlatformViewsController().attachToView(this);
|
||||
flutterEngine.getPlatformViewsController2().attachToView(this);
|
||||
|
||||
// Notify engine attachment listeners of the attachment.
|
||||
for (FlutterEngineAttachmentListener listener : flutterEngineAttachmentListeners) {
|
||||
@ -1219,9 +1226,11 @@ public class FlutterView extends FrameLayout
|
||||
getContext().getContentResolver().unregisterContentObserver(systemSettingsObserver);
|
||||
|
||||
flutterEngine.getPlatformViewsController().detachFromView();
|
||||
flutterEngine.getPlatformViewsController2().detachFromView();
|
||||
|
||||
// Disconnect the FlutterEngine's PlatformViewsController from the AccessibilityBridge.
|
||||
flutterEngine.getPlatformViewsController().detachAccessibilityBridge();
|
||||
flutterEngine.getPlatformViewsController2().detachAccessibilityBridge();
|
||||
|
||||
// Disconnect and clean up the AccessibilityBridge.
|
||||
accessibilityBridge.release();
|
||||
|
@ -41,6 +41,7 @@ import io.flutter.embedding.engine.systemchannels.SystemChannel;
|
||||
import io.flutter.embedding.engine.systemchannels.TextInputChannel;
|
||||
import io.flutter.plugin.localization.LocalizationPlugin;
|
||||
import io.flutter.plugin.platform.PlatformViewsController;
|
||||
import io.flutter.plugin.platform.PlatformViewsController2;
|
||||
import io.flutter.plugin.text.ProcessTextPlugin;
|
||||
import io.flutter.util.ViewUtils;
|
||||
import java.util.HashSet;
|
||||
@ -109,6 +110,7 @@ public class FlutterEngine implements ViewUtils.DisplayUpdater {
|
||||
|
||||
// Platform Views.
|
||||
@NonNull private final PlatformViewsController platformViewsController;
|
||||
@NonNull private final PlatformViewsController2 platformViewsController2;
|
||||
|
||||
// Engine Lifecycle.
|
||||
@NonNull private final Set<EngineLifecycleListener> engineLifecycleListeners = new HashSet<>();
|
||||
@ -124,6 +126,7 @@ public class FlutterEngine implements ViewUtils.DisplayUpdater {
|
||||
}
|
||||
|
||||
platformViewsController.onPreEngineRestart();
|
||||
platformViewsController2.onPreEngineRestart();
|
||||
restorationChannel.clearData();
|
||||
}
|
||||
|
||||
@ -360,8 +363,11 @@ public class FlutterEngine implements ViewUtils.DisplayUpdater {
|
||||
flutterLoader.ensureInitializationComplete(context, dartVmArgs);
|
||||
}
|
||||
|
||||
PlatformViewsController2 platformViewsController2 = new PlatformViewsController2();
|
||||
|
||||
flutterJNI.addEngineLifecycleListener(engineLifecycleListener);
|
||||
flutterJNI.setPlatformViewsController(platformViewsController);
|
||||
flutterJNI.setPlatformViewsController2(platformViewsController2);
|
||||
flutterJNI.setLocalizationPlugin(localizationPlugin);
|
||||
flutterJNI.setDeferredComponentManager(injector.deferredComponentManager());
|
||||
|
||||
@ -377,7 +383,7 @@ public class FlutterEngine implements ViewUtils.DisplayUpdater {
|
||||
this.renderer = new FlutterRenderer(flutterJNI);
|
||||
|
||||
this.platformViewsController = platformViewsController;
|
||||
this.platformViewsController.onAttachedToJNI();
|
||||
this.platformViewsController2 = platformViewsController2;
|
||||
|
||||
this.pluginRegistry =
|
||||
new FlutterEngineConnectionRegistry(
|
||||
@ -472,6 +478,7 @@ public class FlutterEngine implements ViewUtils.DisplayUpdater {
|
||||
// The order that these things are destroyed is important.
|
||||
pluginRegistry.destroy();
|
||||
platformViewsController.onDetachedFromJNI();
|
||||
platformViewsController2.onDetachedFromJNI();
|
||||
dartExecutor.onDetachedFromJNI();
|
||||
flutterJNI.removeEngineLifecycleListener(engineLifecycleListener);
|
||||
flutterJNI.setDeferredComponentManager(null);
|
||||
@ -648,6 +655,11 @@ public class FlutterEngine implements ViewUtils.DisplayUpdater {
|
||||
return platformViewsController;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public PlatformViewsController2 getPlatformViewsController2() {
|
||||
return platformViewsController2;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public ActivityControlSurface getActivityControlSurface() {
|
||||
return pluginRegistry;
|
||||
|
@ -18,6 +18,7 @@ import android.util.DisplayMetrics;
|
||||
import android.util.Size;
|
||||
import android.util.TypedValue;
|
||||
import android.view.Surface;
|
||||
import android.view.SurfaceControl;
|
||||
import android.view.SurfaceHolder;
|
||||
import androidx.annotation.Keep;
|
||||
import androidx.annotation.NonNull;
|
||||
@ -36,6 +37,7 @@ import io.flutter.embedding.engine.systemchannels.SettingsChannel;
|
||||
import io.flutter.plugin.common.StandardMessageCodec;
|
||||
import io.flutter.plugin.localization.LocalizationPlugin;
|
||||
import io.flutter.plugin.platform.PlatformViewsController;
|
||||
import io.flutter.plugin.platform.PlatformViewsController2;
|
||||
import io.flutter.util.Preconditions;
|
||||
import io.flutter.view.AccessibilityBridge;
|
||||
import io.flutter.view.FlutterCallbackInformation;
|
||||
@ -388,6 +390,7 @@ public class FlutterJNI {
|
||||
@Nullable private PlatformMessageHandler platformMessageHandler;
|
||||
@Nullable private LocalizationPlugin localizationPlugin;
|
||||
@Nullable private PlatformViewsController platformViewsController;
|
||||
@Nullable private PlatformViewsController2 platformViewsController2;
|
||||
|
||||
@Nullable private DeferredComponentManager deferredComponentManager;
|
||||
|
||||
@ -765,6 +768,13 @@ public class FlutterJNI {
|
||||
this.platformViewsController = platformViewsController;
|
||||
}
|
||||
|
||||
@UiThread
|
||||
public void setPlatformViewsController2(
|
||||
@NonNull PlatformViewsController2 platformViewsController2) {
|
||||
ensureRunningOnMainThread();
|
||||
this.platformViewsController2 = platformViewsController2;
|
||||
}
|
||||
|
||||
// ------ Start Accessibility Support -----
|
||||
/**
|
||||
* Sets the {@link AccessibilityDelegate} for the attached Flutter context.
|
||||
@ -1274,6 +1284,75 @@ public class FlutterJNI {
|
||||
}
|
||||
// ----- End Engine Lifecycle Support ----
|
||||
|
||||
// ----- New Platform Views ----------
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
@UiThread
|
||||
public SurfaceControl.Transaction createTransaction() {
|
||||
if (platformViewsController2 == null) {
|
||||
throw new RuntimeException("");
|
||||
}
|
||||
return platformViewsController2.createTransaction();
|
||||
}
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
@UiThread
|
||||
public void swapTransactions() {
|
||||
if (platformViewsController2 == null) {
|
||||
throw new RuntimeException("");
|
||||
}
|
||||
platformViewsController2.swapTransactions();
|
||||
}
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
@UiThread
|
||||
public void applyTransactions() {
|
||||
if (platformViewsController2 == null) {
|
||||
throw new RuntimeException("");
|
||||
}
|
||||
platformViewsController2.applyTransactions();
|
||||
}
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
@UiThread
|
||||
public FlutterOverlaySurface createOverlaySurface2() {
|
||||
if (platformViewsController2 == null) {
|
||||
throw new RuntimeException(
|
||||
"platformViewsController must be set before attempting to position an overlay surface");
|
||||
}
|
||||
return platformViewsController2.createOverlaySurface();
|
||||
}
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
@UiThread
|
||||
public void destroyOverlaySurface2() {
|
||||
ensureRunningOnMainThread();
|
||||
if (platformViewsController2 == null) {
|
||||
throw new RuntimeException(
|
||||
"platformViewsController must be set before attempting to destroy an overlay surface");
|
||||
}
|
||||
platformViewsController2.destroyOverlaySurface();
|
||||
}
|
||||
|
||||
@UiThread
|
||||
public void onDisplayPlatformView2(
|
||||
int viewId,
|
||||
int x,
|
||||
int y,
|
||||
int width,
|
||||
int height,
|
||||
int viewWidth,
|
||||
int viewHeight,
|
||||
FlutterMutatorsStack mutatorsStack) {
|
||||
ensureRunningOnMainThread();
|
||||
if (platformViewsController2 == null) {
|
||||
throw new RuntimeException(
|
||||
"platformViewsController must be set before attempting to position a platform view");
|
||||
}
|
||||
platformViewsController2.onDisplayPlatformView(
|
||||
viewId, x, y, width, height, viewWidth, viewHeight, mutatorsStack);
|
||||
}
|
||||
|
||||
// ----- Start Localization Support ----
|
||||
|
||||
/** Sets the localization plugin that is used in various localization methods. */
|
||||
|
@ -0,0 +1,325 @@
|
||||
// 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.embedding.engine.systemchannels;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import io.flutter.Log;
|
||||
import io.flutter.embedding.engine.dart.DartExecutor;
|
||||
import io.flutter.plugin.common.MethodCall;
|
||||
import io.flutter.plugin.common.MethodChannel;
|
||||
import io.flutter.plugin.common.StandardMethodCodec;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* System channel that sends 2-way communication between Flutter and Android to facilitate embedding
|
||||
* of Android Views within a Flutter application.
|
||||
*
|
||||
* <p>Register a {@link PlatformViewsHandler} to implement the Android side of this channel.
|
||||
*/
|
||||
public class PlatformViewsChannel2 {
|
||||
private static final String TAG = "PlatformViewsChannel2";
|
||||
|
||||
private final MethodChannel channel;
|
||||
private PlatformViewsHandler handler;
|
||||
|
||||
/**
|
||||
* Constructs a {@code PlatformViewsChannel} that connects Android to the Dart code running in
|
||||
* {@code dartExecutor}.
|
||||
*
|
||||
* <p>The given {@code dartExecutor} is permitted to be idle or executing code.
|
||||
*
|
||||
* <p>See {@link DartExecutor}.
|
||||
*/
|
||||
public PlatformViewsChannel2(@NonNull DartExecutor dartExecutor) {
|
||||
channel =
|
||||
new MethodChannel(dartExecutor, "flutter/platform_views_2", StandardMethodCodec.INSTANCE);
|
||||
channel.setMethodCallHandler(parsingHandler);
|
||||
}
|
||||
|
||||
public void invokeViewFocused(int viewId) {
|
||||
if (channel == null) {
|
||||
return;
|
||||
}
|
||||
channel.invokeMethod("viewFocused", viewId);
|
||||
}
|
||||
|
||||
private static String detailedExceptionString(Exception exception) {
|
||||
return Log.getStackTraceString(exception);
|
||||
}
|
||||
|
||||
private final MethodChannel.MethodCallHandler parsingHandler =
|
||||
new MethodChannel.MethodCallHandler() {
|
||||
@Override
|
||||
public void onMethodCall(@NonNull MethodCall call, @NonNull MethodChannel.Result result) {
|
||||
// If there is no handler to respond to this message then we don't need to
|
||||
// parse it. Return.
|
||||
if (handler == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
Log.v(TAG, "Received '" + call.method + "' message.");
|
||||
switch (call.method) {
|
||||
case "create":
|
||||
create(call, result);
|
||||
break;
|
||||
case "dispose":
|
||||
dispose(call, result);
|
||||
break;
|
||||
case "touch":
|
||||
touch(call, result);
|
||||
break;
|
||||
case "setDirection":
|
||||
setDirection(call, result);
|
||||
break;
|
||||
case "clearFocus":
|
||||
clearFocus(call, result);
|
||||
break;
|
||||
default:
|
||||
result.notImplemented();
|
||||
}
|
||||
}
|
||||
|
||||
private void create(@NonNull MethodCall call, @NonNull MethodChannel.Result result) {
|
||||
final Map<String, Object> createArgs = call.arguments();
|
||||
final ByteBuffer additionalParams =
|
||||
createArgs.containsKey("params")
|
||||
? ByteBuffer.wrap((byte[]) createArgs.get("params"))
|
||||
: null;
|
||||
try {
|
||||
|
||||
final PlatformViewCreationRequest request =
|
||||
new PlatformViewCreationRequest(
|
||||
(int) createArgs.get("id"),
|
||||
(String) createArgs.get("viewType"),
|
||||
0,
|
||||
0,
|
||||
(int) createArgs.get("direction"),
|
||||
additionalParams);
|
||||
handler.createPlatformView(request);
|
||||
result.success(null);
|
||||
|
||||
} catch (IllegalStateException exception) {
|
||||
result.error("error", detailedExceptionString(exception), null);
|
||||
}
|
||||
}
|
||||
|
||||
private void dispose(@NonNull MethodCall call, @NonNull MethodChannel.Result result) {
|
||||
Map<String, Object> disposeArgs = call.arguments();
|
||||
int viewId = (int) disposeArgs.get("id");
|
||||
|
||||
try {
|
||||
handler.dispose(viewId);
|
||||
result.success(null);
|
||||
} catch (IllegalStateException exception) {
|
||||
result.error("error", detailedExceptionString(exception), null);
|
||||
}
|
||||
}
|
||||
|
||||
private void touch(@NonNull MethodCall call, @NonNull MethodChannel.Result result) {
|
||||
List<Object> args = call.arguments();
|
||||
PlatformViewTouch touch =
|
||||
new PlatformViewTouch(
|
||||
(int) args.get(0),
|
||||
(Number) args.get(1),
|
||||
(Number) args.get(2),
|
||||
(int) args.get(3),
|
||||
(int) args.get(4),
|
||||
args.get(5),
|
||||
args.get(6),
|
||||
(int) args.get(7),
|
||||
(int) args.get(8),
|
||||
(float) (double) args.get(9),
|
||||
(float) (double) args.get(10),
|
||||
(int) args.get(11),
|
||||
(int) args.get(12),
|
||||
(int) args.get(13),
|
||||
(int) args.get(14),
|
||||
((Number) args.get(15)).longValue());
|
||||
|
||||
try {
|
||||
handler.onTouch(touch);
|
||||
result.success(null);
|
||||
} catch (IllegalStateException exception) {
|
||||
result.error("error", detailedExceptionString(exception), null);
|
||||
}
|
||||
}
|
||||
|
||||
private void setDirection(@NonNull MethodCall call, @NonNull MethodChannel.Result result) {
|
||||
Map<String, Object> setDirectionArgs = call.arguments();
|
||||
int newDirectionViewId = (int) setDirectionArgs.get("id");
|
||||
int direction = (int) setDirectionArgs.get("direction");
|
||||
|
||||
try {
|
||||
handler.setDirection(newDirectionViewId, direction);
|
||||
result.success(null);
|
||||
} catch (IllegalStateException exception) {
|
||||
result.error("error", detailedExceptionString(exception), null);
|
||||
}
|
||||
}
|
||||
|
||||
private void clearFocus(@NonNull MethodCall call, @NonNull MethodChannel.Result result) {
|
||||
int viewId = call.arguments();
|
||||
try {
|
||||
handler.clearFocus(viewId);
|
||||
result.success(null);
|
||||
} catch (IllegalStateException exception) {
|
||||
result.error("error", detailedExceptionString(exception), null);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Sets the {@link PlatformViewsHandler} which receives all events and requests that are parsed
|
||||
* from the underlying platform views channel.
|
||||
*/
|
||||
public void setPlatformViewsHandler(@Nullable PlatformViewsHandler handler) {
|
||||
this.handler = handler;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handler that receives platform view messages sent from Flutter to Android through a given
|
||||
* {@link PlatformViewsChannel}.
|
||||
*
|
||||
* <p>To register a {@code PlatformViewsHandler} with a {@link PlatformViewsChannel2}, see {@link
|
||||
* PlatformViewsChannel2#setPlatformViewsHandler(PlatformViewsHandler)}.
|
||||
*/
|
||||
public interface PlatformViewsHandler {
|
||||
/**
|
||||
* The Flutter application would like to display a new Android {@code View}, i.e., platform
|
||||
* view.
|
||||
*/
|
||||
void createPlatformView(@NonNull PlatformViewCreationRequest request);
|
||||
|
||||
/** The Flutter application would like to dispose of an existing Android {@code View}. */
|
||||
void dispose(int viewId);
|
||||
|
||||
/**
|
||||
* The user touched a platform view within Flutter.
|
||||
*
|
||||
* <p>Touch data is reported in {@code touch}.
|
||||
*/
|
||||
void onTouch(@NonNull PlatformViewTouch touch);
|
||||
|
||||
/**
|
||||
* The Flutter application would like to change the layout direction of an existing Android
|
||||
* {@code View}, i.e., platform view.
|
||||
*/
|
||||
void setDirection(int viewId, int direction);
|
||||
|
||||
/** Clears the focus from the platform view with a give id if it is currently focused. */
|
||||
void clearFocus(int viewId);
|
||||
}
|
||||
|
||||
/** Request sent from Flutter to create a new platform view. */
|
||||
public static class PlatformViewCreationRequest {
|
||||
/** The ID of the platform view as seen by the Flutter side. */
|
||||
public final int viewId;
|
||||
|
||||
@NonNull public final String viewType;
|
||||
public final double logicalWidth;
|
||||
public final double logicalHeight;
|
||||
|
||||
/**
|
||||
* The layout direction of the new platform view.
|
||||
*
|
||||
* <p>See {@link android.view.View#LAYOUT_DIRECTION_LTR} and {@link
|
||||
* android.view.View#LAYOUT_DIRECTION_RTL}
|
||||
*/
|
||||
public final int direction;
|
||||
|
||||
/** Custom parameters that are unique to the desired platform view. */
|
||||
@Nullable public final ByteBuffer params;
|
||||
|
||||
public PlatformViewCreationRequest(
|
||||
int viewId,
|
||||
@NonNull String viewType,
|
||||
double logicalWidth,
|
||||
double logicalHeight,
|
||||
int direction,
|
||||
@Nullable ByteBuffer params) {
|
||||
this.viewId = viewId;
|
||||
this.viewType = viewType;
|
||||
this.logicalWidth = logicalWidth;
|
||||
this.logicalHeight = logicalHeight;
|
||||
this.direction = direction;
|
||||
this.params = params;
|
||||
}
|
||||
}
|
||||
|
||||
/** The state of a touch event in Flutter within a platform view. */
|
||||
public static class PlatformViewTouch {
|
||||
/** The ID of the platform view as seen by the Flutter side. */
|
||||
public final int viewId;
|
||||
|
||||
/** The amount of time that the touch has been pressed. */
|
||||
@NonNull public final Number downTime;
|
||||
|
||||
@NonNull public final Number eventTime;
|
||||
|
||||
public final int action;
|
||||
/** The number of pointers (e.g, fingers) involved in the touch event. */
|
||||
public final int pointerCount;
|
||||
/**
|
||||
* Properties for each pointer, encoded in a raw format. Expected to be formatted as a
|
||||
* List[List[Integer]], where each inner list has two items: - An id, at index 0, corresponding
|
||||
* to {@link android.view.MotionEvent.PointerProperties#id} - A tool type, at index 1,
|
||||
* corresponding to {@link android.view.MotionEvent.PointerProperties#toolType}.
|
||||
*/
|
||||
@NonNull public final Object rawPointerPropertiesList;
|
||||
/** Coordinates for each pointer, encoded in a raw format. */
|
||||
@NonNull public final Object rawPointerCoords;
|
||||
|
||||
public final int metaState;
|
||||
public final int buttonState;
|
||||
/** Coordinate precision along the x-axis. */
|
||||
public final float xPrecision;
|
||||
/** Coordinate precision along the y-axis. */
|
||||
public final float yPrecision;
|
||||
|
||||
public final int deviceId;
|
||||
public final int edgeFlags;
|
||||
public final int source;
|
||||
public final int flags;
|
||||
public final long motionEventId;
|
||||
|
||||
public PlatformViewTouch(
|
||||
int viewId,
|
||||
@NonNull Number downTime,
|
||||
@NonNull Number eventTime,
|
||||
int action,
|
||||
int pointerCount,
|
||||
@NonNull Object rawPointerPropertiesList,
|
||||
@NonNull Object rawPointerCoords,
|
||||
int metaState,
|
||||
int buttonState,
|
||||
float xPrecision,
|
||||
float yPrecision,
|
||||
int deviceId,
|
||||
int edgeFlags,
|
||||
int source,
|
||||
int flags,
|
||||
long motionEventId) {
|
||||
this.viewId = viewId;
|
||||
this.downTime = downTime;
|
||||
this.eventTime = eventTime;
|
||||
this.action = action;
|
||||
this.pointerCount = pointerCount;
|
||||
this.rawPointerPropertiesList = rawPointerPropertiesList;
|
||||
this.rawPointerCoords = rawPointerCoords;
|
||||
this.metaState = metaState;
|
||||
this.buttonState = buttonState;
|
||||
this.xPrecision = xPrecision;
|
||||
this.yPrecision = yPrecision;
|
||||
this.deviceId = deviceId;
|
||||
this.edgeFlags = edgeFlags;
|
||||
this.source = source;
|
||||
this.flags = flags;
|
||||
this.motionEventId = motionEventId;
|
||||
}
|
||||
}
|
||||
}
|
@ -33,6 +33,7 @@ import io.flutter.embedding.engine.systemchannels.ScribeChannel;
|
||||
import io.flutter.embedding.engine.systemchannels.TextInputChannel;
|
||||
import io.flutter.embedding.engine.systemchannels.TextInputChannel.TextEditState;
|
||||
import io.flutter.plugin.platform.PlatformViewsController;
|
||||
import io.flutter.plugin.platform.PlatformViewsController2;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
|
||||
@ -52,6 +53,7 @@ public class TextInputPlugin implements ListenableEditingState.EditingStateWatch
|
||||
private boolean mRestartInputPending;
|
||||
@Nullable private InputConnection lastInputConnection;
|
||||
@NonNull private PlatformViewsController platformViewsController;
|
||||
@NonNull private PlatformViewsController2 platformViewsController2;
|
||||
@Nullable private Rect lastClientRect;
|
||||
private ImeSyncDeferringInsetsCallback imeSyncCallback;
|
||||
|
||||
@ -69,7 +71,8 @@ public class TextInputPlugin implements ListenableEditingState.EditingStateWatch
|
||||
@NonNull View view,
|
||||
@NonNull TextInputChannel textInputChannel,
|
||||
@NonNull ScribeChannel scribeChannel,
|
||||
@NonNull PlatformViewsController platformViewsController) {
|
||||
@NonNull PlatformViewsController platformViewsController,
|
||||
@NonNull PlatformViewsController2 platformViewsController2) {
|
||||
mView = view;
|
||||
// Create a default object.
|
||||
mEditable = new ListenableEditingState(null, mView);
|
||||
@ -160,6 +163,8 @@ public class TextInputPlugin implements ListenableEditingState.EditingStateWatch
|
||||
|
||||
this.platformViewsController = platformViewsController;
|
||||
this.platformViewsController.attachTextInputPlugin(this);
|
||||
this.platformViewsController2 = platformViewsController2;
|
||||
this.platformViewsController2.attachTextInputPlugin(this);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@ -215,6 +220,7 @@ public class TextInputPlugin implements ListenableEditingState.EditingStateWatch
|
||||
@SuppressLint("NewApi")
|
||||
public void destroy() {
|
||||
platformViewsController.detachTextInputPlugin();
|
||||
platformViewsController2.detachTextInputPlugin();
|
||||
textInputChannel.setTextInputMethodHandler(null);
|
||||
notifyViewExited();
|
||||
mEditable.removeEditingStateListener(this);
|
||||
|
@ -926,14 +926,6 @@ public class PlatformViewsController implements PlatformViewsAccessibilityDelega
|
||||
return registry;
|
||||
}
|
||||
|
||||
/**
|
||||
* Invoked when the {@link io.flutter.embedding.engine.FlutterEngine} that owns this {@link
|
||||
* PlatformViewsController} attaches to JNI.
|
||||
*/
|
||||
public void onAttachedToJNI() {
|
||||
// Currently no action needs to be taken after JNI attachment.
|
||||
}
|
||||
|
||||
/**
|
||||
* Invoked when the {@link io.flutter.embedding.engine.FlutterEngine} that owns this {@link
|
||||
* PlatformViewsController} detaches from JNI.
|
||||
|
@ -0,0 +1,679 @@
|
||||
// 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 io.flutter.Build.API_LEVELS;
|
||||
|
||||
import android.annotation.TargetApi;
|
||||
import android.content.Context;
|
||||
import android.graphics.PixelFormat;
|
||||
import android.util.SparseArray;
|
||||
import android.view.MotionEvent;
|
||||
import android.view.MotionEvent.PointerCoords;
|
||||
import android.view.MotionEvent.PointerProperties;
|
||||
import android.view.Surface;
|
||||
import android.view.SurfaceControl;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.FrameLayout;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.annotation.RequiresApi;
|
||||
import androidx.annotation.UiThread;
|
||||
import androidx.annotation.VisibleForTesting;
|
||||
import io.flutter.Log;
|
||||
import io.flutter.embedding.android.AndroidTouchProcessor;
|
||||
import io.flutter.embedding.android.FlutterView;
|
||||
import io.flutter.embedding.android.MotionEventTracker;
|
||||
import io.flutter.embedding.engine.FlutterOverlaySurface;
|
||||
import io.flutter.embedding.engine.dart.DartExecutor;
|
||||
import io.flutter.embedding.engine.mutatorsstack.*;
|
||||
import io.flutter.embedding.engine.renderer.FlutterRenderer;
|
||||
import io.flutter.embedding.engine.systemchannels.PlatformViewsChannel2;
|
||||
import io.flutter.plugin.editing.TextInputPlugin;
|
||||
import io.flutter.view.AccessibilityBridge;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Manages platform views.
|
||||
*
|
||||
* <p>Each {@link io.flutter.embedding.engine.FlutterEngine} has a single platform views controller.
|
||||
* A platform views controller can be attached to at most one Flutter view.
|
||||
*/
|
||||
public class PlatformViewsController2 implements PlatformViewsAccessibilityDelegate {
|
||||
private static final String TAG = "PlatformViewsController2";
|
||||
|
||||
private final PlatformViewRegistryImpl registry;
|
||||
private AndroidTouchProcessor androidTouchProcessor;
|
||||
private Context context;
|
||||
private FlutterView flutterView;
|
||||
|
||||
@Nullable private TextInputPlugin textInputPlugin;
|
||||
|
||||
private PlatformViewsChannel2 platformViewsChannel;
|
||||
private final AccessibilityEventsDelegate accessibilityEventsDelegate;
|
||||
|
||||
private final SparseArray<PlatformView> platformViews;
|
||||
private final SparseArray<FlutterMutatorView> platformViewParent;
|
||||
private final MotionEventTracker motionEventTracker;
|
||||
|
||||
private final ArrayList<SurfaceControl.Transaction> pendingTransactions;
|
||||
private final ArrayList<SurfaceControl.Transaction> activeTransactions;
|
||||
private Surface overlayerSurface = null;
|
||||
|
||||
public PlatformViewsController2() {
|
||||
registry = new PlatformViewRegistryImpl();
|
||||
accessibilityEventsDelegate = new AccessibilityEventsDelegate();
|
||||
platformViews = new SparseArray<>();
|
||||
platformViewParent = new SparseArray<>();
|
||||
pendingTransactions = new ArrayList<>();
|
||||
activeTransactions = new ArrayList<>();
|
||||
motionEventTracker = MotionEventTracker.getInstance();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean usesVirtualDisplay(int id) {
|
||||
return false;
|
||||
}
|
||||
|
||||
public PlatformView createFlutterPlatformView(
|
||||
@NonNull PlatformViewsChannel2.PlatformViewCreationRequest request) {
|
||||
final PlatformViewFactory viewFactory = registry.getFactory(request.viewType);
|
||||
if (viewFactory == null) {
|
||||
throw new IllegalStateException(
|
||||
"Trying to create a platform view of unregistered type: " + request.viewType);
|
||||
}
|
||||
|
||||
Object createParams = null;
|
||||
if (request.params != null) {
|
||||
createParams = viewFactory.getCreateArgsCodec().decodeMessage(request.params);
|
||||
}
|
||||
final PlatformView platformView = viewFactory.create(context, request.viewId, createParams);
|
||||
|
||||
// Configure the view to match the requested layout direction.
|
||||
final View embeddedView = platformView.getView();
|
||||
if (embeddedView == null) {
|
||||
throw new IllegalStateException(
|
||||
"PlatformView#getView() returned null, but an Android view reference was expected.");
|
||||
}
|
||||
embeddedView.setLayoutDirection(request.direction);
|
||||
platformViews.put(request.viewId, platformView);
|
||||
maybeInvokeOnFlutterViewAttached(platformView);
|
||||
return platformView;
|
||||
}
|
||||
|
||||
/**
|
||||
* Translates an original touch event to have the same locations as the ones that Flutter
|
||||
* calculates (because original + flutter's - original = flutter's).
|
||||
*
|
||||
* @param originalEvent The saved original input event.
|
||||
* @param pointerCoords The coordinates that Flutter thinks the touch is happening at.
|
||||
*/
|
||||
private static void translateMotionEvent(
|
||||
MotionEvent originalEvent, PointerCoords[] pointerCoords) {
|
||||
if (pointerCoords.length < 1) {
|
||||
return;
|
||||
}
|
||||
|
||||
float xOffset = pointerCoords[0].x - originalEvent.getX();
|
||||
float yOffset = pointerCoords[0].y - originalEvent.getY();
|
||||
|
||||
originalEvent.offsetLocation(xOffset, yOffset);
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
public MotionEvent toMotionEvent(float density, PlatformViewsChannel2.PlatformViewTouch touch) {
|
||||
MotionEventTracker.MotionEventId motionEventId =
|
||||
MotionEventTracker.MotionEventId.from(touch.motionEventId);
|
||||
MotionEvent trackedEvent = motionEventTracker.pop(motionEventId);
|
||||
|
||||
// Pointer coordinates in the tracked events are global to FlutterView
|
||||
// The framework converts them to be local to a widget, given that
|
||||
// motion events operate on local coords, we need to replace these in the tracked
|
||||
// event with their local counterparts.
|
||||
// Compute this early so it can be used as input to translateNonVirtualDisplayMotionEvent.
|
||||
PointerCoords[] pointerCoords =
|
||||
parsePointerCoordsList(touch.rawPointerCoords, density)
|
||||
.toArray(new PointerCoords[touch.pointerCount]);
|
||||
|
||||
if (trackedEvent != null) {
|
||||
// We have the original event, deliver it after offsetting as it will pass the verifiable
|
||||
// input check.
|
||||
translateMotionEvent(trackedEvent, pointerCoords);
|
||||
return trackedEvent;
|
||||
}
|
||||
// We don't have a reference to the original MotionEvent.
|
||||
// In this case we manually recreate a MotionEvent to be delivered. This MotionEvent
|
||||
// will fail the verifiable input check.
|
||||
PointerProperties[] pointerProperties =
|
||||
parsePointerPropertiesList(touch.rawPointerPropertiesList)
|
||||
.toArray(new PointerProperties[touch.pointerCount]);
|
||||
|
||||
return MotionEvent.obtain(
|
||||
touch.downTime.longValue(),
|
||||
touch.eventTime.longValue(),
|
||||
touch.action,
|
||||
touch.pointerCount,
|
||||
pointerProperties,
|
||||
pointerCoords,
|
||||
touch.metaState,
|
||||
touch.buttonState,
|
||||
touch.xPrecision,
|
||||
touch.yPrecision,
|
||||
touch.deviceId,
|
||||
touch.edgeFlags,
|
||||
touch.source,
|
||||
touch.flags);
|
||||
}
|
||||
|
||||
/**
|
||||
* Attaches this platform views controller to its input and output channels.
|
||||
*
|
||||
* @param context The base context that will be passed to embedded views created by this
|
||||
* controller. This should be the context of the Activity hosting the Flutter application.
|
||||
* @param dartExecutor The dart execution context, which is used to set up a system channel.
|
||||
*/
|
||||
public void attach(@Nullable Context context, @NonNull DartExecutor dartExecutor) {
|
||||
if (this.context != null) {
|
||||
throw new AssertionError(
|
||||
"A PlatformViewsController can only be attached to a single output target.\n"
|
||||
+ "attach was called while the PlatformViewsController was already attached.");
|
||||
}
|
||||
this.context = context;
|
||||
platformViewsChannel = new PlatformViewsChannel2(dartExecutor);
|
||||
platformViewsChannel.setPlatformViewsHandler(channelHandler);
|
||||
}
|
||||
|
||||
/**
|
||||
* Detaches this platform views controller.
|
||||
*
|
||||
* <p>This is typically called when a Flutter applications moves to run in the background, or is
|
||||
* destroyed. After calling this the platform views controller will no longer listen to it's
|
||||
* previous messenger, and will not maintain references to the texture registry, context, and
|
||||
* messenger passed to the previous attach call.
|
||||
*/
|
||||
@UiThread
|
||||
public void detach() {
|
||||
if (platformViewsChannel != null) {
|
||||
platformViewsChannel.setPlatformViewsHandler(null);
|
||||
}
|
||||
destroyOverlaySurface();
|
||||
platformViewsChannel = null;
|
||||
context = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Attaches the controller to a {@link FlutterView}.
|
||||
*
|
||||
* <p>When {@link io.flutter.embedding.android.FlutterFragment} is used, this method is called
|
||||
* after the device rotates since the FlutterView is recreated after a rotation.
|
||||
*/
|
||||
public void attachToView(@NonNull FlutterView newFlutterView) {
|
||||
flutterView = newFlutterView;
|
||||
// Add wrapper for platform views that are composed at the view hierarchy level.
|
||||
for (int index = 0; index < platformViewParent.size(); index++) {
|
||||
final FlutterMutatorView view = platformViewParent.valueAt(index);
|
||||
flutterView.addView(view);
|
||||
}
|
||||
// Notify platform views that they are now attached to a FlutterView.
|
||||
for (int index = 0; index < platformViews.size(); index++) {
|
||||
final PlatformView view = platformViews.valueAt(index);
|
||||
view.onFlutterViewAttached(flutterView);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Detaches the controller from {@link FlutterView}.
|
||||
*
|
||||
* <p>When {@link io.flutter.embedding.android.FlutterFragment} is used, this method is called
|
||||
* when the device rotates since the FlutterView is detached from the fragment. The next time the
|
||||
* fragment needs to be displayed, a new Flutter view is created, so attachToView is called again.
|
||||
*/
|
||||
public void detachFromView() {
|
||||
// Remove wrapper for platform views that are composed at the view hierarchy level.
|
||||
for (int index = 0; index < platformViewParent.size(); index++) {
|
||||
final FlutterMutatorView view = platformViewParent.valueAt(index);
|
||||
flutterView.removeView(view);
|
||||
}
|
||||
|
||||
destroyOverlaySurface();
|
||||
flutterView = null;
|
||||
|
||||
// Notify that the platform view have been detached from FlutterView.
|
||||
for (int index = 0; index < platformViews.size(); index++) {
|
||||
final PlatformView view = platformViews.valueAt(index);
|
||||
view.onFlutterViewDetached();
|
||||
}
|
||||
}
|
||||
|
||||
private void maybeInvokeOnFlutterViewAttached(PlatformView view) {
|
||||
if (flutterView == null) {
|
||||
Log.i(TAG, "null flutterView");
|
||||
// There is currently no FlutterView that we are attached to.
|
||||
return;
|
||||
}
|
||||
view.onFlutterViewAttached(flutterView);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void attachAccessibilityBridge(@NonNull AccessibilityBridge accessibilityBridge) {
|
||||
accessibilityEventsDelegate.setAccessibilityBridge(accessibilityBridge);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void detachAccessibilityBridge() {
|
||||
accessibilityEventsDelegate.setAccessibilityBridge(null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Attaches this controller to a text input plugin.
|
||||
*
|
||||
* <p>While a text input plugin is available, the platform views controller interacts with it to
|
||||
* facilitate delegation of text input connections to platform views.
|
||||
*
|
||||
* <p>A platform views controller should be attached to a text input plugin whenever it is
|
||||
* possible for the Flutter framework to receive text input.
|
||||
*/
|
||||
public void attachTextInputPlugin(@NonNull TextInputPlugin textInputPlugin) {
|
||||
this.textInputPlugin = textInputPlugin;
|
||||
}
|
||||
|
||||
/** Detaches this controller from the currently attached text input plugin. */
|
||||
public void detachTextInputPlugin() {
|
||||
textInputPlugin = null;
|
||||
}
|
||||
|
||||
public PlatformViewRegistry getRegistry() {
|
||||
return registry;
|
||||
}
|
||||
|
||||
/**
|
||||
* Invoked when the {@link io.flutter.embedding.engine.FlutterEngine} that owns this {@link
|
||||
* PlatformViewsController} detaches from JNI.
|
||||
*/
|
||||
public void onDetachedFromJNI() {
|
||||
diposeAllViews();
|
||||
}
|
||||
|
||||
public void onPreEngineRestart() {
|
||||
diposeAllViews();
|
||||
}
|
||||
|
||||
@Override
|
||||
@Nullable
|
||||
public View getPlatformViewById(int viewId) {
|
||||
final PlatformView platformView = platformViews.get(viewId);
|
||||
if (platformView == null) {
|
||||
return null;
|
||||
}
|
||||
return platformView.getView();
|
||||
}
|
||||
|
||||
private void lockInputConnection(@NonNull VirtualDisplayController controller) {
|
||||
if (textInputPlugin == null) {
|
||||
return;
|
||||
}
|
||||
textInputPlugin.lockPlatformViewInputConnection();
|
||||
controller.onInputConnectionLocked();
|
||||
}
|
||||
|
||||
private void unlockInputConnection(@NonNull VirtualDisplayController controller) {
|
||||
if (textInputPlugin == null) {
|
||||
return;
|
||||
}
|
||||
textInputPlugin.unlockPlatformViewInputConnection();
|
||||
controller.onInputConnectionUnlocked();
|
||||
}
|
||||
|
||||
private static boolean validateDirection(int direction) {
|
||||
return direction == View.LAYOUT_DIRECTION_LTR || direction == View.LAYOUT_DIRECTION_RTL;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private static List<PointerProperties> parsePointerPropertiesList(Object rawPropertiesList) {
|
||||
List<Object> rawProperties = (List<Object>) rawPropertiesList;
|
||||
List<PointerProperties> pointerProperties = new ArrayList<>();
|
||||
for (Object o : rawProperties) {
|
||||
pointerProperties.add(parsePointerProperties(o));
|
||||
}
|
||||
return pointerProperties;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private static PointerProperties parsePointerProperties(Object rawProperties) {
|
||||
List<Object> propertiesList = (List<Object>) rawProperties;
|
||||
PointerProperties properties = new MotionEvent.PointerProperties();
|
||||
properties.id = (int) propertiesList.get(0);
|
||||
properties.toolType = (int) propertiesList.get(1);
|
||||
return properties;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private static List<PointerCoords> parsePointerCoordsList(Object rawCoordsList, float density) {
|
||||
List<Object> rawCoords = (List<Object>) rawCoordsList;
|
||||
List<PointerCoords> pointerCoords = new ArrayList<>();
|
||||
for (Object o : rawCoords) {
|
||||
pointerCoords.add(parsePointerCoords(o, density));
|
||||
}
|
||||
return pointerCoords;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private static PointerCoords parsePointerCoords(Object rawCoords, float density) {
|
||||
List<Object> coordsList = (List<Object>) rawCoords;
|
||||
PointerCoords coords = new MotionEvent.PointerCoords();
|
||||
coords.orientation = (float) (double) coordsList.get(0);
|
||||
coords.pressure = (float) (double) coordsList.get(1);
|
||||
coords.size = (float) (double) coordsList.get(2);
|
||||
coords.toolMajor = (float) ((double) coordsList.get(3) * density);
|
||||
coords.toolMinor = (float) ((double) coordsList.get(4) * density);
|
||||
coords.touchMajor = (float) ((double) coordsList.get(5) * density);
|
||||
coords.touchMinor = (float) ((double) coordsList.get(6) * density);
|
||||
coords.x = (float) ((double) coordsList.get(7) * density);
|
||||
coords.y = (float) ((double) coordsList.get(8) * density);
|
||||
return coords;
|
||||
}
|
||||
|
||||
private float getDisplayDensity() {
|
||||
return context.getResources().getDisplayMetrics().density;
|
||||
}
|
||||
|
||||
private int toPhysicalPixels(double logicalPixels) {
|
||||
return (int) Math.round(logicalPixels * getDisplayDensity());
|
||||
}
|
||||
|
||||
private int toLogicalPixels(double physicalPixels, float displayDensity) {
|
||||
return (int) Math.round(physicalPixels / displayDensity);
|
||||
}
|
||||
|
||||
private int toLogicalPixels(double physicalPixels) {
|
||||
return toLogicalPixels(physicalPixels, getDisplayDensity());
|
||||
}
|
||||
|
||||
private void diposeAllViews() {
|
||||
while (platformViews.size() > 0) {
|
||||
final int viewId = platformViews.keyAt(0);
|
||||
// Dispose deletes the entry from platformViews and clears associated resources.
|
||||
channelHandler.dispose(viewId);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Disposes a single
|
||||
*
|
||||
* @param viewId the PlatformView ID.
|
||||
*/
|
||||
@VisibleForTesting
|
||||
public void disposePlatformView(int viewId) {
|
||||
channelHandler.dispose(viewId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes a platform view and adds it to the view hierarchy.
|
||||
*
|
||||
* @param viewId The view ID. This member is not intended for public use, and is only visible for
|
||||
* testing.
|
||||
*/
|
||||
@VisibleForTesting
|
||||
boolean initializePlatformViewIfNeeded(int viewId) {
|
||||
final PlatformView platformView = platformViews.get(viewId);
|
||||
if (platformView == null) {
|
||||
return false;
|
||||
}
|
||||
if (platformViewParent.get(viewId) != null) {
|
||||
return true;
|
||||
}
|
||||
final View embeddedView = platformView.getView();
|
||||
if (embeddedView == null) {
|
||||
throw new IllegalStateException(
|
||||
"PlatformView#getView() returned null, but an Android view reference was expected.");
|
||||
}
|
||||
if (embeddedView.getParent() != null) {
|
||||
throw new IllegalStateException(
|
||||
"The Android view returned from PlatformView#getView() was already added to a parent"
|
||||
+ " view.");
|
||||
}
|
||||
final FlutterMutatorView parentView =
|
||||
new FlutterMutatorView(
|
||||
context, context.getResources().getDisplayMetrics().density, androidTouchProcessor);
|
||||
|
||||
parentView.setOnDescendantFocusChangeListener(
|
||||
(view, hasFocus) -> {
|
||||
if (hasFocus) {
|
||||
platformViewsChannel.invokeViewFocused(viewId);
|
||||
} else if (textInputPlugin != null) {
|
||||
textInputPlugin.clearPlatformViewClient(viewId);
|
||||
}
|
||||
});
|
||||
|
||||
platformViewParent.put(viewId, parentView);
|
||||
|
||||
// Accessibility in the embedded view is initially disabled because if a Flutter app disabled
|
||||
// accessibility in the first frame, the embedding won't receive an update to disable
|
||||
// accessibility since the embedding never received an update to enable it.
|
||||
// The AccessibilityBridge keeps track of the accessibility nodes, and handles the deltas when
|
||||
// the framework sends a new a11y tree to the embedding.
|
||||
// To prevent races, the framework populate the SemanticsNode after the platform view has been
|
||||
// created.
|
||||
embeddedView.setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS);
|
||||
|
||||
parentView.addView(embeddedView);
|
||||
flutterView.addView(parentView);
|
||||
return true;
|
||||
}
|
||||
|
||||
public void attachToFlutterRenderer(@NonNull FlutterRenderer flutterRenderer) {
|
||||
androidTouchProcessor = new AndroidTouchProcessor(flutterRenderer, /*trackMotionEvents=*/ true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when a platform view id displayed in the current frame.
|
||||
*
|
||||
* @param viewId The ID of the platform view.
|
||||
* @param x The left position relative to {@code FlutterView}.
|
||||
* @param y The top position relative to {@code FlutterView}.
|
||||
* @param width The width of the platform view.
|
||||
* @param height The height of the platform view.
|
||||
* @param viewWidth The original width of the platform view before applying the mutator stack.
|
||||
* @param viewHeight The original height of the platform view before applying the mutator stack.
|
||||
* @param mutatorsStack The mutator stack. This member is not intended for public use, and is only
|
||||
* visible for testing.
|
||||
*/
|
||||
public void onDisplayPlatformView(
|
||||
int viewId,
|
||||
int x,
|
||||
int y,
|
||||
int width,
|
||||
int height,
|
||||
int viewWidth,
|
||||
int viewHeight,
|
||||
@NonNull FlutterMutatorsStack mutatorsStack) {
|
||||
if (!initializePlatformViewIfNeeded(viewId)) {
|
||||
return;
|
||||
}
|
||||
|
||||
final FlutterMutatorView parentView = platformViewParent.get(viewId);
|
||||
parentView.readyToDisplay(mutatorsStack, x, y, width, height);
|
||||
parentView.setVisibility(View.VISIBLE);
|
||||
parentView.bringToFront();
|
||||
|
||||
final FrameLayout.LayoutParams layoutParams =
|
||||
new FrameLayout.LayoutParams(viewWidth, viewHeight);
|
||||
final View view = platformViews.get(viewId).getView();
|
||||
if (view != null) {
|
||||
view.setLayoutParams(layoutParams);
|
||||
view.bringToFront();
|
||||
}
|
||||
}
|
||||
|
||||
@TargetApi(API_LEVELS.API_34)
|
||||
@RequiresApi(API_LEVELS.API_34)
|
||||
public void onEndFrame() {
|
||||
SurfaceControl.Transaction tx = new SurfaceControl.Transaction();
|
||||
for (int i = 0; i < activeTransactions.size(); i++) {
|
||||
tx = tx.merge(activeTransactions.get(i));
|
||||
}
|
||||
activeTransactions.clear();
|
||||
flutterView.invalidate();
|
||||
flutterView.getRootSurfaceControl().applyTransactionOnDraw(tx);
|
||||
}
|
||||
|
||||
// NOT called from UI thread.
|
||||
public synchronized void swapTransactions() {
|
||||
activeTransactions.clear();
|
||||
for (int i = 0; i < pendingTransactions.size(); i++) {
|
||||
activeTransactions.add(pendingTransactions.get(i));
|
||||
}
|
||||
pendingTransactions.clear();
|
||||
}
|
||||
|
||||
// NOT called from UI thread.
|
||||
@TargetApi(API_LEVELS.API_34)
|
||||
@RequiresApi(API_LEVELS.API_34)
|
||||
public SurfaceControl.Transaction createTransaction() {
|
||||
SurfaceControl.Transaction tx = new SurfaceControl.Transaction();
|
||||
pendingTransactions.add(tx);
|
||||
return tx;
|
||||
}
|
||||
|
||||
// NOT called from UI thread.
|
||||
@TargetApi(API_LEVELS.API_34)
|
||||
@RequiresApi(API_LEVELS.API_34)
|
||||
public void applyTransactions() {
|
||||
SurfaceControl.Transaction tx = new SurfaceControl.Transaction();
|
||||
for (int i = 0; i < pendingTransactions.size(); i++) {
|
||||
tx = tx.merge(pendingTransactions.get(i));
|
||||
}
|
||||
tx.apply();
|
||||
pendingTransactions.clear();
|
||||
}
|
||||
|
||||
@TargetApi(API_LEVELS.API_34)
|
||||
@RequiresApi(API_LEVELS.API_34)
|
||||
public FlutterOverlaySurface createOverlaySurface() {
|
||||
if (overlayerSurface == null) {
|
||||
final SurfaceControl.Builder surfaceControlBuilder = new SurfaceControl.Builder();
|
||||
surfaceControlBuilder.setBufferSize(flutterView.getWidth(), flutterView.getHeight());
|
||||
surfaceControlBuilder.setFormat(PixelFormat.RGBA_8888);
|
||||
surfaceControlBuilder.setName("Flutter Overlay Surface");
|
||||
surfaceControlBuilder.setOpaque(false);
|
||||
final SurfaceControl surfaceControl = surfaceControlBuilder.build();
|
||||
final SurfaceControl.Transaction tx =
|
||||
flutterView.getRootSurfaceControl().buildReparentTransaction(surfaceControl);
|
||||
tx.setLayer(surfaceControl, 1000);
|
||||
tx.apply();
|
||||
overlayerSurface = new Surface(surfaceControl);
|
||||
}
|
||||
|
||||
return new FlutterOverlaySurface(0, overlayerSurface);
|
||||
}
|
||||
|
||||
public void destroyOverlaySurface() {
|
||||
if (overlayerSurface != null) {
|
||||
overlayerSurface.release();
|
||||
overlayerSurface = null;
|
||||
}
|
||||
}
|
||||
|
||||
//// Message Handler ///////
|
||||
|
||||
private final PlatformViewsChannel2.PlatformViewsHandler channelHandler =
|
||||
new PlatformViewsChannel2.PlatformViewsHandler() {
|
||||
|
||||
@Override
|
||||
public void createPlatformView(
|
||||
@NonNull PlatformViewsChannel2.PlatformViewCreationRequest request) {
|
||||
createFlutterPlatformView(request);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dispose(int viewId) {
|
||||
final PlatformView platformView = platformViews.get(viewId);
|
||||
if (platformView == null) {
|
||||
Log.e(TAG, "Disposing unknown platform view with id: " + viewId);
|
||||
return;
|
||||
}
|
||||
if (platformView.getView() != null) {
|
||||
final View embeddedView = platformView.getView();
|
||||
final ViewGroup pvParent = (ViewGroup) embeddedView.getParent();
|
||||
if (pvParent != null) {
|
||||
// Eagerly remove the embedded view from the PlatformViewWrapper.
|
||||
// Without this call, we see some crashes because removing the view
|
||||
// is used as a signal to stop processing.
|
||||
pvParent.removeView(embeddedView);
|
||||
}
|
||||
}
|
||||
platformViews.remove(viewId);
|
||||
try {
|
||||
platformView.dispose();
|
||||
} catch (RuntimeException exception) {
|
||||
Log.e(TAG, "Disposing platform view threw an exception", exception);
|
||||
}
|
||||
|
||||
// The platform view is displayed using a PlatformViewLayer.
|
||||
final FlutterMutatorView parentView = platformViewParent.get(viewId);
|
||||
if (parentView != null) {
|
||||
parentView.removeAllViews();
|
||||
parentView.unsetOnDescendantFocusChangeListener();
|
||||
|
||||
final ViewGroup mutatorViewParent = (ViewGroup) parentView.getParent();
|
||||
if (mutatorViewParent != null) {
|
||||
mutatorViewParent.removeView(parentView);
|
||||
}
|
||||
platformViewParent.remove(viewId);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTouch(@NonNull PlatformViewsChannel2.PlatformViewTouch touch) {
|
||||
final int viewId = touch.viewId;
|
||||
final float density = context.getResources().getDisplayMetrics().density;
|
||||
|
||||
final PlatformView platformView = platformViews.get(viewId);
|
||||
if (platformView == null) {
|
||||
Log.e(TAG, "Sending touch to an unknown view with id: " + viewId);
|
||||
return;
|
||||
}
|
||||
final View view = platformView.getView();
|
||||
if (view == null) {
|
||||
Log.e(TAG, "Sending touch to a null view with id: " + viewId);
|
||||
return;
|
||||
}
|
||||
final MotionEvent event = toMotionEvent(density, touch);
|
||||
view.dispatchTouchEvent(event);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setDirection(int viewId, int direction) {
|
||||
final PlatformView platformView = platformViews.get(viewId);
|
||||
if (platformView == null) {
|
||||
Log.e(TAG, "Setting direction to an unknown view with id: " + viewId);
|
||||
return;
|
||||
}
|
||||
View embeddedView = platformView.getView();
|
||||
if (embeddedView == null) {
|
||||
Log.e(TAG, "Setting direction to a null view with id: " + viewId);
|
||||
return;
|
||||
}
|
||||
embeddedView.setLayoutDirection(direction);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clearFocus(int viewId) {
|
||||
final PlatformView platformView = platformViews.get(viewId);
|
||||
if (platformView == null) {
|
||||
Log.e(TAG, "Clearing focus on an unknown view with id: " + viewId);
|
||||
return;
|
||||
}
|
||||
View embeddedView = platformView.getView();
|
||||
if (embeddedView == null) {
|
||||
Log.e(TAG, "Clearing focus on a null view with id: " + viewId);
|
||||
return;
|
||||
}
|
||||
embeddedView.clearFocus();
|
||||
}
|
||||
};
|
||||
}
|
@ -55,6 +55,7 @@ import io.flutter.embedding.engine.systemchannels.SystemChannel;
|
||||
import io.flutter.embedding.engine.systemchannels.TextInputChannel;
|
||||
import io.flutter.plugin.localization.LocalizationPlugin;
|
||||
import io.flutter.plugin.platform.PlatformViewsController;
|
||||
import io.flutter.plugin.platform.PlatformViewsController2;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
@ -1475,6 +1476,7 @@ public class FlutterActivityAndFragmentDelegateTest {
|
||||
when(engine.getNavigationChannel()).thenReturn(mock(NavigationChannel.class));
|
||||
when(engine.getBackGestureChannel()).thenReturn(mock(BackGestureChannel.class));
|
||||
when(engine.getPlatformViewsController()).thenReturn(mock(PlatformViewsController.class));
|
||||
when(engine.getPlatformViewsController2()).thenReturn(mock(PlatformViewsController2.class));
|
||||
|
||||
FlutterRenderer renderer = mock(FlutterRenderer.class);
|
||||
when(engine.getRenderer()).thenReturn(renderer);
|
||||
|
@ -53,6 +53,7 @@ import io.flutter.embedding.engine.loader.FlutterLoader;
|
||||
import io.flutter.embedding.engine.renderer.FlutterRenderer;
|
||||
import io.flutter.embedding.engine.systemchannels.SettingsChannel;
|
||||
import io.flutter.plugin.platform.PlatformViewsController;
|
||||
import io.flutter.plugin.platform.PlatformViewsController2;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
@ -83,6 +84,7 @@ public class FlutterViewTest {
|
||||
@Mock FlutterJNI mockFlutterJni;
|
||||
@Mock FlutterLoader mockFlutterLoader;
|
||||
@Spy PlatformViewsController platformViewsController;
|
||||
@Spy PlatformViewsController2 platformViewsController2;
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
@ -97,10 +99,12 @@ public class FlutterViewTest {
|
||||
FlutterView flutterView = new FlutterView(ctx);
|
||||
FlutterEngine flutterEngine = spy(new FlutterEngine(ctx, mockFlutterLoader, mockFlutterJni));
|
||||
when(flutterEngine.getPlatformViewsController()).thenReturn(platformViewsController);
|
||||
when(flutterEngine.getPlatformViewsController2()).thenReturn(platformViewsController2);
|
||||
|
||||
flutterView.attachToFlutterEngine(flutterEngine);
|
||||
|
||||
verify(platformViewsController, times(1)).attachToView(flutterView);
|
||||
verify(platformViewsController2, times(1)).attachToView(flutterView);
|
||||
}
|
||||
|
||||
@Test
|
||||
@ -117,11 +121,13 @@ public class FlutterViewTest {
|
||||
FlutterView flutterView = new FlutterView(ctx);
|
||||
FlutterEngine flutterEngine = spy(new FlutterEngine(ctx, mockFlutterLoader, mockFlutterJni));
|
||||
when(flutterEngine.getPlatformViewsController()).thenReturn(platformViewsController);
|
||||
when(flutterEngine.getPlatformViewsController2()).thenReturn(platformViewsController2);
|
||||
|
||||
flutterView.attachToFlutterEngine(flutterEngine);
|
||||
flutterView.detachFromFlutterEngine();
|
||||
|
||||
verify(platformViewsController, times(1)).detachFromView();
|
||||
verify(platformViewsController2, times(1)).detachFromView();
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -234,7 +234,6 @@ public class FlutterEngineTest {
|
||||
platformViewsController,
|
||||
/*dartVmArgs=*/ new String[] {},
|
||||
/*automaticallyRegisterPlugins=*/ false);
|
||||
verify(platformViewsController, times(1)).onAttachedToJNI();
|
||||
|
||||
engine.destroy();
|
||||
verify(platformViewsController, times(1)).onDetachedFromJNI();
|
||||
|
@ -63,6 +63,7 @@ import io.flutter.plugin.common.BinaryMessenger;
|
||||
import io.flutter.plugin.common.JSONMethodCodec;
|
||||
import io.flutter.plugin.common.MethodCall;
|
||||
import io.flutter.plugin.platform.PlatformViewsController;
|
||||
import io.flutter.plugin.platform.PlatformViewsController2;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
@ -139,7 +140,11 @@ public class TextInputPluginTest {
|
||||
ScribeChannel scribeChannel = new ScribeChannel(mock(DartExecutor.class));
|
||||
TextInputPlugin textInputPlugin =
|
||||
new TextInputPlugin(
|
||||
testView, textInputChannel, scribeChannel, mock(PlatformViewsController.class));
|
||||
testView,
|
||||
textInputChannel,
|
||||
scribeChannel,
|
||||
mock(PlatformViewsController.class),
|
||||
mock(PlatformViewsController2.class));
|
||||
|
||||
ArgumentCaptor<String> channelCaptor = ArgumentCaptor.forClass(String.class);
|
||||
ArgumentCaptor<ByteBuffer> bufferCaptor = ArgumentCaptor.forClass(ByteBuffer.class);
|
||||
@ -160,7 +165,11 @@ public class TextInputPluginTest {
|
||||
ScribeChannel scribeChannel = new ScribeChannel(mock(DartExecutor.class));
|
||||
TextInputPlugin textInputPlugin =
|
||||
new TextInputPlugin(
|
||||
testView, textInputChannel, scribeChannel, mock(PlatformViewsController.class));
|
||||
testView,
|
||||
textInputChannel,
|
||||
scribeChannel,
|
||||
mock(PlatformViewsController.class),
|
||||
mock(PlatformViewsController2.class));
|
||||
textInputPlugin.setTextInputClient(
|
||||
0,
|
||||
new TextInputChannel.Configuration(
|
||||
@ -204,7 +213,11 @@ public class TextInputPluginTest {
|
||||
ScribeChannel scribeChannel = new ScribeChannel(mock(DartExecutor.class));
|
||||
TextInputPlugin textInputPlugin =
|
||||
new TextInputPlugin(
|
||||
testView, textInputChannel, scribeChannel, mock(PlatformViewsController.class));
|
||||
testView,
|
||||
textInputChannel,
|
||||
scribeChannel,
|
||||
mock(PlatformViewsController.class),
|
||||
mock(PlatformViewsController2.class));
|
||||
|
||||
// Here's no textInputPlugin.setTextInputClient()
|
||||
textInputPlugin.setTextInputEditingState(
|
||||
@ -223,7 +236,11 @@ public class TextInputPluginTest {
|
||||
ScribeChannel scribeChannel = new ScribeChannel(mock(DartExecutor.class));
|
||||
TextInputPlugin textInputPlugin =
|
||||
new TextInputPlugin(
|
||||
testView, textInputChannel, scribeChannel, mock(PlatformViewsController.class));
|
||||
testView,
|
||||
textInputChannel,
|
||||
scribeChannel,
|
||||
mock(PlatformViewsController.class),
|
||||
mock(PlatformViewsController2.class));
|
||||
textInputPlugin.setTextInputClient(
|
||||
0,
|
||||
new TextInputChannel.Configuration(
|
||||
@ -275,7 +292,11 @@ public class TextInputPluginTest {
|
||||
ScribeChannel scribeChannel = new ScribeChannel(mock(DartExecutor.class));
|
||||
TextInputPlugin textInputPlugin =
|
||||
new TextInputPlugin(
|
||||
testView, textInputChannel, scribeChannel, mock(PlatformViewsController.class));
|
||||
testView,
|
||||
textInputChannel,
|
||||
scribeChannel,
|
||||
mock(PlatformViewsController.class),
|
||||
mock(PlatformViewsController2.class));
|
||||
CharSequence newText = "I do not fear computers. I fear the lack of them.";
|
||||
|
||||
// Change InputTarget to FRAMEWORK_CLIENT.
|
||||
@ -390,7 +411,11 @@ public class TextInputPluginTest {
|
||||
ScribeChannel scribeChannel = new ScribeChannel(mock(DartExecutor.class));
|
||||
TextInputPlugin textInputPlugin =
|
||||
new TextInputPlugin(
|
||||
testView, textInputChannel, scribeChannel, mock(PlatformViewsController.class));
|
||||
testView,
|
||||
textInputChannel,
|
||||
scribeChannel,
|
||||
mock(PlatformViewsController.class),
|
||||
mock(PlatformViewsController2.class));
|
||||
CharSequence newText = "I do not fear computers. I fear the lack of them.";
|
||||
final TextEditingDelta expectedDelta =
|
||||
new TextEditingDelta("", 0, 0, newText, newText.length(), newText.length(), 0, 49);
|
||||
@ -521,7 +546,11 @@ public class TextInputPluginTest {
|
||||
ScribeChannel scribeChannel = new ScribeChannel(mock(DartExecutor.class));
|
||||
TextInputPlugin textInputPlugin =
|
||||
new TextInputPlugin(
|
||||
testView, textInputChannel, scribeChannel, mock(PlatformViewsController.class));
|
||||
testView,
|
||||
textInputChannel,
|
||||
scribeChannel,
|
||||
mock(PlatformViewsController.class),
|
||||
mock(PlatformViewsController2.class));
|
||||
CharSequence newText = "I do not fear computers. I fear the lack of them.";
|
||||
final TextEditingDelta expectedDelta =
|
||||
new TextEditingDelta("", 0, 0, newText, newText.length(), newText.length(), 0, 49);
|
||||
@ -632,7 +661,11 @@ public class TextInputPluginTest {
|
||||
ScribeChannel scribeChannel = new ScribeChannel(mock(DartExecutor.class));
|
||||
TextInputPlugin textInputPlugin =
|
||||
new TextInputPlugin(
|
||||
testView, textInputChannel, scribeChannel, mock(PlatformViewsController.class));
|
||||
testView,
|
||||
textInputChannel,
|
||||
scribeChannel,
|
||||
mock(PlatformViewsController.class),
|
||||
mock(PlatformViewsController2.class));
|
||||
CharSequence newText = "I do not fear computers. I fear the lack of them.";
|
||||
final TextEditingDelta expectedDelta =
|
||||
new TextEditingDelta(
|
||||
@ -744,7 +777,11 @@ public class TextInputPluginTest {
|
||||
ScribeChannel scribeChannel = new ScribeChannel(mock(DartExecutor.class));
|
||||
TextInputPlugin textInputPlugin =
|
||||
new TextInputPlugin(
|
||||
testView, textInputChannel, scribeChannel, mock(PlatformViewsController.class));
|
||||
testView,
|
||||
textInputChannel,
|
||||
scribeChannel,
|
||||
mock(PlatformViewsController.class),
|
||||
mock(PlatformViewsController2.class));
|
||||
CharSequence newText = "helfo";
|
||||
final TextEditingDelta expectedDelta = new TextEditingDelta(newText, 0, 5, "hello", 5, 5, 0, 5);
|
||||
|
||||
@ -853,7 +890,11 @@ public class TextInputPluginTest {
|
||||
ScribeChannel scribeChannel = new ScribeChannel(mock(DartExecutor.class));
|
||||
TextInputPlugin textInputPlugin =
|
||||
new TextInputPlugin(
|
||||
testView, textInputChannel, scribeChannel, mock(PlatformViewsController.class));
|
||||
testView,
|
||||
textInputChannel,
|
||||
scribeChannel,
|
||||
mock(PlatformViewsController.class),
|
||||
mock(PlatformViewsController2.class));
|
||||
|
||||
// Change InputTarget to FRAMEWORK_CLIENT.
|
||||
textInputPlugin.setTextInputClient(
|
||||
@ -946,7 +987,11 @@ public class TextInputPluginTest {
|
||||
ScribeChannel scribeChannel = new ScribeChannel(mock(DartExecutor.class));
|
||||
TextInputPlugin textInputPlugin =
|
||||
new TextInputPlugin(
|
||||
testView, textInputChannel, scribeChannel, mock(PlatformViewsController.class));
|
||||
testView,
|
||||
textInputChannel,
|
||||
scribeChannel,
|
||||
mock(PlatformViewsController.class),
|
||||
mock(PlatformViewsController2.class));
|
||||
textInputPlugin.setTextInputClient(
|
||||
0,
|
||||
new TextInputChannel.Configuration(
|
||||
@ -986,7 +1031,11 @@ public class TextInputPluginTest {
|
||||
ScribeChannel scribeChannel = new ScribeChannel(mock(DartExecutor.class));
|
||||
TextInputPlugin textInputPlugin =
|
||||
new TextInputPlugin(
|
||||
testView, textInputChannel, scribeChannel, mock(PlatformViewsController.class));
|
||||
testView,
|
||||
textInputChannel,
|
||||
scribeChannel,
|
||||
mock(PlatformViewsController.class),
|
||||
mock(PlatformViewsController2.class));
|
||||
textInputPlugin.setTextInputClient(
|
||||
0,
|
||||
new TextInputChannel.Configuration(
|
||||
@ -1036,7 +1085,11 @@ public class TextInputPluginTest {
|
||||
ScribeChannel scribeChannel = new ScribeChannel(mock(DartExecutor.class));
|
||||
TextInputPlugin textInputPlugin =
|
||||
new TextInputPlugin(
|
||||
testView, textInputChannel, scribeChannel, mock(PlatformViewsController.class));
|
||||
testView,
|
||||
textInputChannel,
|
||||
scribeChannel,
|
||||
mock(PlatformViewsController.class),
|
||||
mock(PlatformViewsController2.class));
|
||||
textInputPlugin.setTextInputClient(
|
||||
0,
|
||||
new TextInputChannel.Configuration(
|
||||
@ -1139,7 +1192,11 @@ public class TextInputPluginTest {
|
||||
ScribeChannel scribeChannel = new ScribeChannel(mock(DartExecutor.class));
|
||||
TextInputPlugin textInputPlugin =
|
||||
new TextInputPlugin(
|
||||
testView, textInputChannel, scribeChannel, mock(PlatformViewsController.class));
|
||||
testView,
|
||||
textInputChannel,
|
||||
scribeChannel,
|
||||
mock(PlatformViewsController.class),
|
||||
mock(PlatformViewsController2.class));
|
||||
textInputPlugin.setTextInputClient(
|
||||
0,
|
||||
new TextInputChannel.Configuration(
|
||||
@ -1168,7 +1225,11 @@ public class TextInputPluginTest {
|
||||
ScribeChannel scribeChannel = new ScribeChannel(mock(DartExecutor.class));
|
||||
TextInputPlugin textInputPlugin =
|
||||
new TextInputPlugin(
|
||||
testView, textInputChannel, scribeChannel, mock(PlatformViewsController.class));
|
||||
testView,
|
||||
textInputChannel,
|
||||
scribeChannel,
|
||||
mock(PlatformViewsController.class),
|
||||
mock(PlatformViewsController2.class));
|
||||
verify(textInputChannel, times(1)).setTextInputMethodHandler(isNotNull());
|
||||
textInputPlugin.destroy();
|
||||
verify(textInputChannel, times(1)).setTextInputMethodHandler(isNull());
|
||||
@ -1186,7 +1247,11 @@ public class TextInputPluginTest {
|
||||
ScribeChannel scribeChannel = new ScribeChannel(mock(DartExecutor.class));
|
||||
TextInputPlugin textInputPlugin =
|
||||
new TextInputPlugin(
|
||||
testView, textInputChannel, scribeChannel, mock(PlatformViewsController.class));
|
||||
testView,
|
||||
textInputChannel,
|
||||
scribeChannel,
|
||||
mock(PlatformViewsController.class),
|
||||
mock(PlatformViewsController2.class));
|
||||
textInputPlugin.setTextInputClient(
|
||||
0,
|
||||
new TextInputChannel.Configuration(
|
||||
@ -1263,7 +1328,11 @@ public class TextInputPluginTest {
|
||||
ScribeChannel scribeChannel = new ScribeChannel(mock(DartExecutor.class));
|
||||
TextInputPlugin textInputPlugin =
|
||||
new TextInputPlugin(
|
||||
testView, textInputChannel, scribeChannel, mock(PlatformViewsController.class));
|
||||
testView,
|
||||
textInputChannel,
|
||||
scribeChannel,
|
||||
mock(PlatformViewsController.class),
|
||||
mock(PlatformViewsController2.class));
|
||||
textInputPlugin.setTextInputClient(
|
||||
0,
|
||||
new TextInputChannel.Configuration(
|
||||
@ -1303,7 +1372,11 @@ public class TextInputPluginTest {
|
||||
ScribeChannel scribeChannel = new ScribeChannel(mock(DartExecutor.class));
|
||||
TextInputPlugin textInputPlugin =
|
||||
new TextInputPlugin(
|
||||
testView, textInputChannel, scribeChannel, mock(PlatformViewsController.class));
|
||||
testView,
|
||||
textInputChannel,
|
||||
scribeChannel,
|
||||
mock(PlatformViewsController.class),
|
||||
mock(PlatformViewsController2.class));
|
||||
textInputPlugin.setTextInputClient(
|
||||
0,
|
||||
new TextInputChannel.Configuration(
|
||||
@ -1335,7 +1408,11 @@ public class TextInputPluginTest {
|
||||
ScribeChannel scribeChannel = new ScribeChannel(mock(DartExecutor.class));
|
||||
TextInputPlugin textInputPlugin =
|
||||
new TextInputPlugin(
|
||||
testView, textInputChannel, scribeChannel, mock(PlatformViewsController.class));
|
||||
testView,
|
||||
textInputChannel,
|
||||
scribeChannel,
|
||||
mock(PlatformViewsController.class),
|
||||
mock(PlatformViewsController2.class));
|
||||
textInputPlugin.setTextInputClient(
|
||||
0,
|
||||
new TextInputChannel.Configuration(
|
||||
@ -1365,7 +1442,11 @@ public class TextInputPluginTest {
|
||||
ScribeChannel scribeChannel = new ScribeChannel(mock(DartExecutor.class));
|
||||
TextInputPlugin textInputPlugin =
|
||||
new TextInputPlugin(
|
||||
testView, textInputChannel, scribeChannel, mock(PlatformViewsController.class));
|
||||
testView,
|
||||
textInputChannel,
|
||||
scribeChannel,
|
||||
mock(PlatformViewsController.class),
|
||||
mock(PlatformViewsController2.class));
|
||||
textInputPlugin.setTextInputClient(
|
||||
0,
|
||||
new TextInputChannel.Configuration(
|
||||
@ -1399,7 +1480,11 @@ public class TextInputPluginTest {
|
||||
ScribeChannel scribeChannel = new ScribeChannel(mock(DartExecutor.class));
|
||||
TextInputPlugin textInputPlugin =
|
||||
new TextInputPlugin(
|
||||
testView, textInputChannel, scribeChannel, mock(PlatformViewsController.class));
|
||||
testView,
|
||||
textInputChannel,
|
||||
scribeChannel,
|
||||
mock(PlatformViewsController.class),
|
||||
mock(PlatformViewsController2.class));
|
||||
textInputPlugin.setTextInputClient(
|
||||
0,
|
||||
new TextInputChannel.Configuration(
|
||||
@ -1434,7 +1519,11 @@ public class TextInputPluginTest {
|
||||
ScribeChannel scribeChannel = new ScribeChannel(mock(DartExecutor.class));
|
||||
TextInputPlugin textInputPlugin =
|
||||
new TextInputPlugin(
|
||||
testView, textInputChannel, scribeChannel, mock(PlatformViewsController.class));
|
||||
testView,
|
||||
textInputChannel,
|
||||
scribeChannel,
|
||||
mock(PlatformViewsController.class),
|
||||
mock(PlatformViewsController2.class));
|
||||
textInputPlugin.setTextInputClient(
|
||||
0,
|
||||
new TextInputChannel.Configuration(
|
||||
@ -1473,7 +1562,11 @@ public class TextInputPluginTest {
|
||||
ScribeChannel scribeChannel = new ScribeChannel(mock(DartExecutor.class));
|
||||
TextInputPlugin textInputPlugin =
|
||||
new TextInputPlugin(
|
||||
testView, textInputChannel, scribeChannel, mock(PlatformViewsController.class));
|
||||
testView,
|
||||
textInputChannel,
|
||||
scribeChannel,
|
||||
mock(PlatformViewsController.class),
|
||||
mock(PlatformViewsController2.class));
|
||||
textInputPlugin.setTextInputClient(
|
||||
0,
|
||||
new TextInputChannel.Configuration(
|
||||
@ -1507,7 +1600,11 @@ public class TextInputPluginTest {
|
||||
ScribeChannel scribeChannel = new ScribeChannel(mock(DartExecutor.class));
|
||||
TextInputPlugin textInputPlugin =
|
||||
new TextInputPlugin(
|
||||
testView, textInputChannel, scribeChannel, mock(PlatformViewsController.class));
|
||||
testView,
|
||||
textInputChannel,
|
||||
scribeChannel,
|
||||
mock(PlatformViewsController.class),
|
||||
mock(PlatformViewsController2.class));
|
||||
textInputPlugin.setTextInputClient(
|
||||
0,
|
||||
new TextInputChannel.Configuration(
|
||||
@ -1542,7 +1639,11 @@ public class TextInputPluginTest {
|
||||
ScribeChannel scribeChannel = new ScribeChannel(mock(DartExecutor.class));
|
||||
TextInputPlugin textInputPlugin =
|
||||
new TextInputPlugin(
|
||||
testView, textInputChannel, scribeChannel, mock(PlatformViewsController.class));
|
||||
testView,
|
||||
textInputChannel,
|
||||
scribeChannel,
|
||||
mock(PlatformViewsController.class),
|
||||
mock(PlatformViewsController2.class));
|
||||
final TextInputChannel.Configuration.Autofill autofill =
|
||||
new TextInputChannel.Configuration.Autofill(
|
||||
"1", new String[] {}, null, new TextInputChannel.TextEditState("", 0, 0, -1, -1));
|
||||
@ -1604,7 +1705,11 @@ public class TextInputPluginTest {
|
||||
ScribeChannel scribeChannel = new ScribeChannel(mock(DartExecutor.class));
|
||||
TextInputPlugin textInputPlugin =
|
||||
new TextInputPlugin(
|
||||
testView, textInputChannel, scribeChannel, mock(PlatformViewsController.class));
|
||||
testView,
|
||||
textInputChannel,
|
||||
scribeChannel,
|
||||
mock(PlatformViewsController.class),
|
||||
mock(PlatformViewsController2.class));
|
||||
final TextInputChannel.Configuration.Autofill autofill =
|
||||
new TextInputChannel.Configuration.Autofill(
|
||||
"1", new String[] {}, null, new TextInputChannel.TextEditState("", 0, 0, -1, -1));
|
||||
@ -1643,7 +1748,11 @@ public class TextInputPluginTest {
|
||||
ScribeChannel scribeChannel = new ScribeChannel(mock(DartExecutor.class));
|
||||
TextInputPlugin textInputPlugin =
|
||||
new TextInputPlugin(
|
||||
testView, textInputChannel, scribeChannel, mock(PlatformViewsController.class));
|
||||
testView,
|
||||
textInputChannel,
|
||||
scribeChannel,
|
||||
mock(PlatformViewsController.class),
|
||||
mock(PlatformViewsController2.class));
|
||||
final TextInputChannel.Configuration.Autofill autofill =
|
||||
new TextInputChannel.Configuration.Autofill(
|
||||
"1",
|
||||
@ -1690,7 +1799,11 @@ public class TextInputPluginTest {
|
||||
ScribeChannel scribeChannel = new ScribeChannel(mock(DartExecutor.class));
|
||||
TextInputPlugin textInputPlugin =
|
||||
new TextInputPlugin(
|
||||
testView, textInputChannel, scribeChannel, mock(PlatformViewsController.class));
|
||||
testView,
|
||||
textInputChannel,
|
||||
scribeChannel,
|
||||
mock(PlatformViewsController.class),
|
||||
mock(PlatformViewsController2.class));
|
||||
final TextInputChannel.Configuration.Autofill autofill1 =
|
||||
new TextInputChannel.Configuration.Autofill(
|
||||
"1",
|
||||
@ -1784,7 +1897,11 @@ public class TextInputPluginTest {
|
||||
ScribeChannel scribeChannel = new ScribeChannel(mock(DartExecutor.class));
|
||||
TextInputPlugin textInputPlugin =
|
||||
new TextInputPlugin(
|
||||
testView, textInputChannel, scribeChannel, mock(PlatformViewsController.class));
|
||||
testView,
|
||||
textInputChannel,
|
||||
scribeChannel,
|
||||
mock(PlatformViewsController.class),
|
||||
mock(PlatformViewsController2.class));
|
||||
final TextInputChannel.Configuration.Autofill autofill =
|
||||
new TextInputChannel.Configuration.Autofill(
|
||||
"1",
|
||||
@ -1839,7 +1956,11 @@ public class TextInputPluginTest {
|
||||
ScribeChannel scribeChannel = new ScribeChannel(mock(DartExecutor.class));
|
||||
TextInputPlugin textInputPlugin =
|
||||
new TextInputPlugin(
|
||||
testView, textInputChannel, scribeChannel, mock(PlatformViewsController.class));
|
||||
testView,
|
||||
textInputChannel,
|
||||
scribeChannel,
|
||||
mock(PlatformViewsController.class),
|
||||
mock(PlatformViewsController2.class));
|
||||
|
||||
// Set up an autofill scenario with 2 fields.
|
||||
final TextInputChannel.Configuration.Autofill autofill1 =
|
||||
@ -1978,7 +2099,11 @@ public class TextInputPluginTest {
|
||||
ScribeChannel scribeChannel = new ScribeChannel(mock(DartExecutor.class));
|
||||
TextInputPlugin textInputPlugin =
|
||||
new TextInputPlugin(
|
||||
testView, textInputChannel, scribeChannel, mock(PlatformViewsController.class));
|
||||
testView,
|
||||
textInputChannel,
|
||||
scribeChannel,
|
||||
mock(PlatformViewsController.class),
|
||||
mock(PlatformViewsController2.class));
|
||||
|
||||
// Set up an autofill scenario with 2 fields.
|
||||
final TextInputChannel.Configuration.Autofill autofill1 =
|
||||
@ -2075,7 +2200,11 @@ public class TextInputPluginTest {
|
||||
ScribeChannel scribeChannel = new ScribeChannel(mock(DartExecutor.class));
|
||||
TextInputPlugin textInputPlugin =
|
||||
new TextInputPlugin(
|
||||
testView, textInputChannel, scribeChannel, mock(PlatformViewsController.class));
|
||||
testView,
|
||||
textInputChannel,
|
||||
scribeChannel,
|
||||
mock(PlatformViewsController.class),
|
||||
mock(PlatformViewsController2.class));
|
||||
// Set up an autofill scenario with 2 fields.
|
||||
final TextInputChannel.Configuration.Autofill autofillConfig =
|
||||
new TextInputChannel.Configuration.Autofill(
|
||||
@ -2128,7 +2257,11 @@ public class TextInputPluginTest {
|
||||
ScribeChannel scribeChannel = new ScribeChannel(mock(DartExecutor.class));
|
||||
TextInputPlugin textInputPlugin =
|
||||
new TextInputPlugin(
|
||||
testView, textInputChannel, scribeChannel, mock(PlatformViewsController.class));
|
||||
testView,
|
||||
textInputChannel,
|
||||
scribeChannel,
|
||||
mock(PlatformViewsController.class),
|
||||
mock(PlatformViewsController2.class));
|
||||
|
||||
// Set up an autofill scenario with 2 fields.
|
||||
final TextInputChannel.Configuration.Autofill autofill1 =
|
||||
@ -2249,7 +2382,11 @@ public class TextInputPluginTest {
|
||||
View testView = new View(ctx);
|
||||
TextInputPlugin textInputPlugin =
|
||||
new TextInputPlugin(
|
||||
testView, textInputChannel, scribeChannel, mock(PlatformViewsController.class));
|
||||
testView,
|
||||
textInputChannel,
|
||||
scribeChannel,
|
||||
mock(PlatformViewsController.class),
|
||||
mock(PlatformViewsController2.class));
|
||||
|
||||
verify(mockBinaryMessenger, times(1))
|
||||
.setMessageHandler(any(String.class), binaryMessageHandlerCaptor.capture());
|
||||
@ -2282,7 +2419,11 @@ public class TextInputPluginTest {
|
||||
View testView = new View(ctx);
|
||||
TextInputPlugin textInputPlugin =
|
||||
new TextInputPlugin(
|
||||
testView, textInputChannel, scribeChannel, mock(PlatformViewsController.class));
|
||||
testView,
|
||||
textInputChannel,
|
||||
scribeChannel,
|
||||
mock(PlatformViewsController.class),
|
||||
mock(PlatformViewsController2.class));
|
||||
|
||||
verify(mockBinaryMessenger, times(1))
|
||||
.setMessageHandler(any(String.class), binaryMessageHandlerCaptor.capture());
|
||||
@ -2314,7 +2455,11 @@ public class TextInputPluginTest {
|
||||
ScribeChannel scribeChannel = new ScribeChannel(mock(DartExecutor.class));
|
||||
TextInputPlugin textInputPlugin =
|
||||
new TextInputPlugin(
|
||||
testView, textInputChannel, scribeChannel, mock(PlatformViewsController.class));
|
||||
testView,
|
||||
textInputChannel,
|
||||
scribeChannel,
|
||||
mock(PlatformViewsController.class),
|
||||
mock(PlatformViewsController2.class));
|
||||
ImeSyncDeferringInsetsCallback imeSyncCallback = textInputPlugin.getImeSyncCallback();
|
||||
FlutterEngine flutterEngine = spy(new FlutterEngine(ctx, mockFlutterLoader, mockFlutterJni));
|
||||
FlutterRenderer flutterRenderer = spy(new FlutterRenderer(mockFlutterJni));
|
||||
@ -2397,7 +2542,11 @@ public class TextInputPluginTest {
|
||||
ScribeChannel scribeChannel = new ScribeChannel(mock(DartExecutor.class));
|
||||
TextInputPlugin textInputPlugin =
|
||||
new TextInputPlugin(
|
||||
testView, textInputChannel, scribeChannel, mock(PlatformViewsController.class));
|
||||
testView,
|
||||
textInputChannel,
|
||||
scribeChannel,
|
||||
mock(PlatformViewsController.class),
|
||||
mock(PlatformViewsController2.class));
|
||||
ImeSyncDeferringInsetsCallback imeSyncCallback = textInputPlugin.getImeSyncCallback();
|
||||
FlutterEngine flutterEngine = spy(new FlutterEngine(ctx, mockFlutterLoader, mockFlutterJni));
|
||||
FlutterRenderer flutterRenderer = spy(new FlutterRenderer(mockFlutterJni));
|
||||
@ -2478,7 +2627,11 @@ public class TextInputPluginTest {
|
||||
ScribeChannel scribeChannel = new ScribeChannel(mock(DartExecutor.class));
|
||||
TextInputPlugin textInputPlugin =
|
||||
new TextInputPlugin(
|
||||
testView, textInputChannel, scribeChannel, mock(PlatformViewsController.class));
|
||||
testView,
|
||||
textInputChannel,
|
||||
scribeChannel,
|
||||
mock(PlatformViewsController.class),
|
||||
mock(PlatformViewsController2.class));
|
||||
ImeSyncDeferringInsetsCallback imeSyncCallback = textInputPlugin.getImeSyncCallback();
|
||||
FlutterEngine flutterEngine = spy(new FlutterEngine(ctx, mockFlutterLoader, mockFlutterJni));
|
||||
FlutterRenderer flutterRenderer = spy(new FlutterRenderer(mockFlutterJni));
|
||||
|
@ -0,0 +1,726 @@
|
||||
// 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 io.flutter.embedding.engine.systemchannels.PlatformViewsChannel2.PlatformViewTouch;
|
||||
import static org.junit.Assert.*;
|
||||
import static org.mockito.ArgumentMatchers.*;
|
||||
import static org.mockito.Mockito.*;
|
||||
|
||||
import android.app.Presentation;
|
||||
import android.content.Context;
|
||||
import android.content.res.AssetManager;
|
||||
import android.util.SparseArray;
|
||||
import android.view.MotionEvent;
|
||||
import android.view.Surface;
|
||||
import android.view.SurfaceHolder;
|
||||
import android.view.SurfaceView;
|
||||
import android.view.View;
|
||||
import androidx.test.core.app.ApplicationProvider;
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
||||
import io.flutter.embedding.android.FlutterImageView;
|
||||
import io.flutter.embedding.android.FlutterSurfaceView;
|
||||
import io.flutter.embedding.android.FlutterView;
|
||||
import io.flutter.embedding.android.MotionEventTracker;
|
||||
import io.flutter.embedding.engine.FlutterEngine;
|
||||
import io.flutter.embedding.engine.FlutterJNI;
|
||||
import io.flutter.embedding.engine.dart.DartExecutor;
|
||||
import io.flutter.embedding.engine.mutatorsstack.FlutterMutatorView;
|
||||
import io.flutter.embedding.engine.renderer.FlutterRenderer;
|
||||
import io.flutter.embedding.engine.systemchannels.AccessibilityChannel;
|
||||
import io.flutter.embedding.engine.systemchannels.MouseCursorChannel;
|
||||
import io.flutter.embedding.engine.systemchannels.PlatformViewsChannel2;
|
||||
import io.flutter.embedding.engine.systemchannels.PlatformViewsChannel2.PlatformViewTouch;
|
||||
import io.flutter.embedding.engine.systemchannels.ScribeChannel;
|
||||
import io.flutter.embedding.engine.systemchannels.SettingsChannel;
|
||||
import io.flutter.embedding.engine.systemchannels.TextInputChannel;
|
||||
import io.flutter.plugin.common.MethodCall;
|
||||
import io.flutter.plugin.common.StandardMessageCodec;
|
||||
import io.flutter.plugin.common.StandardMethodCodec;
|
||||
import io.flutter.plugin.localization.LocalizationPlugin;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.robolectric.annotation.Config;
|
||||
import org.robolectric.annotation.Implementation;
|
||||
import org.robolectric.annotation.Implements;
|
||||
import org.robolectric.shadows.ShadowDialog;
|
||||
import org.robolectric.shadows.ShadowSurfaceView;
|
||||
|
||||
@Config(manifest = Config.NONE)
|
||||
@RunWith(AndroidJUnit4.class)
|
||||
public class PlatformViewsController2Test {
|
||||
// An implementation of PlatformView that counts invocations of its lifecycle callbacks.
|
||||
class CountingPlatformView implements PlatformView {
|
||||
static final String VIEW_TYPE_ID = "CountingPlatformView";
|
||||
private View view;
|
||||
|
||||
public CountingPlatformView(Context context) {
|
||||
view = new SurfaceView(context);
|
||||
}
|
||||
|
||||
public int disposeCalls = 0;
|
||||
public int attachCalls = 0;
|
||||
public int detachCalls = 0;
|
||||
|
||||
@Override
|
||||
public void dispose() {
|
||||
// We have been removed from the view hierarhy before the call to dispose.
|
||||
assertNull(view.getParent());
|
||||
disposeCalls++;
|
||||
}
|
||||
|
||||
@Override
|
||||
public View getView() {
|
||||
return view;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFlutterViewAttached(View flutterView) {
|
||||
attachCalls++;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFlutterViewDetached() {
|
||||
detachCalls++;
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
@Config(shadows = {ShadowFlutterJNI.class, ShadowPlatformTaskQueue.class})
|
||||
public void itRemovesPlatformViewBeforeDiposeIsCalled() {
|
||||
PlatformViewsController2 PlatformViewsController2 = new PlatformViewsController2();
|
||||
FlutterJNI jni = new FlutterJNI();
|
||||
attach(jni, PlatformViewsController2);
|
||||
// Get the platform view registry.
|
||||
PlatformViewRegistry registry = PlatformViewsController2.getRegistry();
|
||||
|
||||
// Register a factory for our platform view.
|
||||
registry.registerViewFactory(
|
||||
CountingPlatformView.VIEW_TYPE_ID,
|
||||
new PlatformViewFactory(StandardMessageCodec.INSTANCE) {
|
||||
@Override
|
||||
public PlatformView create(Context context, int viewId, Object args) {
|
||||
return new CountingPlatformView(context);
|
||||
}
|
||||
});
|
||||
|
||||
// Create the platform view.
|
||||
int viewId = 0;
|
||||
final PlatformViewsChannel2.PlatformViewCreationRequest request =
|
||||
new PlatformViewsChannel2.PlatformViewCreationRequest(
|
||||
viewId, CountingPlatformView.VIEW_TYPE_ID, 128, 128, View.LAYOUT_DIRECTION_LTR, null);
|
||||
PlatformView pView = PlatformViewsController2.createFlutterPlatformView(request);
|
||||
assertTrue(pView instanceof CountingPlatformView);
|
||||
CountingPlatformView cpv = (CountingPlatformView) pView;
|
||||
|
||||
PlatformViewsController2.disposePlatformView(viewId);
|
||||
assertEquals(1, cpv.disposeCalls);
|
||||
}
|
||||
|
||||
@Test
|
||||
@Config(shadows = {ShadowFlutterJNI.class, ShadowPlatformTaskQueue.class})
|
||||
public void itNotifiesPlatformViewsOfEngineAttachmentAndDetachment() {
|
||||
PlatformViewsController2 PlatformViewsController2 = new PlatformViewsController2();
|
||||
FlutterJNI jni = new FlutterJNI();
|
||||
attach(jni, PlatformViewsController2);
|
||||
// Get the platform view registry.
|
||||
PlatformViewRegistry registry = PlatformViewsController2.getRegistry();
|
||||
|
||||
// Register a factory for our platform view.
|
||||
registry.registerViewFactory(
|
||||
CountingPlatformView.VIEW_TYPE_ID,
|
||||
new PlatformViewFactory(StandardMessageCodec.INSTANCE) {
|
||||
@Override
|
||||
public PlatformView create(Context context, int viewId, Object args) {
|
||||
return new CountingPlatformView(context);
|
||||
}
|
||||
});
|
||||
|
||||
// Create the platform view.
|
||||
int viewId = 0;
|
||||
final PlatformViewsChannel2.PlatformViewCreationRequest request =
|
||||
new PlatformViewsChannel2.PlatformViewCreationRequest(
|
||||
viewId, CountingPlatformView.VIEW_TYPE_ID, 128, 128, View.LAYOUT_DIRECTION_LTR, null);
|
||||
|
||||
PlatformView pView = PlatformViewsController2.createFlutterPlatformView(request);
|
||||
assertTrue(pView instanceof CountingPlatformView);
|
||||
CountingPlatformView cpv = (CountingPlatformView) pView;
|
||||
assertEquals(1, cpv.attachCalls);
|
||||
assertEquals(0, cpv.detachCalls);
|
||||
assertEquals(0, cpv.disposeCalls);
|
||||
PlatformViewsController2.detachFromView();
|
||||
assertEquals(1, cpv.attachCalls);
|
||||
assertEquals(1, cpv.detachCalls);
|
||||
assertEquals(0, cpv.disposeCalls);
|
||||
PlatformViewsController2.disposePlatformView(viewId);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void itUsesActionEventTypeFromFrameworkEventAsActionChanged() {
|
||||
MotionEventTracker motionEventTracker = MotionEventTracker.getInstance();
|
||||
PlatformViewsController2 PlatformViewsController2 = new PlatformViewsController2();
|
||||
|
||||
MotionEvent original =
|
||||
MotionEvent.obtain(
|
||||
10, // downTime
|
||||
10, // eventTime
|
||||
261, // action
|
||||
0, // x
|
||||
0, // y
|
||||
0 // metaState
|
||||
);
|
||||
|
||||
MotionEventTracker.MotionEventId motionEventId = motionEventTracker.track(original);
|
||||
|
||||
PlatformViewTouch frameWorkTouch =
|
||||
new PlatformViewTouch(
|
||||
0, // viewId
|
||||
original.getDownTime(),
|
||||
original.getEventTime(),
|
||||
0, // action
|
||||
1, // pointerCount
|
||||
Arrays.asList(Arrays.asList(0, 0)), // pointer properties
|
||||
Arrays.asList(Arrays.asList(0., 1., 2., 3., 4., 5., 6., 7., 8.)), // pointer coords
|
||||
original.getMetaState(),
|
||||
original.getButtonState(),
|
||||
original.getXPrecision(),
|
||||
original.getYPrecision(),
|
||||
original.getDeviceId(),
|
||||
original.getEdgeFlags(),
|
||||
original.getSource(),
|
||||
original.getFlags(),
|
||||
motionEventId.getId());
|
||||
MotionEvent resolvedEvent =
|
||||
PlatformViewsController2.toMotionEvent(
|
||||
1, // density
|
||||
frameWorkTouch);
|
||||
assertEquals(resolvedEvent.getAction(), original.getAction());
|
||||
assertNotEquals(resolvedEvent.getAction(), frameWorkTouch.action);
|
||||
}
|
||||
|
||||
private MotionEvent makePlatformViewTouchAndInvokeToMotionEvent(
|
||||
PlatformViewsController2 PlatformViewsController2,
|
||||
MotionEventTracker motionEventTracker,
|
||||
MotionEvent original,
|
||||
boolean usingVirtualDisplays) {
|
||||
MotionEventTracker.MotionEventId motionEventId = motionEventTracker.track(original);
|
||||
|
||||
// Construct a PlatformViewTouch.rawPointerPropertiesList by doing the inverse of
|
||||
// PlatformViewsController2.parsePointerPropertiesList.
|
||||
List<List<Integer>> pointerProperties =
|
||||
Arrays.asList(Arrays.asList(original.getPointerId(0), original.getToolType(0)));
|
||||
// Construct a PlatformViewTouch.rawPointerCoords by doing the inverse of
|
||||
// PlatformViewsController2.parsePointerCoordsList.
|
||||
List<List<Double>> pointerCoordinates =
|
||||
Arrays.asList(
|
||||
Arrays.asList(
|
||||
(double) original.getOrientation(),
|
||||
(double) original.getPressure(),
|
||||
(double) original.getSize(),
|
||||
(double) original.getToolMajor(),
|
||||
(double) original.getToolMinor(),
|
||||
(double) original.getTouchMajor(),
|
||||
(double) original.getTouchMinor(),
|
||||
(double) original.getX(),
|
||||
(double) original.getY()));
|
||||
// Make a platform view touch from the motion event.
|
||||
PlatformViewTouch frameWorkTouchNonVd =
|
||||
new PlatformViewTouch(
|
||||
0, // viewId
|
||||
original.getDownTime(),
|
||||
original.getEventTime(),
|
||||
original.getAction(),
|
||||
1, // pointerCount
|
||||
pointerProperties, // pointer properties
|
||||
pointerCoordinates, // pointer coords
|
||||
original.getMetaState(),
|
||||
original.getButtonState(),
|
||||
original.getXPrecision(),
|
||||
original.getYPrecision(),
|
||||
original.getDeviceId(),
|
||||
original.getEdgeFlags(),
|
||||
original.getSource(),
|
||||
original.getFlags(),
|
||||
motionEventId.getId());
|
||||
|
||||
return PlatformViewsController2.toMotionEvent(
|
||||
1, // density
|
||||
frameWorkTouchNonVd);
|
||||
}
|
||||
|
||||
@Config(shadows = {ShadowFlutterJNI.class, ShadowPlatformTaskQueue.class})
|
||||
public void getPlatformViewById() {
|
||||
PlatformViewsController2 PlatformViewsController2 = new PlatformViewsController2();
|
||||
|
||||
int platformViewId = 0;
|
||||
assertNull(PlatformViewsController2.getPlatformViewById(platformViewId));
|
||||
|
||||
PlatformViewFactory viewFactory = mock(PlatformViewFactory.class);
|
||||
PlatformView platformView = mock(PlatformView.class);
|
||||
View androidView = mock(View.class);
|
||||
when(platformView.getView()).thenReturn(androidView);
|
||||
when(viewFactory.create(any(), eq(platformViewId), any())).thenReturn(platformView);
|
||||
PlatformViewsController2.getRegistry().registerViewFactory("testType", viewFactory);
|
||||
|
||||
FlutterJNI jni = new FlutterJNI();
|
||||
attach(jni, PlatformViewsController2);
|
||||
|
||||
// Simulate create call from the framework.
|
||||
createPlatformView(jni, PlatformViewsController2, platformViewId, "testType");
|
||||
|
||||
assertTrue(PlatformViewsController2.initializePlatformViewIfNeeded(platformViewId));
|
||||
|
||||
View resultAndroidView = PlatformViewsController2.getPlatformViewById(platformViewId);
|
||||
assertNotNull(resultAndroidView);
|
||||
assertEquals(resultAndroidView, androidView);
|
||||
}
|
||||
|
||||
@Test
|
||||
@Config(shadows = {ShadowFlutterJNI.class, ShadowPlatformTaskQueue.class})
|
||||
public void createPlatformViewMessage_initializesAndroidView() {
|
||||
PlatformViewsController2 PlatformViewsController2 = new PlatformViewsController2();
|
||||
|
||||
int platformViewId = 0;
|
||||
assertNull(PlatformViewsController2.getPlatformViewById(platformViewId));
|
||||
|
||||
PlatformViewFactory viewFactory = mock(PlatformViewFactory.class);
|
||||
PlatformView platformView = mock(PlatformView.class);
|
||||
when(platformView.getView()).thenReturn(mock(View.class));
|
||||
when(viewFactory.create(any(), eq(platformViewId), any())).thenReturn(platformView);
|
||||
PlatformViewsController2.getRegistry().registerViewFactory("testType", viewFactory);
|
||||
|
||||
FlutterJNI jni = new FlutterJNI();
|
||||
attach(jni, PlatformViewsController2);
|
||||
|
||||
// Simulate create call from the framework.
|
||||
createPlatformView(jni, PlatformViewsController2, platformViewId, "testType");
|
||||
verify(viewFactory, times(1)).create(any(), eq(platformViewId), any());
|
||||
}
|
||||
|
||||
@Test
|
||||
@Config(shadows = {ShadowFlutterJNI.class, ShadowPlatformTaskQueue.class})
|
||||
public void createPlatformViewMessage_setsAndroidViewLayoutDirection() {
|
||||
PlatformViewsController2 PlatformViewsController2 = new PlatformViewsController2();
|
||||
|
||||
int platformViewId = 0;
|
||||
assertNull(PlatformViewsController2.getPlatformViewById(platformViewId));
|
||||
|
||||
PlatformViewFactory viewFactory = mock(PlatformViewFactory.class);
|
||||
PlatformView platformView = mock(PlatformView.class);
|
||||
|
||||
View androidView = mock(View.class);
|
||||
when(platformView.getView()).thenReturn(androidView);
|
||||
when(viewFactory.create(any(), eq(platformViewId), any())).thenReturn(platformView);
|
||||
PlatformViewsController2.getRegistry().registerViewFactory("testType", viewFactory);
|
||||
|
||||
FlutterJNI jni = new FlutterJNI();
|
||||
attach(jni, PlatformViewsController2);
|
||||
|
||||
// Simulate create call from the framework.
|
||||
createPlatformView(jni, PlatformViewsController2, platformViewId, "testType");
|
||||
verify(androidView, times(1)).setLayoutDirection(0);
|
||||
}
|
||||
|
||||
@Test
|
||||
@Config(shadows = {ShadowFlutterJNI.class, ShadowPlatformTaskQueue.class})
|
||||
public void createPlatformViewMessage_throwsIfViewIsNull() {
|
||||
PlatformViewsController2 PlatformViewsController2 = new PlatformViewsController2();
|
||||
|
||||
int platformViewId = 0;
|
||||
assertNull(PlatformViewsController2.getPlatformViewById(platformViewId));
|
||||
|
||||
PlatformViewFactory viewFactory = mock(PlatformViewFactory.class);
|
||||
PlatformView platformView = mock(PlatformView.class);
|
||||
when(platformView.getView()).thenReturn(null);
|
||||
when(viewFactory.create(any(), eq(platformViewId), any())).thenReturn(platformView);
|
||||
PlatformViewsController2.getRegistry().registerViewFactory("testType", viewFactory);
|
||||
|
||||
FlutterJNI jni = new FlutterJNI();
|
||||
attach(jni, PlatformViewsController2);
|
||||
|
||||
// Simulate create call from the framework.
|
||||
createPlatformView(jni, PlatformViewsController2, platformViewId, "testType");
|
||||
assertEquals(ShadowFlutterJNI.getResponses().size(), 1);
|
||||
|
||||
assertFalse(PlatformViewsController2.initializePlatformViewIfNeeded(platformViewId));
|
||||
}
|
||||
|
||||
@Test
|
||||
@Config(shadows = {ShadowFlutterJNI.class, ShadowPlatformTaskQueue.class})
|
||||
public void createHybridPlatformViewMessage_throwsIfViewIsNull() {
|
||||
PlatformViewsController2 PlatformViewsController2 = new PlatformViewsController2();
|
||||
|
||||
int platformViewId = 0;
|
||||
assertNull(PlatformViewsController2.getPlatformViewById(platformViewId));
|
||||
|
||||
PlatformViewFactory viewFactory = mock(PlatformViewFactory.class);
|
||||
PlatformView platformView = mock(PlatformView.class);
|
||||
when(platformView.getView()).thenReturn(null);
|
||||
when(viewFactory.create(any(), eq(platformViewId), any())).thenReturn(platformView);
|
||||
PlatformViewsController2.getRegistry().registerViewFactory("testType", viewFactory);
|
||||
|
||||
FlutterJNI jni = new FlutterJNI();
|
||||
attach(jni, PlatformViewsController2);
|
||||
|
||||
// Simulate create call from the framework.
|
||||
createPlatformView(jni, PlatformViewsController2, platformViewId, "testType");
|
||||
assertEquals(ShadowFlutterJNI.getResponses().size(), 1);
|
||||
|
||||
assertFalse(PlatformViewsController2.initializePlatformViewIfNeeded(platformViewId));
|
||||
}
|
||||
|
||||
@Test
|
||||
@Config(shadows = {ShadowFlutterJNI.class, ShadowPlatformTaskQueue.class})
|
||||
public void setPlatformViewDirection_throwIfPlatformViewNotFound() {
|
||||
PlatformViewsController2 PlatformViewsController2 = new PlatformViewsController2();
|
||||
|
||||
int platformViewId = 0;
|
||||
assertNull(PlatformViewsController2.getPlatformViewById(platformViewId));
|
||||
|
||||
PlatformViewFactory viewFactory = mock(PlatformViewFactory.class);
|
||||
PlatformView platformView = mock(PlatformView.class);
|
||||
final View androidView = mock(View.class);
|
||||
when(platformView.getView()).thenReturn(androidView);
|
||||
when(viewFactory.create(any(), eq(platformViewId), any())).thenReturn(platformView);
|
||||
PlatformViewsController2.getRegistry().registerViewFactory("testType", viewFactory);
|
||||
|
||||
FlutterJNI jni = new FlutterJNI();
|
||||
attach(jni, PlatformViewsController2);
|
||||
|
||||
verify(androidView, never()).setLayoutDirection(anyInt());
|
||||
|
||||
// Simulate create call from the framework.
|
||||
createPlatformView(jni, PlatformViewsController2, platformViewId, "testType");
|
||||
assertEquals(ShadowFlutterJNI.getResponses().size(), 1);
|
||||
|
||||
// Simulate set direction call from the framework.
|
||||
setLayoutDirection(jni, PlatformViewsController2, platformViewId, 1);
|
||||
verify(androidView, times(1)).setLayoutDirection(1);
|
||||
|
||||
// The limit value of reply message will be equal to 2 if the layout direction is set
|
||||
// successfully, otherwise it will be much more than 2 due to the reply message contains
|
||||
// an error message wrapped with exception detail information.
|
||||
assertEquals(ShadowFlutterJNI.getResponses().get(0).limit(), 2);
|
||||
}
|
||||
|
||||
@Test
|
||||
@Config(shadows = {ShadowFlutterJNI.class, ShadowPlatformTaskQueue.class})
|
||||
public void disposeAndroidView() {
|
||||
PlatformViewsController2 PlatformViewsController2 = new PlatformViewsController2();
|
||||
|
||||
int platformViewId = 0;
|
||||
assertNull(PlatformViewsController2.getPlatformViewById(platformViewId));
|
||||
|
||||
PlatformViewFactory viewFactory = mock(PlatformViewFactory.class);
|
||||
PlatformView platformView = mock(PlatformView.class);
|
||||
|
||||
Context context = ApplicationProvider.getApplicationContext();
|
||||
View androidView = new View(context);
|
||||
|
||||
when(platformView.getView()).thenReturn(androidView);
|
||||
when(viewFactory.create(any(), eq(platformViewId), any())).thenReturn(platformView);
|
||||
PlatformViewsController2.getRegistry().registerViewFactory("testType", viewFactory);
|
||||
|
||||
FlutterJNI jni = new FlutterJNI();
|
||||
attach(jni, PlatformViewsController2);
|
||||
|
||||
// Simulate create call from the framework.
|
||||
createPlatformView(jni, PlatformViewsController2, platformViewId, "testType");
|
||||
assertTrue(PlatformViewsController2.initializePlatformViewIfNeeded(platformViewId));
|
||||
|
||||
assertNotNull(androidView.getParent());
|
||||
assertTrue(androidView.getParent() instanceof FlutterMutatorView);
|
||||
|
||||
// Simulate dispose call from the framework.
|
||||
disposePlatformView(jni, PlatformViewsController2, platformViewId);
|
||||
assertNull(androidView.getParent());
|
||||
|
||||
// Simulate create call from the framework.
|
||||
createPlatformView(jni, PlatformViewsController2, platformViewId, "testType");
|
||||
assertTrue(PlatformViewsController2.initializePlatformViewIfNeeded(platformViewId));
|
||||
|
||||
assertNotNull(androidView.getParent());
|
||||
assertTrue(androidView.getParent() instanceof FlutterMutatorView);
|
||||
verify(platformView, times(1)).dispose();
|
||||
}
|
||||
|
||||
@Test
|
||||
@Config(shadows = {ShadowFlutterJNI.class, ShadowPlatformTaskQueue.class})
|
||||
public void disposeNullAndroidView() {
|
||||
PlatformViewsController2 PlatformViewsController2 = new PlatformViewsController2();
|
||||
|
||||
int platformViewId = 0;
|
||||
assertNull(PlatformViewsController2.getPlatformViewById(platformViewId));
|
||||
|
||||
PlatformViewFactory viewFactory = mock(PlatformViewFactory.class);
|
||||
PlatformView platformView = mock(PlatformView.class);
|
||||
|
||||
View androidView = mock(View.class);
|
||||
when(platformView.getView()).thenReturn(androidView);
|
||||
when(viewFactory.create(any(), eq(platformViewId), any())).thenReturn(platformView);
|
||||
PlatformViewsController2.getRegistry().registerViewFactory("testType", viewFactory);
|
||||
|
||||
FlutterJNI jni = new FlutterJNI();
|
||||
attach(jni, PlatformViewsController2);
|
||||
|
||||
// Simulate create call from the framework.
|
||||
createPlatformView(jni, PlatformViewsController2, platformViewId, "testType");
|
||||
assertTrue(PlatformViewsController2.initializePlatformViewIfNeeded(platformViewId));
|
||||
|
||||
when(platformView.getView()).thenReturn(null);
|
||||
|
||||
// Simulate dispose call from the framework.
|
||||
disposePlatformView(jni, PlatformViewsController2, platformViewId);
|
||||
verify(platformView, times(1)).dispose();
|
||||
}
|
||||
|
||||
private static ByteBuffer encodeMethodCall(MethodCall call) {
|
||||
final ByteBuffer buffer = StandardMethodCodec.INSTANCE.encodeMethodCall(call);
|
||||
buffer.rewind();
|
||||
return buffer;
|
||||
}
|
||||
|
||||
private static void createPlatformView(
|
||||
FlutterJNI jni,
|
||||
PlatformViewsController2 PlatformViewsController2,
|
||||
int platformViewId,
|
||||
String viewType) {
|
||||
final Map<String, Object> args = new HashMap<>();
|
||||
args.put("id", platformViewId);
|
||||
args.put("viewType", viewType);
|
||||
args.put("direction", 0);
|
||||
args.put("width", 1.0);
|
||||
args.put("height", 1.0);
|
||||
|
||||
final MethodCall platformCreateMethodCall = new MethodCall("create", args);
|
||||
|
||||
jni.handlePlatformMessage(
|
||||
"flutter/platform_views_2",
|
||||
encodeMethodCall(platformCreateMethodCall),
|
||||
/*replyId=*/ 0,
|
||||
/*messageData=*/ 0);
|
||||
}
|
||||
|
||||
private static void setLayoutDirection(
|
||||
FlutterJNI jni,
|
||||
PlatformViewsController2 PlatformViewsController2,
|
||||
int platformViewId,
|
||||
int direction) {
|
||||
final Map<String, Object> args = new HashMap<>();
|
||||
args.put("id", platformViewId);
|
||||
args.put("direction", direction);
|
||||
|
||||
final MethodCall platformSetDirectionMethodCall = new MethodCall("setDirection", args);
|
||||
|
||||
jni.handlePlatformMessage(
|
||||
"flutter/platform_views_2",
|
||||
encodeMethodCall(platformSetDirectionMethodCall),
|
||||
/*replyId=*/ 0,
|
||||
/*messageData=*/ 0);
|
||||
}
|
||||
|
||||
private static void disposePlatformView(
|
||||
FlutterJNI jni, PlatformViewsController2 PlatformViewsController2, int platformViewId) {
|
||||
|
||||
final Map<String, Object> args = new HashMap<>();
|
||||
args.put("id", platformViewId);
|
||||
|
||||
final MethodCall platformDisposeMethodCall = new MethodCall("dispose", args);
|
||||
|
||||
jni.handlePlatformMessage(
|
||||
"flutter/platform_views_2",
|
||||
encodeMethodCall(platformDisposeMethodCall),
|
||||
/*replyId=*/ 0,
|
||||
/*messageData=*/ 0);
|
||||
}
|
||||
|
||||
private static void synchronizeToNativeViewHierarchy(
|
||||
FlutterJNI jni, PlatformViewsController2 PlatformViewsController2, boolean yes) {
|
||||
|
||||
final MethodCall convertMethodCall = new MethodCall("synchronizeToNativeViewHierarchy", yes);
|
||||
|
||||
jni.handlePlatformMessage(
|
||||
"flutter/platform_views_2",
|
||||
encodeMethodCall(convertMethodCall),
|
||||
/*replyId=*/ 0,
|
||||
/*messageData=*/ 0);
|
||||
}
|
||||
|
||||
private static FlutterView attach(
|
||||
FlutterJNI jni, PlatformViewsController2 PlatformViewsController2) {
|
||||
final Context context = ApplicationProvider.getApplicationContext();
|
||||
final FlutterView flutterView =
|
||||
new FlutterView(context, new FlutterSurfaceView(context)) {
|
||||
@Override
|
||||
public FlutterImageView createImageView() {
|
||||
final FlutterImageView view = mock(FlutterImageView.class);
|
||||
when(view.acquireLatestImage()).thenReturn(true);
|
||||
return mock(FlutterImageView.class);
|
||||
}
|
||||
};
|
||||
attachToFlutterView(jni, PlatformViewsController2, flutterView);
|
||||
return flutterView;
|
||||
}
|
||||
|
||||
private static void attachToFlutterView(
|
||||
FlutterJNI jni, PlatformViewsController2 PlatformViewsController2, FlutterView flutterView) {
|
||||
final DartExecutor executor = new DartExecutor(jni, mock(AssetManager.class));
|
||||
executor.onAttachedToJNI();
|
||||
|
||||
final Context context = ApplicationProvider.getApplicationContext();
|
||||
PlatformViewsController2.attach(context, executor);
|
||||
|
||||
PlatformViewsController oldController = new PlatformViewsController();
|
||||
|
||||
final FlutterEngine engine = mock(FlutterEngine.class);
|
||||
when(engine.getRenderer()).thenReturn(new FlutterRenderer(jni));
|
||||
when(engine.getMouseCursorChannel()).thenReturn(mock(MouseCursorChannel.class));
|
||||
when(engine.getTextInputChannel()).thenReturn(mock(TextInputChannel.class));
|
||||
when(engine.getSettingsChannel()).thenReturn(new SettingsChannel(executor));
|
||||
when(engine.getScribeChannel()).thenReturn(mock(ScribeChannel.class));
|
||||
when(engine.getPlatformViewsController2()).thenReturn(PlatformViewsController2);
|
||||
when(engine.getPlatformViewsController()).thenReturn(oldController);
|
||||
when(engine.getLocalizationPlugin()).thenReturn(mock(LocalizationPlugin.class));
|
||||
when(engine.getAccessibilityChannel()).thenReturn(mock(AccessibilityChannel.class));
|
||||
when(engine.getDartExecutor()).thenReturn(executor);
|
||||
|
||||
flutterView.attachToFlutterEngine(engine);
|
||||
PlatformViewsController2.attachToView(flutterView);
|
||||
}
|
||||
|
||||
/**
|
||||
* For convenience when writing tests, this allows us to make fake messages from Flutter via
|
||||
* Platform Channels. Typically those calls happen on the ui thread which dispatches to the
|
||||
* platform thread. Since tests run on the platform thread it makes it difficult to test without
|
||||
* this, but isn't technically required.
|
||||
*/
|
||||
@Implements(io.flutter.embedding.engine.dart.PlatformTaskQueue.class)
|
||||
public static class ShadowPlatformTaskQueue {
|
||||
@Implementation
|
||||
public void dispatch(Runnable runnable) {
|
||||
runnable.run();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The shadow class of {@link Presentation} to simulate Presentation showing logic.
|
||||
*
|
||||
* <p>Robolectric doesn't support VirtualDisplay creating correctly now, so this shadow class is
|
||||
* used to simulate custom logic for Presentation.
|
||||
*/
|
||||
@Implements(Presentation.class)
|
||||
public static class ShadowPresentation extends ShadowDialog {
|
||||
private boolean isShowing = false;
|
||||
|
||||
public ShadowPresentation() {}
|
||||
|
||||
@Implementation
|
||||
protected void show() {
|
||||
isShowing = true;
|
||||
}
|
||||
|
||||
@Implementation
|
||||
protected void dismiss() {
|
||||
isShowing = false;
|
||||
}
|
||||
|
||||
@Implementation
|
||||
protected boolean isShowing() {
|
||||
return isShowing;
|
||||
}
|
||||
}
|
||||
|
||||
@Implements(FlutterJNI.class)
|
||||
public static class ShadowFlutterJNI {
|
||||
private static SparseArray<ByteBuffer> replies = new SparseArray<>();
|
||||
|
||||
public ShadowFlutterJNI() {}
|
||||
|
||||
@Implementation
|
||||
public boolean getIsSoftwareRenderingEnabled() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Implementation
|
||||
public long performNativeAttach(FlutterJNI flutterJNI) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
@Implementation
|
||||
public void dispatchPlatformMessage(
|
||||
String channel, ByteBuffer message, int position, int responseId) {}
|
||||
|
||||
@Implementation
|
||||
public void onSurfaceCreated(Surface surface) {}
|
||||
|
||||
@Implementation
|
||||
public void onSurfaceDestroyed() {}
|
||||
|
||||
@Implementation
|
||||
public void onSurfaceWindowChanged(Surface surface) {}
|
||||
|
||||
@Implementation
|
||||
public void setViewportMetrics(
|
||||
float devicePixelRatio,
|
||||
int physicalWidth,
|
||||
int physicalHeight,
|
||||
int physicalPaddingTop,
|
||||
int physicalPaddingRight,
|
||||
int physicalPaddingBottom,
|
||||
int physicalPaddingLeft,
|
||||
int physicalViewInsetTop,
|
||||
int physicalViewInsetRight,
|
||||
int physicalViewInsetBottom,
|
||||
int physicalViewInsetLeft,
|
||||
int systemGestureInsetTop,
|
||||
int systemGestureInsetRight,
|
||||
int systemGestureInsetBottom,
|
||||
int systemGestureInsetLeft,
|
||||
int physicalTouchSlop,
|
||||
int[] displayFeaturesBounds,
|
||||
int[] displayFeaturesType,
|
||||
int[] displayFeaturesState) {}
|
||||
|
||||
@Implementation
|
||||
public void invokePlatformMessageResponseCallback(
|
||||
int responseId, ByteBuffer message, int position) {
|
||||
replies.put(responseId, message);
|
||||
}
|
||||
|
||||
public static SparseArray<ByteBuffer> getResponses() {
|
||||
return replies;
|
||||
}
|
||||
}
|
||||
|
||||
@Implements(SurfaceView.class)
|
||||
public static class ShadowFlutterSurfaceView extends ShadowSurfaceView {
|
||||
private final FakeSurfaceHolder holder = new FakeSurfaceHolder();
|
||||
|
||||
public static class FakeSurfaceHolder extends ShadowSurfaceView.FakeSurfaceHolder {
|
||||
private final Surface surface = mock(Surface.class);
|
||||
|
||||
public Surface getSurface() {
|
||||
return surface;
|
||||
}
|
||||
|
||||
@Implementation
|
||||
public void addCallback(SurfaceHolder.Callback callback) {
|
||||
callback.surfaceCreated(this);
|
||||
}
|
||||
}
|
||||
|
||||
public ShadowFlutterSurfaceView() {}
|
||||
|
||||
@Implementation
|
||||
public SurfaceHolder getHolder() {
|
||||
return holder;
|
||||
}
|
||||
}
|
||||
}
|
@ -1664,6 +1664,8 @@ public class PlatformViewsControllerTest {
|
||||
|
||||
platformViewsController.attach(context, registry, executor);
|
||||
|
||||
PlatformViewsController2 secondController = new PlatformViewsController2();
|
||||
|
||||
final FlutterEngine engine = mock(FlutterEngine.class);
|
||||
when(engine.getRenderer()).thenReturn(new FlutterRenderer(jni));
|
||||
when(engine.getMouseCursorChannel()).thenReturn(mock(MouseCursorChannel.class));
|
||||
@ -1671,6 +1673,7 @@ public class PlatformViewsControllerTest {
|
||||
when(engine.getSettingsChannel()).thenReturn(new SettingsChannel(executor));
|
||||
when(engine.getScribeChannel()).thenReturn(mock(ScribeChannel.class));
|
||||
when(engine.getPlatformViewsController()).thenReturn(platformViewsController);
|
||||
when(engine.getPlatformViewsController2()).thenReturn(secondController);
|
||||
when(engine.getLocalizationPlugin()).thenReturn(mock(LocalizationPlugin.class));
|
||||
when(engine.getAccessibilityChannel()).thenReturn(mock(AccessibilityChannel.class));
|
||||
when(engine.getDartExecutor()).thenReturn(executor);
|
||||
|
Loading…
x
Reference in New Issue
Block a user