[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/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/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/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/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/RestorationChannel.java + ../../../flutter/LICENSE
|
||||||
ORIGIN: ../../../flutter/shell/platform/android/io/flutter/embedding/engine/systemchannels/ScribeChannel.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/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/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/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/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/SingleViewPresentation.java + ../../../flutter/LICENSE
|
||||||
ORIGIN: ../../../flutter/shell/platform/android/io/flutter/plugin/platform/SingleViewWindowManager.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/NavigationChannel.java
|
||||||
FILE: ../../../flutter/shell/platform/android/io/flutter/embedding/engine/systemchannels/PlatformChannel.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/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/ProcessTextChannel.java
|
||||||
FILE: ../../../flutter/shell/platform/android/io/flutter/embedding/engine/systemchannels/RestorationChannel.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
|
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/PlatformViewWrapper.java
|
||||||
FILE: ../../../flutter/shell/platform/android/io/flutter/plugin/platform/PlatformViewsAccessibilityDelegate.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/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/SingleViewFakeWindowViewGroup.java
|
||||||
FILE: ../../../flutter/shell/platform/android/io/flutter/plugin/platform/SingleViewPresentation.java
|
FILE: ../../../flutter/shell/platform/android/io/flutter/plugin/platform/SingleViewPresentation.java
|
||||||
FILE: ../../../flutter/shell/platform/android/io/flutter/plugin/platform/SingleViewWindowManager.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/NavigationChannel.java",
|
||||||
"io/flutter/embedding/engine/systemchannels/PlatformChannel.java",
|
"io/flutter/embedding/engine/systemchannels/PlatformChannel.java",
|
||||||
"io/flutter/embedding/engine/systemchannels/PlatformViewsChannel.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/ProcessTextChannel.java",
|
||||||
"io/flutter/embedding/engine/systemchannels/RestorationChannel.java",
|
"io/flutter/embedding/engine/systemchannels/RestorationChannel.java",
|
||||||
"io/flutter/embedding/engine/systemchannels/ScribeChannel.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/PlatformViewWrapper.java",
|
||||||
"io/flutter/plugin/platform/PlatformViewsAccessibilityDelegate.java",
|
"io/flutter/plugin/platform/PlatformViewsAccessibilityDelegate.java",
|
||||||
"io/flutter/plugin/platform/PlatformViewsController.java",
|
"io/flutter/plugin/platform/PlatformViewsController.java",
|
||||||
|
"io/flutter/plugin/platform/PlatformViewsController2.java",
|
||||||
"io/flutter/plugin/platform/SingleViewFakeWindowViewGroup.java",
|
"io/flutter/plugin/platform/SingleViewFakeWindowViewGroup.java",
|
||||||
"io/flutter/plugin/platform/SingleViewPresentation.java",
|
"io/flutter/plugin/platform/SingleViewPresentation.java",
|
||||||
"io/flutter/plugin/platform/SingleViewWindowManager.java",
|
"io/flutter/plugin/platform/SingleViewWindowManager.java",
|
||||||
|
@ -1122,7 +1122,8 @@ public class FlutterView extends FrameLayout
|
|||||||
this,
|
this,
|
||||||
this.flutterEngine.getTextInputChannel(),
|
this.flutterEngine.getTextInputChannel(),
|
||||||
this.flutterEngine.getScribeChannel(),
|
this.flutterEngine.getScribeChannel(),
|
||||||
this.flutterEngine.getPlatformViewsController());
|
this.flutterEngine.getPlatformViewsController(),
|
||||||
|
this.flutterEngine.getPlatformViewsController2());
|
||||||
|
|
||||||
try {
|
try {
|
||||||
textServicesManager =
|
textServicesManager =
|
||||||
@ -1162,6 +1163,11 @@ public class FlutterView extends FrameLayout
|
|||||||
.getPlatformViewsController()
|
.getPlatformViewsController()
|
||||||
.attachToFlutterRenderer(this.flutterEngine.getRenderer());
|
.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
|
// Inform the Android framework that it should retrieve a new InputConnection
|
||||||
// now that an engine is attached.
|
// now that an engine is attached.
|
||||||
// TODO(mattcarroll): once this is proven to work, move this line ot TextInputPlugin
|
// TODO(mattcarroll): once this is proven to work, move this line ot TextInputPlugin
|
||||||
@ -1179,6 +1185,7 @@ public class FlutterView extends FrameLayout
|
|||||||
sendViewportMetricsToFlutter();
|
sendViewportMetricsToFlutter();
|
||||||
|
|
||||||
flutterEngine.getPlatformViewsController().attachToView(this);
|
flutterEngine.getPlatformViewsController().attachToView(this);
|
||||||
|
flutterEngine.getPlatformViewsController2().attachToView(this);
|
||||||
|
|
||||||
// Notify engine attachment listeners of the attachment.
|
// Notify engine attachment listeners of the attachment.
|
||||||
for (FlutterEngineAttachmentListener listener : flutterEngineAttachmentListeners) {
|
for (FlutterEngineAttachmentListener listener : flutterEngineAttachmentListeners) {
|
||||||
@ -1219,9 +1226,11 @@ public class FlutterView extends FrameLayout
|
|||||||
getContext().getContentResolver().unregisterContentObserver(systemSettingsObserver);
|
getContext().getContentResolver().unregisterContentObserver(systemSettingsObserver);
|
||||||
|
|
||||||
flutterEngine.getPlatformViewsController().detachFromView();
|
flutterEngine.getPlatformViewsController().detachFromView();
|
||||||
|
flutterEngine.getPlatformViewsController2().detachFromView();
|
||||||
|
|
||||||
// Disconnect the FlutterEngine's PlatformViewsController from the AccessibilityBridge.
|
// Disconnect the FlutterEngine's PlatformViewsController from the AccessibilityBridge.
|
||||||
flutterEngine.getPlatformViewsController().detachAccessibilityBridge();
|
flutterEngine.getPlatformViewsController().detachAccessibilityBridge();
|
||||||
|
flutterEngine.getPlatformViewsController2().detachAccessibilityBridge();
|
||||||
|
|
||||||
// Disconnect and clean up the AccessibilityBridge.
|
// Disconnect and clean up the AccessibilityBridge.
|
||||||
accessibilityBridge.release();
|
accessibilityBridge.release();
|
||||||
|
@ -41,6 +41,7 @@ import io.flutter.embedding.engine.systemchannels.SystemChannel;
|
|||||||
import io.flutter.embedding.engine.systemchannels.TextInputChannel;
|
import io.flutter.embedding.engine.systemchannels.TextInputChannel;
|
||||||
import io.flutter.plugin.localization.LocalizationPlugin;
|
import io.flutter.plugin.localization.LocalizationPlugin;
|
||||||
import io.flutter.plugin.platform.PlatformViewsController;
|
import io.flutter.plugin.platform.PlatformViewsController;
|
||||||
|
import io.flutter.plugin.platform.PlatformViewsController2;
|
||||||
import io.flutter.plugin.text.ProcessTextPlugin;
|
import io.flutter.plugin.text.ProcessTextPlugin;
|
||||||
import io.flutter.util.ViewUtils;
|
import io.flutter.util.ViewUtils;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
@ -109,6 +110,7 @@ public class FlutterEngine implements ViewUtils.DisplayUpdater {
|
|||||||
|
|
||||||
// Platform Views.
|
// Platform Views.
|
||||||
@NonNull private final PlatformViewsController platformViewsController;
|
@NonNull private final PlatformViewsController platformViewsController;
|
||||||
|
@NonNull private final PlatformViewsController2 platformViewsController2;
|
||||||
|
|
||||||
// Engine Lifecycle.
|
// Engine Lifecycle.
|
||||||
@NonNull private final Set<EngineLifecycleListener> engineLifecycleListeners = new HashSet<>();
|
@NonNull private final Set<EngineLifecycleListener> engineLifecycleListeners = new HashSet<>();
|
||||||
@ -124,6 +126,7 @@ public class FlutterEngine implements ViewUtils.DisplayUpdater {
|
|||||||
}
|
}
|
||||||
|
|
||||||
platformViewsController.onPreEngineRestart();
|
platformViewsController.onPreEngineRestart();
|
||||||
|
platformViewsController2.onPreEngineRestart();
|
||||||
restorationChannel.clearData();
|
restorationChannel.clearData();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -360,8 +363,11 @@ public class FlutterEngine implements ViewUtils.DisplayUpdater {
|
|||||||
flutterLoader.ensureInitializationComplete(context, dartVmArgs);
|
flutterLoader.ensureInitializationComplete(context, dartVmArgs);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
PlatformViewsController2 platformViewsController2 = new PlatformViewsController2();
|
||||||
|
|
||||||
flutterJNI.addEngineLifecycleListener(engineLifecycleListener);
|
flutterJNI.addEngineLifecycleListener(engineLifecycleListener);
|
||||||
flutterJNI.setPlatformViewsController(platformViewsController);
|
flutterJNI.setPlatformViewsController(platformViewsController);
|
||||||
|
flutterJNI.setPlatformViewsController2(platformViewsController2);
|
||||||
flutterJNI.setLocalizationPlugin(localizationPlugin);
|
flutterJNI.setLocalizationPlugin(localizationPlugin);
|
||||||
flutterJNI.setDeferredComponentManager(injector.deferredComponentManager());
|
flutterJNI.setDeferredComponentManager(injector.deferredComponentManager());
|
||||||
|
|
||||||
@ -377,7 +383,7 @@ public class FlutterEngine implements ViewUtils.DisplayUpdater {
|
|||||||
this.renderer = new FlutterRenderer(flutterJNI);
|
this.renderer = new FlutterRenderer(flutterJNI);
|
||||||
|
|
||||||
this.platformViewsController = platformViewsController;
|
this.platformViewsController = platformViewsController;
|
||||||
this.platformViewsController.onAttachedToJNI();
|
this.platformViewsController2 = platformViewsController2;
|
||||||
|
|
||||||
this.pluginRegistry =
|
this.pluginRegistry =
|
||||||
new FlutterEngineConnectionRegistry(
|
new FlutterEngineConnectionRegistry(
|
||||||
@ -472,6 +478,7 @@ public class FlutterEngine implements ViewUtils.DisplayUpdater {
|
|||||||
// The order that these things are destroyed is important.
|
// The order that these things are destroyed is important.
|
||||||
pluginRegistry.destroy();
|
pluginRegistry.destroy();
|
||||||
platformViewsController.onDetachedFromJNI();
|
platformViewsController.onDetachedFromJNI();
|
||||||
|
platformViewsController2.onDetachedFromJNI();
|
||||||
dartExecutor.onDetachedFromJNI();
|
dartExecutor.onDetachedFromJNI();
|
||||||
flutterJNI.removeEngineLifecycleListener(engineLifecycleListener);
|
flutterJNI.removeEngineLifecycleListener(engineLifecycleListener);
|
||||||
flutterJNI.setDeferredComponentManager(null);
|
flutterJNI.setDeferredComponentManager(null);
|
||||||
@ -648,6 +655,11 @@ public class FlutterEngine implements ViewUtils.DisplayUpdater {
|
|||||||
return platformViewsController;
|
return platformViewsController;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
public PlatformViewsController2 getPlatformViewsController2() {
|
||||||
|
return platformViewsController2;
|
||||||
|
}
|
||||||
|
|
||||||
@NonNull
|
@NonNull
|
||||||
public ActivityControlSurface getActivityControlSurface() {
|
public ActivityControlSurface getActivityControlSurface() {
|
||||||
return pluginRegistry;
|
return pluginRegistry;
|
||||||
|
@ -18,6 +18,7 @@ import android.util.DisplayMetrics;
|
|||||||
import android.util.Size;
|
import android.util.Size;
|
||||||
import android.util.TypedValue;
|
import android.util.TypedValue;
|
||||||
import android.view.Surface;
|
import android.view.Surface;
|
||||||
|
import android.view.SurfaceControl;
|
||||||
import android.view.SurfaceHolder;
|
import android.view.SurfaceHolder;
|
||||||
import androidx.annotation.Keep;
|
import androidx.annotation.Keep;
|
||||||
import androidx.annotation.NonNull;
|
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.common.StandardMessageCodec;
|
||||||
import io.flutter.plugin.localization.LocalizationPlugin;
|
import io.flutter.plugin.localization.LocalizationPlugin;
|
||||||
import io.flutter.plugin.platform.PlatformViewsController;
|
import io.flutter.plugin.platform.PlatformViewsController;
|
||||||
|
import io.flutter.plugin.platform.PlatformViewsController2;
|
||||||
import io.flutter.util.Preconditions;
|
import io.flutter.util.Preconditions;
|
||||||
import io.flutter.view.AccessibilityBridge;
|
import io.flutter.view.AccessibilityBridge;
|
||||||
import io.flutter.view.FlutterCallbackInformation;
|
import io.flutter.view.FlutterCallbackInformation;
|
||||||
@ -388,6 +390,7 @@ public class FlutterJNI {
|
|||||||
@Nullable private PlatformMessageHandler platformMessageHandler;
|
@Nullable private PlatformMessageHandler platformMessageHandler;
|
||||||
@Nullable private LocalizationPlugin localizationPlugin;
|
@Nullable private LocalizationPlugin localizationPlugin;
|
||||||
@Nullable private PlatformViewsController platformViewsController;
|
@Nullable private PlatformViewsController platformViewsController;
|
||||||
|
@Nullable private PlatformViewsController2 platformViewsController2;
|
||||||
|
|
||||||
@Nullable private DeferredComponentManager deferredComponentManager;
|
@Nullable private DeferredComponentManager deferredComponentManager;
|
||||||
|
|
||||||
@ -765,6 +768,13 @@ public class FlutterJNI {
|
|||||||
this.platformViewsController = platformViewsController;
|
this.platformViewsController = platformViewsController;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@UiThread
|
||||||
|
public void setPlatformViewsController2(
|
||||||
|
@NonNull PlatformViewsController2 platformViewsController2) {
|
||||||
|
ensureRunningOnMainThread();
|
||||||
|
this.platformViewsController2 = platformViewsController2;
|
||||||
|
}
|
||||||
|
|
||||||
// ------ Start Accessibility Support -----
|
// ------ Start Accessibility Support -----
|
||||||
/**
|
/**
|
||||||
* Sets the {@link AccessibilityDelegate} for the attached Flutter context.
|
* Sets the {@link AccessibilityDelegate} for the attached Flutter context.
|
||||||
@ -1274,6 +1284,75 @@ public class FlutterJNI {
|
|||||||
}
|
}
|
||||||
// ----- End Engine Lifecycle Support ----
|
// ----- 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 ----
|
// ----- Start Localization Support ----
|
||||||
|
|
||||||
/** Sets the localization plugin that is used in various localization methods. */
|
/** 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;
|
||||||
import io.flutter.embedding.engine.systemchannels.TextInputChannel.TextEditState;
|
import io.flutter.embedding.engine.systemchannels.TextInputChannel.TextEditState;
|
||||||
import io.flutter.plugin.platform.PlatformViewsController;
|
import io.flutter.plugin.platform.PlatformViewsController;
|
||||||
|
import io.flutter.plugin.platform.PlatformViewsController2;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
|
|
||||||
@ -52,6 +53,7 @@ public class TextInputPlugin implements ListenableEditingState.EditingStateWatch
|
|||||||
private boolean mRestartInputPending;
|
private boolean mRestartInputPending;
|
||||||
@Nullable private InputConnection lastInputConnection;
|
@Nullable private InputConnection lastInputConnection;
|
||||||
@NonNull private PlatformViewsController platformViewsController;
|
@NonNull private PlatformViewsController platformViewsController;
|
||||||
|
@NonNull private PlatformViewsController2 platformViewsController2;
|
||||||
@Nullable private Rect lastClientRect;
|
@Nullable private Rect lastClientRect;
|
||||||
private ImeSyncDeferringInsetsCallback imeSyncCallback;
|
private ImeSyncDeferringInsetsCallback imeSyncCallback;
|
||||||
|
|
||||||
@ -69,7 +71,8 @@ public class TextInputPlugin implements ListenableEditingState.EditingStateWatch
|
|||||||
@NonNull View view,
|
@NonNull View view,
|
||||||
@NonNull TextInputChannel textInputChannel,
|
@NonNull TextInputChannel textInputChannel,
|
||||||
@NonNull ScribeChannel scribeChannel,
|
@NonNull ScribeChannel scribeChannel,
|
||||||
@NonNull PlatformViewsController platformViewsController) {
|
@NonNull PlatformViewsController platformViewsController,
|
||||||
|
@NonNull PlatformViewsController2 platformViewsController2) {
|
||||||
mView = view;
|
mView = view;
|
||||||
// Create a default object.
|
// Create a default object.
|
||||||
mEditable = new ListenableEditingState(null, mView);
|
mEditable = new ListenableEditingState(null, mView);
|
||||||
@ -160,6 +163,8 @@ public class TextInputPlugin implements ListenableEditingState.EditingStateWatch
|
|||||||
|
|
||||||
this.platformViewsController = platformViewsController;
|
this.platformViewsController = platformViewsController;
|
||||||
this.platformViewsController.attachTextInputPlugin(this);
|
this.platformViewsController.attachTextInputPlugin(this);
|
||||||
|
this.platformViewsController2 = platformViewsController2;
|
||||||
|
this.platformViewsController2.attachTextInputPlugin(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
@NonNull
|
@NonNull
|
||||||
@ -215,6 +220,7 @@ public class TextInputPlugin implements ListenableEditingState.EditingStateWatch
|
|||||||
@SuppressLint("NewApi")
|
@SuppressLint("NewApi")
|
||||||
public void destroy() {
|
public void destroy() {
|
||||||
platformViewsController.detachTextInputPlugin();
|
platformViewsController.detachTextInputPlugin();
|
||||||
|
platformViewsController2.detachTextInputPlugin();
|
||||||
textInputChannel.setTextInputMethodHandler(null);
|
textInputChannel.setTextInputMethodHandler(null);
|
||||||
notifyViewExited();
|
notifyViewExited();
|
||||||
mEditable.removeEditingStateListener(this);
|
mEditable.removeEditingStateListener(this);
|
||||||
|
@ -926,14 +926,6 @@ public class PlatformViewsController implements PlatformViewsAccessibilityDelega
|
|||||||
return registry;
|
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
|
* Invoked when the {@link io.flutter.embedding.engine.FlutterEngine} that owns this {@link
|
||||||
* PlatformViewsController} detaches from JNI.
|
* 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.embedding.engine.systemchannels.TextInputChannel;
|
||||||
import io.flutter.plugin.localization.LocalizationPlugin;
|
import io.flutter.plugin.localization.LocalizationPlugin;
|
||||||
import io.flutter.plugin.platform.PlatformViewsController;
|
import io.flutter.plugin.platform.PlatformViewsController;
|
||||||
|
import io.flutter.plugin.platform.PlatformViewsController2;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
@ -1475,6 +1476,7 @@ public class FlutterActivityAndFragmentDelegateTest {
|
|||||||
when(engine.getNavigationChannel()).thenReturn(mock(NavigationChannel.class));
|
when(engine.getNavigationChannel()).thenReturn(mock(NavigationChannel.class));
|
||||||
when(engine.getBackGestureChannel()).thenReturn(mock(BackGestureChannel.class));
|
when(engine.getBackGestureChannel()).thenReturn(mock(BackGestureChannel.class));
|
||||||
when(engine.getPlatformViewsController()).thenReturn(mock(PlatformViewsController.class));
|
when(engine.getPlatformViewsController()).thenReturn(mock(PlatformViewsController.class));
|
||||||
|
when(engine.getPlatformViewsController2()).thenReturn(mock(PlatformViewsController2.class));
|
||||||
|
|
||||||
FlutterRenderer renderer = mock(FlutterRenderer.class);
|
FlutterRenderer renderer = mock(FlutterRenderer.class);
|
||||||
when(engine.getRenderer()).thenReturn(renderer);
|
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.renderer.FlutterRenderer;
|
||||||
import io.flutter.embedding.engine.systemchannels.SettingsChannel;
|
import io.flutter.embedding.engine.systemchannels.SettingsChannel;
|
||||||
import io.flutter.plugin.platform.PlatformViewsController;
|
import io.flutter.plugin.platform.PlatformViewsController;
|
||||||
|
import io.flutter.plugin.platform.PlatformViewsController2;
|
||||||
import java.lang.reflect.Method;
|
import java.lang.reflect.Method;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
@ -83,6 +84,7 @@ public class FlutterViewTest {
|
|||||||
@Mock FlutterJNI mockFlutterJni;
|
@Mock FlutterJNI mockFlutterJni;
|
||||||
@Mock FlutterLoader mockFlutterLoader;
|
@Mock FlutterLoader mockFlutterLoader;
|
||||||
@Spy PlatformViewsController platformViewsController;
|
@Spy PlatformViewsController platformViewsController;
|
||||||
|
@Spy PlatformViewsController2 platformViewsController2;
|
||||||
|
|
||||||
@Before
|
@Before
|
||||||
public void setUp() {
|
public void setUp() {
|
||||||
@ -97,10 +99,12 @@ public class FlutterViewTest {
|
|||||||
FlutterView flutterView = new FlutterView(ctx);
|
FlutterView flutterView = new FlutterView(ctx);
|
||||||
FlutterEngine flutterEngine = spy(new FlutterEngine(ctx, mockFlutterLoader, mockFlutterJni));
|
FlutterEngine flutterEngine = spy(new FlutterEngine(ctx, mockFlutterLoader, mockFlutterJni));
|
||||||
when(flutterEngine.getPlatformViewsController()).thenReturn(platformViewsController);
|
when(flutterEngine.getPlatformViewsController()).thenReturn(platformViewsController);
|
||||||
|
when(flutterEngine.getPlatformViewsController2()).thenReturn(platformViewsController2);
|
||||||
|
|
||||||
flutterView.attachToFlutterEngine(flutterEngine);
|
flutterView.attachToFlutterEngine(flutterEngine);
|
||||||
|
|
||||||
verify(platformViewsController, times(1)).attachToView(flutterView);
|
verify(platformViewsController, times(1)).attachToView(flutterView);
|
||||||
|
verify(platformViewsController2, times(1)).attachToView(flutterView);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@ -117,11 +121,13 @@ public class FlutterViewTest {
|
|||||||
FlutterView flutterView = new FlutterView(ctx);
|
FlutterView flutterView = new FlutterView(ctx);
|
||||||
FlutterEngine flutterEngine = spy(new FlutterEngine(ctx, mockFlutterLoader, mockFlutterJni));
|
FlutterEngine flutterEngine = spy(new FlutterEngine(ctx, mockFlutterLoader, mockFlutterJni));
|
||||||
when(flutterEngine.getPlatformViewsController()).thenReturn(platformViewsController);
|
when(flutterEngine.getPlatformViewsController()).thenReturn(platformViewsController);
|
||||||
|
when(flutterEngine.getPlatformViewsController2()).thenReturn(platformViewsController2);
|
||||||
|
|
||||||
flutterView.attachToFlutterEngine(flutterEngine);
|
flutterView.attachToFlutterEngine(flutterEngine);
|
||||||
flutterView.detachFromFlutterEngine();
|
flutterView.detachFromFlutterEngine();
|
||||||
|
|
||||||
verify(platformViewsController, times(1)).detachFromView();
|
verify(platformViewsController, times(1)).detachFromView();
|
||||||
|
verify(platformViewsController2, times(1)).detachFromView();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@ -234,7 +234,6 @@ public class FlutterEngineTest {
|
|||||||
platformViewsController,
|
platformViewsController,
|
||||||
/*dartVmArgs=*/ new String[] {},
|
/*dartVmArgs=*/ new String[] {},
|
||||||
/*automaticallyRegisterPlugins=*/ false);
|
/*automaticallyRegisterPlugins=*/ false);
|
||||||
verify(platformViewsController, times(1)).onAttachedToJNI();
|
|
||||||
|
|
||||||
engine.destroy();
|
engine.destroy();
|
||||||
verify(platformViewsController, times(1)).onDetachedFromJNI();
|
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.JSONMethodCodec;
|
||||||
import io.flutter.plugin.common.MethodCall;
|
import io.flutter.plugin.common.MethodCall;
|
||||||
import io.flutter.plugin.platform.PlatformViewsController;
|
import io.flutter.plugin.platform.PlatformViewsController;
|
||||||
|
import io.flutter.plugin.platform.PlatformViewsController2;
|
||||||
import java.nio.ByteBuffer;
|
import java.nio.ByteBuffer;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
@ -139,7 +140,11 @@ public class TextInputPluginTest {
|
|||||||
ScribeChannel scribeChannel = new ScribeChannel(mock(DartExecutor.class));
|
ScribeChannel scribeChannel = new ScribeChannel(mock(DartExecutor.class));
|
||||||
TextInputPlugin textInputPlugin =
|
TextInputPlugin textInputPlugin =
|
||||||
new 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<String> channelCaptor = ArgumentCaptor.forClass(String.class);
|
||||||
ArgumentCaptor<ByteBuffer> bufferCaptor = ArgumentCaptor.forClass(ByteBuffer.class);
|
ArgumentCaptor<ByteBuffer> bufferCaptor = ArgumentCaptor.forClass(ByteBuffer.class);
|
||||||
@ -160,7 +165,11 @@ public class TextInputPluginTest {
|
|||||||
ScribeChannel scribeChannel = new ScribeChannel(mock(DartExecutor.class));
|
ScribeChannel scribeChannel = new ScribeChannel(mock(DartExecutor.class));
|
||||||
TextInputPlugin textInputPlugin =
|
TextInputPlugin textInputPlugin =
|
||||||
new TextInputPlugin(
|
new TextInputPlugin(
|
||||||
testView, textInputChannel, scribeChannel, mock(PlatformViewsController.class));
|
testView,
|
||||||
|
textInputChannel,
|
||||||
|
scribeChannel,
|
||||||
|
mock(PlatformViewsController.class),
|
||||||
|
mock(PlatformViewsController2.class));
|
||||||
textInputPlugin.setTextInputClient(
|
textInputPlugin.setTextInputClient(
|
||||||
0,
|
0,
|
||||||
new TextInputChannel.Configuration(
|
new TextInputChannel.Configuration(
|
||||||
@ -204,7 +213,11 @@ public class TextInputPluginTest {
|
|||||||
ScribeChannel scribeChannel = new ScribeChannel(mock(DartExecutor.class));
|
ScribeChannel scribeChannel = new ScribeChannel(mock(DartExecutor.class));
|
||||||
TextInputPlugin textInputPlugin =
|
TextInputPlugin textInputPlugin =
|
||||||
new TextInputPlugin(
|
new TextInputPlugin(
|
||||||
testView, textInputChannel, scribeChannel, mock(PlatformViewsController.class));
|
testView,
|
||||||
|
textInputChannel,
|
||||||
|
scribeChannel,
|
||||||
|
mock(PlatformViewsController.class),
|
||||||
|
mock(PlatformViewsController2.class));
|
||||||
|
|
||||||
// Here's no textInputPlugin.setTextInputClient()
|
// Here's no textInputPlugin.setTextInputClient()
|
||||||
textInputPlugin.setTextInputEditingState(
|
textInputPlugin.setTextInputEditingState(
|
||||||
@ -223,7 +236,11 @@ public class TextInputPluginTest {
|
|||||||
ScribeChannel scribeChannel = new ScribeChannel(mock(DartExecutor.class));
|
ScribeChannel scribeChannel = new ScribeChannel(mock(DartExecutor.class));
|
||||||
TextInputPlugin textInputPlugin =
|
TextInputPlugin textInputPlugin =
|
||||||
new TextInputPlugin(
|
new TextInputPlugin(
|
||||||
testView, textInputChannel, scribeChannel, mock(PlatformViewsController.class));
|
testView,
|
||||||
|
textInputChannel,
|
||||||
|
scribeChannel,
|
||||||
|
mock(PlatformViewsController.class),
|
||||||
|
mock(PlatformViewsController2.class));
|
||||||
textInputPlugin.setTextInputClient(
|
textInputPlugin.setTextInputClient(
|
||||||
0,
|
0,
|
||||||
new TextInputChannel.Configuration(
|
new TextInputChannel.Configuration(
|
||||||
@ -275,7 +292,11 @@ public class TextInputPluginTest {
|
|||||||
ScribeChannel scribeChannel = new ScribeChannel(mock(DartExecutor.class));
|
ScribeChannel scribeChannel = new ScribeChannel(mock(DartExecutor.class));
|
||||||
TextInputPlugin textInputPlugin =
|
TextInputPlugin textInputPlugin =
|
||||||
new 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.";
|
CharSequence newText = "I do not fear computers. I fear the lack of them.";
|
||||||
|
|
||||||
// Change InputTarget to FRAMEWORK_CLIENT.
|
// Change InputTarget to FRAMEWORK_CLIENT.
|
||||||
@ -390,7 +411,11 @@ public class TextInputPluginTest {
|
|||||||
ScribeChannel scribeChannel = new ScribeChannel(mock(DartExecutor.class));
|
ScribeChannel scribeChannel = new ScribeChannel(mock(DartExecutor.class));
|
||||||
TextInputPlugin textInputPlugin =
|
TextInputPlugin textInputPlugin =
|
||||||
new 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.";
|
CharSequence newText = "I do not fear computers. I fear the lack of them.";
|
||||||
final TextEditingDelta expectedDelta =
|
final TextEditingDelta expectedDelta =
|
||||||
new TextEditingDelta("", 0, 0, newText, newText.length(), newText.length(), 0, 49);
|
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));
|
ScribeChannel scribeChannel = new ScribeChannel(mock(DartExecutor.class));
|
||||||
TextInputPlugin textInputPlugin =
|
TextInputPlugin textInputPlugin =
|
||||||
new 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.";
|
CharSequence newText = "I do not fear computers. I fear the lack of them.";
|
||||||
final TextEditingDelta expectedDelta =
|
final TextEditingDelta expectedDelta =
|
||||||
new TextEditingDelta("", 0, 0, newText, newText.length(), newText.length(), 0, 49);
|
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));
|
ScribeChannel scribeChannel = new ScribeChannel(mock(DartExecutor.class));
|
||||||
TextInputPlugin textInputPlugin =
|
TextInputPlugin textInputPlugin =
|
||||||
new 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.";
|
CharSequence newText = "I do not fear computers. I fear the lack of them.";
|
||||||
final TextEditingDelta expectedDelta =
|
final TextEditingDelta expectedDelta =
|
||||||
new TextEditingDelta(
|
new TextEditingDelta(
|
||||||
@ -744,7 +777,11 @@ public class TextInputPluginTest {
|
|||||||
ScribeChannel scribeChannel = new ScribeChannel(mock(DartExecutor.class));
|
ScribeChannel scribeChannel = new ScribeChannel(mock(DartExecutor.class));
|
||||||
TextInputPlugin textInputPlugin =
|
TextInputPlugin textInputPlugin =
|
||||||
new TextInputPlugin(
|
new TextInputPlugin(
|
||||||
testView, textInputChannel, scribeChannel, mock(PlatformViewsController.class));
|
testView,
|
||||||
|
textInputChannel,
|
||||||
|
scribeChannel,
|
||||||
|
mock(PlatformViewsController.class),
|
||||||
|
mock(PlatformViewsController2.class));
|
||||||
CharSequence newText = "helfo";
|
CharSequence newText = "helfo";
|
||||||
final TextEditingDelta expectedDelta = new TextEditingDelta(newText, 0, 5, "hello", 5, 5, 0, 5);
|
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));
|
ScribeChannel scribeChannel = new ScribeChannel(mock(DartExecutor.class));
|
||||||
TextInputPlugin textInputPlugin =
|
TextInputPlugin textInputPlugin =
|
||||||
new TextInputPlugin(
|
new TextInputPlugin(
|
||||||
testView, textInputChannel, scribeChannel, mock(PlatformViewsController.class));
|
testView,
|
||||||
|
textInputChannel,
|
||||||
|
scribeChannel,
|
||||||
|
mock(PlatformViewsController.class),
|
||||||
|
mock(PlatformViewsController2.class));
|
||||||
|
|
||||||
// Change InputTarget to FRAMEWORK_CLIENT.
|
// Change InputTarget to FRAMEWORK_CLIENT.
|
||||||
textInputPlugin.setTextInputClient(
|
textInputPlugin.setTextInputClient(
|
||||||
@ -946,7 +987,11 @@ public class TextInputPluginTest {
|
|||||||
ScribeChannel scribeChannel = new ScribeChannel(mock(DartExecutor.class));
|
ScribeChannel scribeChannel = new ScribeChannel(mock(DartExecutor.class));
|
||||||
TextInputPlugin textInputPlugin =
|
TextInputPlugin textInputPlugin =
|
||||||
new TextInputPlugin(
|
new TextInputPlugin(
|
||||||
testView, textInputChannel, scribeChannel, mock(PlatformViewsController.class));
|
testView,
|
||||||
|
textInputChannel,
|
||||||
|
scribeChannel,
|
||||||
|
mock(PlatformViewsController.class),
|
||||||
|
mock(PlatformViewsController2.class));
|
||||||
textInputPlugin.setTextInputClient(
|
textInputPlugin.setTextInputClient(
|
||||||
0,
|
0,
|
||||||
new TextInputChannel.Configuration(
|
new TextInputChannel.Configuration(
|
||||||
@ -986,7 +1031,11 @@ public class TextInputPluginTest {
|
|||||||
ScribeChannel scribeChannel = new ScribeChannel(mock(DartExecutor.class));
|
ScribeChannel scribeChannel = new ScribeChannel(mock(DartExecutor.class));
|
||||||
TextInputPlugin textInputPlugin =
|
TextInputPlugin textInputPlugin =
|
||||||
new TextInputPlugin(
|
new TextInputPlugin(
|
||||||
testView, textInputChannel, scribeChannel, mock(PlatformViewsController.class));
|
testView,
|
||||||
|
textInputChannel,
|
||||||
|
scribeChannel,
|
||||||
|
mock(PlatformViewsController.class),
|
||||||
|
mock(PlatformViewsController2.class));
|
||||||
textInputPlugin.setTextInputClient(
|
textInputPlugin.setTextInputClient(
|
||||||
0,
|
0,
|
||||||
new TextInputChannel.Configuration(
|
new TextInputChannel.Configuration(
|
||||||
@ -1036,7 +1085,11 @@ public class TextInputPluginTest {
|
|||||||
ScribeChannel scribeChannel = new ScribeChannel(mock(DartExecutor.class));
|
ScribeChannel scribeChannel = new ScribeChannel(mock(DartExecutor.class));
|
||||||
TextInputPlugin textInputPlugin =
|
TextInputPlugin textInputPlugin =
|
||||||
new TextInputPlugin(
|
new TextInputPlugin(
|
||||||
testView, textInputChannel, scribeChannel, mock(PlatformViewsController.class));
|
testView,
|
||||||
|
textInputChannel,
|
||||||
|
scribeChannel,
|
||||||
|
mock(PlatformViewsController.class),
|
||||||
|
mock(PlatformViewsController2.class));
|
||||||
textInputPlugin.setTextInputClient(
|
textInputPlugin.setTextInputClient(
|
||||||
0,
|
0,
|
||||||
new TextInputChannel.Configuration(
|
new TextInputChannel.Configuration(
|
||||||
@ -1139,7 +1192,11 @@ public class TextInputPluginTest {
|
|||||||
ScribeChannel scribeChannel = new ScribeChannel(mock(DartExecutor.class));
|
ScribeChannel scribeChannel = new ScribeChannel(mock(DartExecutor.class));
|
||||||
TextInputPlugin textInputPlugin =
|
TextInputPlugin textInputPlugin =
|
||||||
new TextInputPlugin(
|
new TextInputPlugin(
|
||||||
testView, textInputChannel, scribeChannel, mock(PlatformViewsController.class));
|
testView,
|
||||||
|
textInputChannel,
|
||||||
|
scribeChannel,
|
||||||
|
mock(PlatformViewsController.class),
|
||||||
|
mock(PlatformViewsController2.class));
|
||||||
textInputPlugin.setTextInputClient(
|
textInputPlugin.setTextInputClient(
|
||||||
0,
|
0,
|
||||||
new TextInputChannel.Configuration(
|
new TextInputChannel.Configuration(
|
||||||
@ -1168,7 +1225,11 @@ public class TextInputPluginTest {
|
|||||||
ScribeChannel scribeChannel = new ScribeChannel(mock(DartExecutor.class));
|
ScribeChannel scribeChannel = new ScribeChannel(mock(DartExecutor.class));
|
||||||
TextInputPlugin textInputPlugin =
|
TextInputPlugin textInputPlugin =
|
||||||
new TextInputPlugin(
|
new TextInputPlugin(
|
||||||
testView, textInputChannel, scribeChannel, mock(PlatformViewsController.class));
|
testView,
|
||||||
|
textInputChannel,
|
||||||
|
scribeChannel,
|
||||||
|
mock(PlatformViewsController.class),
|
||||||
|
mock(PlatformViewsController2.class));
|
||||||
verify(textInputChannel, times(1)).setTextInputMethodHandler(isNotNull());
|
verify(textInputChannel, times(1)).setTextInputMethodHandler(isNotNull());
|
||||||
textInputPlugin.destroy();
|
textInputPlugin.destroy();
|
||||||
verify(textInputChannel, times(1)).setTextInputMethodHandler(isNull());
|
verify(textInputChannel, times(1)).setTextInputMethodHandler(isNull());
|
||||||
@ -1186,7 +1247,11 @@ public class TextInputPluginTest {
|
|||||||
ScribeChannel scribeChannel = new ScribeChannel(mock(DartExecutor.class));
|
ScribeChannel scribeChannel = new ScribeChannel(mock(DartExecutor.class));
|
||||||
TextInputPlugin textInputPlugin =
|
TextInputPlugin textInputPlugin =
|
||||||
new TextInputPlugin(
|
new TextInputPlugin(
|
||||||
testView, textInputChannel, scribeChannel, mock(PlatformViewsController.class));
|
testView,
|
||||||
|
textInputChannel,
|
||||||
|
scribeChannel,
|
||||||
|
mock(PlatformViewsController.class),
|
||||||
|
mock(PlatformViewsController2.class));
|
||||||
textInputPlugin.setTextInputClient(
|
textInputPlugin.setTextInputClient(
|
||||||
0,
|
0,
|
||||||
new TextInputChannel.Configuration(
|
new TextInputChannel.Configuration(
|
||||||
@ -1263,7 +1328,11 @@ public class TextInputPluginTest {
|
|||||||
ScribeChannel scribeChannel = new ScribeChannel(mock(DartExecutor.class));
|
ScribeChannel scribeChannel = new ScribeChannel(mock(DartExecutor.class));
|
||||||
TextInputPlugin textInputPlugin =
|
TextInputPlugin textInputPlugin =
|
||||||
new TextInputPlugin(
|
new TextInputPlugin(
|
||||||
testView, textInputChannel, scribeChannel, mock(PlatformViewsController.class));
|
testView,
|
||||||
|
textInputChannel,
|
||||||
|
scribeChannel,
|
||||||
|
mock(PlatformViewsController.class),
|
||||||
|
mock(PlatformViewsController2.class));
|
||||||
textInputPlugin.setTextInputClient(
|
textInputPlugin.setTextInputClient(
|
||||||
0,
|
0,
|
||||||
new TextInputChannel.Configuration(
|
new TextInputChannel.Configuration(
|
||||||
@ -1303,7 +1372,11 @@ public class TextInputPluginTest {
|
|||||||
ScribeChannel scribeChannel = new ScribeChannel(mock(DartExecutor.class));
|
ScribeChannel scribeChannel = new ScribeChannel(mock(DartExecutor.class));
|
||||||
TextInputPlugin textInputPlugin =
|
TextInputPlugin textInputPlugin =
|
||||||
new TextInputPlugin(
|
new TextInputPlugin(
|
||||||
testView, textInputChannel, scribeChannel, mock(PlatformViewsController.class));
|
testView,
|
||||||
|
textInputChannel,
|
||||||
|
scribeChannel,
|
||||||
|
mock(PlatformViewsController.class),
|
||||||
|
mock(PlatformViewsController2.class));
|
||||||
textInputPlugin.setTextInputClient(
|
textInputPlugin.setTextInputClient(
|
||||||
0,
|
0,
|
||||||
new TextInputChannel.Configuration(
|
new TextInputChannel.Configuration(
|
||||||
@ -1335,7 +1408,11 @@ public class TextInputPluginTest {
|
|||||||
ScribeChannel scribeChannel = new ScribeChannel(mock(DartExecutor.class));
|
ScribeChannel scribeChannel = new ScribeChannel(mock(DartExecutor.class));
|
||||||
TextInputPlugin textInputPlugin =
|
TextInputPlugin textInputPlugin =
|
||||||
new TextInputPlugin(
|
new TextInputPlugin(
|
||||||
testView, textInputChannel, scribeChannel, mock(PlatformViewsController.class));
|
testView,
|
||||||
|
textInputChannel,
|
||||||
|
scribeChannel,
|
||||||
|
mock(PlatformViewsController.class),
|
||||||
|
mock(PlatformViewsController2.class));
|
||||||
textInputPlugin.setTextInputClient(
|
textInputPlugin.setTextInputClient(
|
||||||
0,
|
0,
|
||||||
new TextInputChannel.Configuration(
|
new TextInputChannel.Configuration(
|
||||||
@ -1365,7 +1442,11 @@ public class TextInputPluginTest {
|
|||||||
ScribeChannel scribeChannel = new ScribeChannel(mock(DartExecutor.class));
|
ScribeChannel scribeChannel = new ScribeChannel(mock(DartExecutor.class));
|
||||||
TextInputPlugin textInputPlugin =
|
TextInputPlugin textInputPlugin =
|
||||||
new TextInputPlugin(
|
new TextInputPlugin(
|
||||||
testView, textInputChannel, scribeChannel, mock(PlatformViewsController.class));
|
testView,
|
||||||
|
textInputChannel,
|
||||||
|
scribeChannel,
|
||||||
|
mock(PlatformViewsController.class),
|
||||||
|
mock(PlatformViewsController2.class));
|
||||||
textInputPlugin.setTextInputClient(
|
textInputPlugin.setTextInputClient(
|
||||||
0,
|
0,
|
||||||
new TextInputChannel.Configuration(
|
new TextInputChannel.Configuration(
|
||||||
@ -1399,7 +1480,11 @@ public class TextInputPluginTest {
|
|||||||
ScribeChannel scribeChannel = new ScribeChannel(mock(DartExecutor.class));
|
ScribeChannel scribeChannel = new ScribeChannel(mock(DartExecutor.class));
|
||||||
TextInputPlugin textInputPlugin =
|
TextInputPlugin textInputPlugin =
|
||||||
new TextInputPlugin(
|
new TextInputPlugin(
|
||||||
testView, textInputChannel, scribeChannel, mock(PlatformViewsController.class));
|
testView,
|
||||||
|
textInputChannel,
|
||||||
|
scribeChannel,
|
||||||
|
mock(PlatformViewsController.class),
|
||||||
|
mock(PlatformViewsController2.class));
|
||||||
textInputPlugin.setTextInputClient(
|
textInputPlugin.setTextInputClient(
|
||||||
0,
|
0,
|
||||||
new TextInputChannel.Configuration(
|
new TextInputChannel.Configuration(
|
||||||
@ -1434,7 +1519,11 @@ public class TextInputPluginTest {
|
|||||||
ScribeChannel scribeChannel = new ScribeChannel(mock(DartExecutor.class));
|
ScribeChannel scribeChannel = new ScribeChannel(mock(DartExecutor.class));
|
||||||
TextInputPlugin textInputPlugin =
|
TextInputPlugin textInputPlugin =
|
||||||
new TextInputPlugin(
|
new TextInputPlugin(
|
||||||
testView, textInputChannel, scribeChannel, mock(PlatformViewsController.class));
|
testView,
|
||||||
|
textInputChannel,
|
||||||
|
scribeChannel,
|
||||||
|
mock(PlatformViewsController.class),
|
||||||
|
mock(PlatformViewsController2.class));
|
||||||
textInputPlugin.setTextInputClient(
|
textInputPlugin.setTextInputClient(
|
||||||
0,
|
0,
|
||||||
new TextInputChannel.Configuration(
|
new TextInputChannel.Configuration(
|
||||||
@ -1473,7 +1562,11 @@ public class TextInputPluginTest {
|
|||||||
ScribeChannel scribeChannel = new ScribeChannel(mock(DartExecutor.class));
|
ScribeChannel scribeChannel = new ScribeChannel(mock(DartExecutor.class));
|
||||||
TextInputPlugin textInputPlugin =
|
TextInputPlugin textInputPlugin =
|
||||||
new TextInputPlugin(
|
new TextInputPlugin(
|
||||||
testView, textInputChannel, scribeChannel, mock(PlatformViewsController.class));
|
testView,
|
||||||
|
textInputChannel,
|
||||||
|
scribeChannel,
|
||||||
|
mock(PlatformViewsController.class),
|
||||||
|
mock(PlatformViewsController2.class));
|
||||||
textInputPlugin.setTextInputClient(
|
textInputPlugin.setTextInputClient(
|
||||||
0,
|
0,
|
||||||
new TextInputChannel.Configuration(
|
new TextInputChannel.Configuration(
|
||||||
@ -1507,7 +1600,11 @@ public class TextInputPluginTest {
|
|||||||
ScribeChannel scribeChannel = new ScribeChannel(mock(DartExecutor.class));
|
ScribeChannel scribeChannel = new ScribeChannel(mock(DartExecutor.class));
|
||||||
TextInputPlugin textInputPlugin =
|
TextInputPlugin textInputPlugin =
|
||||||
new TextInputPlugin(
|
new TextInputPlugin(
|
||||||
testView, textInputChannel, scribeChannel, mock(PlatformViewsController.class));
|
testView,
|
||||||
|
textInputChannel,
|
||||||
|
scribeChannel,
|
||||||
|
mock(PlatformViewsController.class),
|
||||||
|
mock(PlatformViewsController2.class));
|
||||||
textInputPlugin.setTextInputClient(
|
textInputPlugin.setTextInputClient(
|
||||||
0,
|
0,
|
||||||
new TextInputChannel.Configuration(
|
new TextInputChannel.Configuration(
|
||||||
@ -1542,7 +1639,11 @@ public class TextInputPluginTest {
|
|||||||
ScribeChannel scribeChannel = new ScribeChannel(mock(DartExecutor.class));
|
ScribeChannel scribeChannel = new ScribeChannel(mock(DartExecutor.class));
|
||||||
TextInputPlugin textInputPlugin =
|
TextInputPlugin textInputPlugin =
|
||||||
new TextInputPlugin(
|
new TextInputPlugin(
|
||||||
testView, textInputChannel, scribeChannel, mock(PlatformViewsController.class));
|
testView,
|
||||||
|
textInputChannel,
|
||||||
|
scribeChannel,
|
||||||
|
mock(PlatformViewsController.class),
|
||||||
|
mock(PlatformViewsController2.class));
|
||||||
final TextInputChannel.Configuration.Autofill autofill =
|
final TextInputChannel.Configuration.Autofill autofill =
|
||||||
new TextInputChannel.Configuration.Autofill(
|
new TextInputChannel.Configuration.Autofill(
|
||||||
"1", new String[] {}, null, new TextInputChannel.TextEditState("", 0, 0, -1, -1));
|
"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));
|
ScribeChannel scribeChannel = new ScribeChannel(mock(DartExecutor.class));
|
||||||
TextInputPlugin textInputPlugin =
|
TextInputPlugin textInputPlugin =
|
||||||
new TextInputPlugin(
|
new TextInputPlugin(
|
||||||
testView, textInputChannel, scribeChannel, mock(PlatformViewsController.class));
|
testView,
|
||||||
|
textInputChannel,
|
||||||
|
scribeChannel,
|
||||||
|
mock(PlatformViewsController.class),
|
||||||
|
mock(PlatformViewsController2.class));
|
||||||
final TextInputChannel.Configuration.Autofill autofill =
|
final TextInputChannel.Configuration.Autofill autofill =
|
||||||
new TextInputChannel.Configuration.Autofill(
|
new TextInputChannel.Configuration.Autofill(
|
||||||
"1", new String[] {}, null, new TextInputChannel.TextEditState("", 0, 0, -1, -1));
|
"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));
|
ScribeChannel scribeChannel = new ScribeChannel(mock(DartExecutor.class));
|
||||||
TextInputPlugin textInputPlugin =
|
TextInputPlugin textInputPlugin =
|
||||||
new TextInputPlugin(
|
new TextInputPlugin(
|
||||||
testView, textInputChannel, scribeChannel, mock(PlatformViewsController.class));
|
testView,
|
||||||
|
textInputChannel,
|
||||||
|
scribeChannel,
|
||||||
|
mock(PlatformViewsController.class),
|
||||||
|
mock(PlatformViewsController2.class));
|
||||||
final TextInputChannel.Configuration.Autofill autofill =
|
final TextInputChannel.Configuration.Autofill autofill =
|
||||||
new TextInputChannel.Configuration.Autofill(
|
new TextInputChannel.Configuration.Autofill(
|
||||||
"1",
|
"1",
|
||||||
@ -1690,7 +1799,11 @@ public class TextInputPluginTest {
|
|||||||
ScribeChannel scribeChannel = new ScribeChannel(mock(DartExecutor.class));
|
ScribeChannel scribeChannel = new ScribeChannel(mock(DartExecutor.class));
|
||||||
TextInputPlugin textInputPlugin =
|
TextInputPlugin textInputPlugin =
|
||||||
new TextInputPlugin(
|
new TextInputPlugin(
|
||||||
testView, textInputChannel, scribeChannel, mock(PlatformViewsController.class));
|
testView,
|
||||||
|
textInputChannel,
|
||||||
|
scribeChannel,
|
||||||
|
mock(PlatformViewsController.class),
|
||||||
|
mock(PlatformViewsController2.class));
|
||||||
final TextInputChannel.Configuration.Autofill autofill1 =
|
final TextInputChannel.Configuration.Autofill autofill1 =
|
||||||
new TextInputChannel.Configuration.Autofill(
|
new TextInputChannel.Configuration.Autofill(
|
||||||
"1",
|
"1",
|
||||||
@ -1784,7 +1897,11 @@ public class TextInputPluginTest {
|
|||||||
ScribeChannel scribeChannel = new ScribeChannel(mock(DartExecutor.class));
|
ScribeChannel scribeChannel = new ScribeChannel(mock(DartExecutor.class));
|
||||||
TextInputPlugin textInputPlugin =
|
TextInputPlugin textInputPlugin =
|
||||||
new TextInputPlugin(
|
new TextInputPlugin(
|
||||||
testView, textInputChannel, scribeChannel, mock(PlatformViewsController.class));
|
testView,
|
||||||
|
textInputChannel,
|
||||||
|
scribeChannel,
|
||||||
|
mock(PlatformViewsController.class),
|
||||||
|
mock(PlatformViewsController2.class));
|
||||||
final TextInputChannel.Configuration.Autofill autofill =
|
final TextInputChannel.Configuration.Autofill autofill =
|
||||||
new TextInputChannel.Configuration.Autofill(
|
new TextInputChannel.Configuration.Autofill(
|
||||||
"1",
|
"1",
|
||||||
@ -1839,7 +1956,11 @@ public class TextInputPluginTest {
|
|||||||
ScribeChannel scribeChannel = new ScribeChannel(mock(DartExecutor.class));
|
ScribeChannel scribeChannel = new ScribeChannel(mock(DartExecutor.class));
|
||||||
TextInputPlugin textInputPlugin =
|
TextInputPlugin textInputPlugin =
|
||||||
new 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.
|
// Set up an autofill scenario with 2 fields.
|
||||||
final TextInputChannel.Configuration.Autofill autofill1 =
|
final TextInputChannel.Configuration.Autofill autofill1 =
|
||||||
@ -1978,7 +2099,11 @@ public class TextInputPluginTest {
|
|||||||
ScribeChannel scribeChannel = new ScribeChannel(mock(DartExecutor.class));
|
ScribeChannel scribeChannel = new ScribeChannel(mock(DartExecutor.class));
|
||||||
TextInputPlugin textInputPlugin =
|
TextInputPlugin textInputPlugin =
|
||||||
new 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.
|
// Set up an autofill scenario with 2 fields.
|
||||||
final TextInputChannel.Configuration.Autofill autofill1 =
|
final TextInputChannel.Configuration.Autofill autofill1 =
|
||||||
@ -2075,7 +2200,11 @@ public class TextInputPluginTest {
|
|||||||
ScribeChannel scribeChannel = new ScribeChannel(mock(DartExecutor.class));
|
ScribeChannel scribeChannel = new ScribeChannel(mock(DartExecutor.class));
|
||||||
TextInputPlugin textInputPlugin =
|
TextInputPlugin textInputPlugin =
|
||||||
new 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.
|
// Set up an autofill scenario with 2 fields.
|
||||||
final TextInputChannel.Configuration.Autofill autofillConfig =
|
final TextInputChannel.Configuration.Autofill autofillConfig =
|
||||||
new TextInputChannel.Configuration.Autofill(
|
new TextInputChannel.Configuration.Autofill(
|
||||||
@ -2128,7 +2257,11 @@ public class TextInputPluginTest {
|
|||||||
ScribeChannel scribeChannel = new ScribeChannel(mock(DartExecutor.class));
|
ScribeChannel scribeChannel = new ScribeChannel(mock(DartExecutor.class));
|
||||||
TextInputPlugin textInputPlugin =
|
TextInputPlugin textInputPlugin =
|
||||||
new 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.
|
// Set up an autofill scenario with 2 fields.
|
||||||
final TextInputChannel.Configuration.Autofill autofill1 =
|
final TextInputChannel.Configuration.Autofill autofill1 =
|
||||||
@ -2249,7 +2382,11 @@ public class TextInputPluginTest {
|
|||||||
View testView = new View(ctx);
|
View testView = new View(ctx);
|
||||||
TextInputPlugin textInputPlugin =
|
TextInputPlugin textInputPlugin =
|
||||||
new TextInputPlugin(
|
new TextInputPlugin(
|
||||||
testView, textInputChannel, scribeChannel, mock(PlatformViewsController.class));
|
testView,
|
||||||
|
textInputChannel,
|
||||||
|
scribeChannel,
|
||||||
|
mock(PlatformViewsController.class),
|
||||||
|
mock(PlatformViewsController2.class));
|
||||||
|
|
||||||
verify(mockBinaryMessenger, times(1))
|
verify(mockBinaryMessenger, times(1))
|
||||||
.setMessageHandler(any(String.class), binaryMessageHandlerCaptor.capture());
|
.setMessageHandler(any(String.class), binaryMessageHandlerCaptor.capture());
|
||||||
@ -2282,7 +2419,11 @@ public class TextInputPluginTest {
|
|||||||
View testView = new View(ctx);
|
View testView = new View(ctx);
|
||||||
TextInputPlugin textInputPlugin =
|
TextInputPlugin textInputPlugin =
|
||||||
new TextInputPlugin(
|
new TextInputPlugin(
|
||||||
testView, textInputChannel, scribeChannel, mock(PlatformViewsController.class));
|
testView,
|
||||||
|
textInputChannel,
|
||||||
|
scribeChannel,
|
||||||
|
mock(PlatformViewsController.class),
|
||||||
|
mock(PlatformViewsController2.class));
|
||||||
|
|
||||||
verify(mockBinaryMessenger, times(1))
|
verify(mockBinaryMessenger, times(1))
|
||||||
.setMessageHandler(any(String.class), binaryMessageHandlerCaptor.capture());
|
.setMessageHandler(any(String.class), binaryMessageHandlerCaptor.capture());
|
||||||
@ -2314,7 +2455,11 @@ public class TextInputPluginTest {
|
|||||||
ScribeChannel scribeChannel = new ScribeChannel(mock(DartExecutor.class));
|
ScribeChannel scribeChannel = new ScribeChannel(mock(DartExecutor.class));
|
||||||
TextInputPlugin textInputPlugin =
|
TextInputPlugin textInputPlugin =
|
||||||
new TextInputPlugin(
|
new TextInputPlugin(
|
||||||
testView, textInputChannel, scribeChannel, mock(PlatformViewsController.class));
|
testView,
|
||||||
|
textInputChannel,
|
||||||
|
scribeChannel,
|
||||||
|
mock(PlatformViewsController.class),
|
||||||
|
mock(PlatformViewsController2.class));
|
||||||
ImeSyncDeferringInsetsCallback imeSyncCallback = textInputPlugin.getImeSyncCallback();
|
ImeSyncDeferringInsetsCallback imeSyncCallback = textInputPlugin.getImeSyncCallback();
|
||||||
FlutterEngine flutterEngine = spy(new FlutterEngine(ctx, mockFlutterLoader, mockFlutterJni));
|
FlutterEngine flutterEngine = spy(new FlutterEngine(ctx, mockFlutterLoader, mockFlutterJni));
|
||||||
FlutterRenderer flutterRenderer = spy(new FlutterRenderer(mockFlutterJni));
|
FlutterRenderer flutterRenderer = spy(new FlutterRenderer(mockFlutterJni));
|
||||||
@ -2397,7 +2542,11 @@ public class TextInputPluginTest {
|
|||||||
ScribeChannel scribeChannel = new ScribeChannel(mock(DartExecutor.class));
|
ScribeChannel scribeChannel = new ScribeChannel(mock(DartExecutor.class));
|
||||||
TextInputPlugin textInputPlugin =
|
TextInputPlugin textInputPlugin =
|
||||||
new TextInputPlugin(
|
new TextInputPlugin(
|
||||||
testView, textInputChannel, scribeChannel, mock(PlatformViewsController.class));
|
testView,
|
||||||
|
textInputChannel,
|
||||||
|
scribeChannel,
|
||||||
|
mock(PlatformViewsController.class),
|
||||||
|
mock(PlatformViewsController2.class));
|
||||||
ImeSyncDeferringInsetsCallback imeSyncCallback = textInputPlugin.getImeSyncCallback();
|
ImeSyncDeferringInsetsCallback imeSyncCallback = textInputPlugin.getImeSyncCallback();
|
||||||
FlutterEngine flutterEngine = spy(new FlutterEngine(ctx, mockFlutterLoader, mockFlutterJni));
|
FlutterEngine flutterEngine = spy(new FlutterEngine(ctx, mockFlutterLoader, mockFlutterJni));
|
||||||
FlutterRenderer flutterRenderer = spy(new FlutterRenderer(mockFlutterJni));
|
FlutterRenderer flutterRenderer = spy(new FlutterRenderer(mockFlutterJni));
|
||||||
@ -2478,7 +2627,11 @@ public class TextInputPluginTest {
|
|||||||
ScribeChannel scribeChannel = new ScribeChannel(mock(DartExecutor.class));
|
ScribeChannel scribeChannel = new ScribeChannel(mock(DartExecutor.class));
|
||||||
TextInputPlugin textInputPlugin =
|
TextInputPlugin textInputPlugin =
|
||||||
new TextInputPlugin(
|
new TextInputPlugin(
|
||||||
testView, textInputChannel, scribeChannel, mock(PlatformViewsController.class));
|
testView,
|
||||||
|
textInputChannel,
|
||||||
|
scribeChannel,
|
||||||
|
mock(PlatformViewsController.class),
|
||||||
|
mock(PlatformViewsController2.class));
|
||||||
ImeSyncDeferringInsetsCallback imeSyncCallback = textInputPlugin.getImeSyncCallback();
|
ImeSyncDeferringInsetsCallback imeSyncCallback = textInputPlugin.getImeSyncCallback();
|
||||||
FlutterEngine flutterEngine = spy(new FlutterEngine(ctx, mockFlutterLoader, mockFlutterJni));
|
FlutterEngine flutterEngine = spy(new FlutterEngine(ctx, mockFlutterLoader, mockFlutterJni));
|
||||||
FlutterRenderer flutterRenderer = spy(new FlutterRenderer(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);
|
platformViewsController.attach(context, registry, executor);
|
||||||
|
|
||||||
|
PlatformViewsController2 secondController = new PlatformViewsController2();
|
||||||
|
|
||||||
final FlutterEngine engine = mock(FlutterEngine.class);
|
final FlutterEngine engine = mock(FlutterEngine.class);
|
||||||
when(engine.getRenderer()).thenReturn(new FlutterRenderer(jni));
|
when(engine.getRenderer()).thenReturn(new FlutterRenderer(jni));
|
||||||
when(engine.getMouseCursorChannel()).thenReturn(mock(MouseCursorChannel.class));
|
when(engine.getMouseCursorChannel()).thenReturn(mock(MouseCursorChannel.class));
|
||||||
@ -1671,6 +1673,7 @@ public class PlatformViewsControllerTest {
|
|||||||
when(engine.getSettingsChannel()).thenReturn(new SettingsChannel(executor));
|
when(engine.getSettingsChannel()).thenReturn(new SettingsChannel(executor));
|
||||||
when(engine.getScribeChannel()).thenReturn(mock(ScribeChannel.class));
|
when(engine.getScribeChannel()).thenReturn(mock(ScribeChannel.class));
|
||||||
when(engine.getPlatformViewsController()).thenReturn(platformViewsController);
|
when(engine.getPlatformViewsController()).thenReturn(platformViewsController);
|
||||||
|
when(engine.getPlatformViewsController2()).thenReturn(secondController);
|
||||||
when(engine.getLocalizationPlugin()).thenReturn(mock(LocalizationPlugin.class));
|
when(engine.getLocalizationPlugin()).thenReturn(mock(LocalizationPlugin.class));
|
||||||
when(engine.getAccessibilityChannel()).thenReturn(mock(AccessibilityChannel.class));
|
when(engine.getAccessibilityChannel()).thenReturn(mock(AccessibilityChannel.class));
|
||||||
when(engine.getDartExecutor()).thenReturn(executor);
|
when(engine.getDartExecutor()).thenReturn(executor);
|
||||||
|
Loading…
x
Reference in New Issue
Block a user