diff --git a/shell/platform/android/io/flutter/embedding/android/FlutterActivity.java b/shell/platform/android/io/flutter/embedding/android/FlutterActivity.java index 3341db91c7569..f9eabc5015cd4 100644 --- a/shell/platform/android/io/flutter/embedding/android/FlutterActivity.java +++ b/shell/platform/android/io/flutter/embedding/android/FlutterActivity.java @@ -16,6 +16,7 @@ import android.os.Bundle; import android.support.annotation.NonNull; import android.support.annotation.Nullable; +import android.support.v4.app.Fragment; import android.support.v4.app.FragmentActivity; import android.support.v4.app.FragmentManager; import android.util.TypedValue; @@ -66,7 +67,10 @@ * {@code Fragment}. */ // TODO(mattcarroll): explain each call forwarded to Fragment (first requires resolution of PluginRegistry API). -public class FlutterActivity extends FragmentActivity implements OnFirstFrameRenderedListener { +public class FlutterActivity extends FragmentActivity + implements FlutterFragment.FlutterEngineProvider, + FlutterFragment.FlutterEngineConfigurator, + OnFirstFrameRenderedListener { private static final String TAG = "FlutterActivity"; // Meta-data arguments, processed from manifest XML. @@ -365,6 +369,27 @@ protected boolean shouldAttachEngineToActivity() { return true; } + /** + * Hook for subclasses to easily provide a custom {@code FlutterEngine}. + */ + @Nullable + @Override + public FlutterEngine provideFlutterEngine(@NonNull Context context) { + // No-op. Hook for subclasses. + return null; + } + + /** + * Hook for subclasses to easily configure a {@code FlutterEngine}, e.g., register + * plugins. + *
+ * This method is called after {@link #provideFlutterEngine(Context)}. + */ + @Override + public void configureFlutterEngine(@NonNull FlutterEngine flutterEngine) { + // No-op. Hook for subclasses. + } + @Override public void onPostResume() { super.onPostResume(); diff --git a/shell/platform/android/io/flutter/embedding/android/FlutterFragment.java b/shell/platform/android/io/flutter/embedding/android/FlutterFragment.java index 59d9719dc940d..4893002a5f280 100644 --- a/shell/platform/android/io/flutter/embedding/android/FlutterFragment.java +++ b/shell/platform/android/io/flutter/embedding/android/FlutterFragment.java @@ -6,6 +6,8 @@ import static android.content.ComponentCallbacks2.TRIM_MEMORY_RUNNING_LOW; +import android.app.Activity; +import android.arch.lifecycle.Lifecycle; import android.content.Context; import android.content.Intent; import android.os.Build; @@ -359,8 +361,13 @@ public void onAttach(@NonNull Context context) { // sync with the Activity. We use the Fragment's Lifecycle because it is possible that the // attached Activity is not a LifecycleOwner. Log.d(TAG, "Attaching FlutterEngine to the Activity that owns this Fragment."); - flutterEngine.getActivityControlSurface().attachToActivity(getActivity(), getLifecycle()); + flutterEngine.getActivityControlSurface().attachToActivity( + getActivity(), + getLifecycle() + ); } + + configureFlutterEngine(flutterEngine); } private void initializeFlutter(@NonNull Context context) { @@ -401,11 +408,11 @@ private void setupFlutterEngine() { // Defer to the Activity that owns us to provide a FlutterEngine. Log.d(TAG, "Deferring to attached Activity to provide a FlutterEngine."); FlutterEngineProvider flutterEngineProvider = (FlutterEngineProvider) attachedActivity; - flutterEngine = flutterEngineProvider.getFlutterEngine(getContext()); + flutterEngine = flutterEngineProvider.provideFlutterEngine(getContext()); if (flutterEngine != null) { isFlutterEngineFromActivity = true; + return; } - return; } // Neither our subclass, nor our owning Activity wanted to provide a custom FlutterEngine. @@ -434,11 +441,34 @@ protected FlutterEngine createFlutterEngine(@NonNull Context context) { return null; } + /** + * Configures a {@link FlutterEngine} after its creation. + *
+ * This method is called after the given {@link FlutterEngine} has been attached to the + * owning {@code FragmentActivity}. See + * {@link io.flutter.embedding.engine.plugins.activity.ActivityControlSurface#attachToActivity(Activity, Lifecycle)}. + *
+ * It is possible that the owning {@code FragmentActivity} opted not to connect itself as + * an {@link io.flutter.embedding.engine.plugins.activity.ActivityControlSurface}. In that + * case, any configuration, e.g., plugins, must not expect or depend upon an available + * {@code Activity} at the time that this method is invoked. + *
+ * The default behavior of this method is to defer to the owning {@code FragmentActivity} + * as a {@link FlutterEngineConfigurator}. Subclasses can override this method if the + * subclass needs to override the {@code FragmentActivity}'s behavior, or add to it. + */ + protected void configureFlutterEngine(@NonNull FlutterEngine flutterEngine) { + FragmentActivity attachedActivity = getActivity(); + if (attachedActivity instanceof FlutterEngineConfigurator) { + ((FlutterEngineConfigurator) attachedActivity).configureFlutterEngine(flutterEngine); + } + } + @Nullable @Override - public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { + public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { Log.v(TAG, "Creating FlutterView."); - flutterView = new FlutterView(getContext(), getRenderMode(), getTransparencyMode()); + flutterView = new FlutterView(getActivity(), getRenderMode(), getTransparencyMode()); flutterView.addOnFirstFrameRenderedListener(onFirstFrameRenderedListener); return flutterView; } @@ -548,10 +578,6 @@ public void run() { Log.v(TAG, "Attaching FlutterEngine to FlutterView."); flutterView.attachToFlutterEngine(flutterEngine); - // TODO(mattcarroll): the following call should exist here, but the plugin system needs to be revamped. - // The existing attach() method does not know how to handle this kind of FlutterView. - //flutterEngine.getPlugins().attach(this, getActivity()); - doInitialFlutterViewRun(); } }); @@ -788,7 +814,7 @@ protected void onFirstFrameRendered() {} * {@link FlutterActivity}s and/or {@code FlutterFragments}. *
* If the {@link FragmentActivity} that owns this {@code FlutterFragment} implements - * {@code FlutterEngineProvider}, that {@link FlutterActivity} will be given an opportunity + * {@code FlutterEngineProvider}, that {@link FragmentActivity} will be given an opportunity * to provide a {@link FlutterEngine} instead of the {@code FlutterFragment} creating a * new one. The {@link FragmentActivity} can provide an existing, pre-warmed {@link FlutterEngine}, * if desired. @@ -804,6 +830,27 @@ public interface FlutterEngineProvider { * to provide its own {@code FlutterEngine} instance. */ @Nullable - FlutterEngine getFlutterEngine(@NonNull Context context); + FlutterEngine provideFlutterEngine(@NonNull Context context); + } + + /** + * Configures a {@link FlutterEngine} after it is created, e.g., adds plugins. + *
+ * This interface may be applied to a {@link FragmentActivity} that owns a {@code FlutterFragment}. + */ + public interface FlutterEngineConfigurator { + /** + * Configures the given {@link FlutterEngine}. + *
+ * This method is called after the given {@link FlutterEngine} has been attached to the + * owning {@code FragmentActivity}. See + * {@link io.flutter.embedding.engine.plugins.activity.ActivityControlSurface#attachToActivity(Activity, Lifecycle)}. + *
+ * It is possible that the owning {@code FragmentActivity} opted not to connect itself as + * an {@link io.flutter.embedding.engine.plugins.activity.ActivityControlSurface}. In that + * case, any configuration, e.g., plugins, must not expect or depend upon an available + * {@code Activity} at the time that this method is invoked. + */ + void configureFlutterEngine(@NonNull FlutterEngine flutterEngine); } } diff --git a/shell/platform/android/io/flutter/embedding/android/FlutterView.java b/shell/platform/android/io/flutter/embedding/android/FlutterView.java index 45ad7515effed..49b933c03ad44 100644 --- a/shell/platform/android/io/flutter/embedding/android/FlutterView.java +++ b/shell/platform/android/io/flutter/embedding/android/FlutterView.java @@ -18,6 +18,7 @@ import android.util.AttributeSet; import android.view.KeyEvent; import android.view.MotionEvent; +import android.view.View; import android.view.WindowInsets; import android.view.accessibility.AccessibilityManager; import android.view.accessibility.AccessibilityNodeProvider; @@ -36,6 +37,7 @@ import io.flutter.embedding.engine.renderer.FlutterRenderer; import io.flutter.embedding.engine.renderer.OnFirstFrameRenderedListener; import io.flutter.plugin.editing.TextInputPlugin; +import io.flutter.plugin.platform.PlatformViewsController; import io.flutter.view.AccessibilityBridge; /** @@ -117,6 +119,8 @@ public void onFirstFrameRendered() { *
* {@link #transparencyMode} defaults to {@link TransparencyMode#opaque}. + *
+ * {@code FlutterView} requires an {@code Activity} instead of a generic {@code Context} + * to be compatible with {@link PlatformViewsController}. */ public FlutterView(@NonNull Context context, @NonNull RenderMode renderMode) { this(context, null, renderMode, null); @@ -135,6 +142,9 @@ public FlutterView(@NonNull Context context, @NonNull RenderMode renderMode) { /** * Constructs a {@code FlutterView} programmatically, without any XML attributes, * assumes the use of {@link RenderMode#surface}, and allows selection of a {@link #transparencyMode}. + *
+ * {@code FlutterView} requires an {@code Activity} instead of a generic {@code Context} + * to be compatible with {@link PlatformViewsController}. */ public FlutterView(@NonNull Context context, @NonNull TransparencyMode transparencyMode) { this(context, null, RenderMode.surface, transparencyMode); @@ -143,6 +153,9 @@ public FlutterView(@NonNull Context context, @NonNull TransparencyMode transpare /** * Constructs a {@code FlutterView} programmatically, without any XML attributes, and allows * a selection of {@link #renderMode} and {@link #transparencyMode}. + *
+ * {@code FlutterView} requires an {@code Activity} instead of a generic {@code Context} + * to be compatible with {@link PlatformViewsController}. */ public FlutterView(@NonNull Context context, @NonNull RenderMode renderMode, @NonNull TransparencyMode transparencyMode) { this(context, null, renderMode, transparencyMode); @@ -150,9 +163,11 @@ public FlutterView(@NonNull Context context, @NonNull RenderMode renderMode, @No /** * Constructs a {@code FlutterSurfaceView} in an XML-inflation-compliant manner. - * - * // TODO(mattcarroll): expose renderMode in XML when build system supports R.attr + *
+ * {@code FlutterView} requires an {@code Activity} instead of a generic {@code Context} + * to be compatible with {@link PlatformViewsController}. */ + // TODO(mattcarroll): expose renderMode in XML when build system supports R.attr public FlutterView(@NonNull Context context, @Nullable AttributeSet attrs) { this(context, attrs, null, null); } @@ -367,6 +382,21 @@ public InputConnection onCreateInputConnection(@NonNull EditorInfo outAttrs) { return textInputPlugin.createInputConnection(this, outAttrs); } + /** + * Allows a {@code View} that is not currently the input connection target to invoke commands on + * the {@link android.view.inputmethod.InputMethodManager}, which is otherwise disallowed. + *
+ * Returns true to allow non-input-connection-targets to invoke methods on
+ * {@code InputMethodManager}, or false to exclusively allow the input connection target to invoke
+ * such methods.
+ */
+ @Override
+ public boolean checkInputConnectionProxy(View view) {
+ return flutterEngine != null
+ ? flutterEngine.getPlatformViewsController().checkInputConnectionProxy(view)
+ : super.checkInputConnectionProxy(view);
+ }
+
/**
* Invoked when key is released.
*
@@ -511,7 +541,9 @@ private void resetWillNotDraw(boolean isAccessibilityEnabled, boolean isTouchExp
* See {@link #detachFromFlutterEngine()} for information on how to detach from a
* {@link FlutterEngine}.
*/
- public void attachToFlutterEngine(@NonNull FlutterEngine flutterEngine) {
+ public void attachToFlutterEngine(
+ @NonNull FlutterEngine flutterEngine
+ ) {
Log.d(TAG, "Attaching to a FlutterEngine: " + flutterEngine);
if (isAttachedToFlutterEngine()) {
if (flutterEngine == this.flutterEngine) {
@@ -537,7 +569,7 @@ public void attachToFlutterEngine(@NonNull FlutterEngine flutterEngine) {
textInputPlugin = new TextInputPlugin(
this,
this.flutterEngine.getDartExecutor(),
- null
+ this.flutterEngine.getPlatformViewsController()
);
androidKeyProcessor = new AndroidKeyProcessor(
this.flutterEngine.getKeyEventChannel(),
@@ -549,9 +581,7 @@ public void attachToFlutterEngine(@NonNull FlutterEngine flutterEngine) {
flutterEngine.getAccessibilityChannel(),
(AccessibilityManager) getContext().getSystemService(Context.ACCESSIBILITY_SERVICE),
getContext().getContentResolver(),
- // TODO(mattcaroll): plumb the platform views controller to the accessibility bridge.
- // https://github.com/flutter/flutter/issues/29618
- null
+ this.flutterEngine.getPlatformViewsController()
);
accessibilityBridge.setOnAccessibilityChangeListener(onAccessibilityChangeListener);
resetWillNotDraw(
@@ -559,6 +589,10 @@ public void attachToFlutterEngine(@NonNull FlutterEngine flutterEngine) {
accessibilityBridge.isTouchExplorationEnabled()
);
+ // Connect AccessibilityBridge to the PlatformViewsController within the FlutterEngine.
+ // This allows platform Views to hook into Flutter's overall accessibility system.
+ this.flutterEngine.getPlatformViewsController().attachAccessibilityBridge(accessibilityBridge);
+
// Inform the Android framework that it should retrieve a new InputConnection
// now that an engine is attached.
// TODO(mattcarroll): once this is proven to work, move this line ot TextInputPlugin
@@ -597,6 +631,9 @@ public void detachFromFlutterEngine() {
listener.onFlutterEngineDetachedFromFlutterView();
}
+ // Disconnect the FlutterEngine's PlatformViewsController from the AccessibilityBridge.
+ flutterEngine.getPlatformViewsController().detachAccessibiltyBridge();
+
// Disconnect and clean up the AccessibilityBridge.
accessibilityBridge.release();
accessibilityBridge = null;
diff --git a/shell/platform/android/io/flutter/embedding/engine/FlutterEngine.java b/shell/platform/android/io/flutter/embedding/engine/FlutterEngine.java
index 08ed9f75004f7..3e2fb2b1c2644 100644
--- a/shell/platform/android/io/flutter/embedding/engine/FlutterEngine.java
+++ b/shell/platform/android/io/flutter/embedding/engine/FlutterEngine.java
@@ -29,6 +29,7 @@
import io.flutter.embedding.engine.systemchannels.SettingsChannel;
import io.flutter.embedding.engine.systemchannels.SystemChannel;
import io.flutter.embedding.engine.systemchannels.TextInputChannel;
+import io.flutter.plugin.platform.PlatformViewsController;
/**
* A single Flutter execution environment.
@@ -88,6 +89,11 @@ public class FlutterEngine implements LifecycleOwner {
@NonNull
private final TextInputChannel textInputChannel;
+ // Platform Views.
+ @NonNull
+ private final PlatformViewsController platformViewsController;
+
+ // Engine Lifecycle.
@NonNull
private final Set
diff --git a/shell/platform/android/io/flutter/embedding/engine/plugins/activity/ActivityPluginBinding.java b/shell/platform/android/io/flutter/embedding/engine/plugins/activity/ActivityPluginBinding.java
index 9420c43c57b10..2958d79a81322 100644
--- a/shell/platform/android/io/flutter/embedding/engine/plugins/activity/ActivityPluginBinding.java
+++ b/shell/platform/android/io/flutter/embedding/engine/plugins/activity/ActivityPluginBinding.java
@@ -8,6 +8,7 @@
import android.support.annotation.NonNull;
import io.flutter.plugin.common.PluginRegistry;
+import io.flutter.plugin.platform.PlatformViewsController;
/**
* Binding that gives {@link ActivityAware} plugins access to an associated {@link Activity} and
diff --git a/shell/platform/android/io/flutter/embedding/engine/plugins/shim/ShimPluginRegistry.java b/shell/platform/android/io/flutter/embedding/engine/plugins/shim/ShimPluginRegistry.java
index ce85eb0f37c90..011eea303bab1 100644
--- a/shell/platform/android/io/flutter/embedding/engine/plugins/shim/ShimPluginRegistry.java
+++ b/shell/platform/android/io/flutter/embedding/engine/plugins/shim/ShimPluginRegistry.java
@@ -4,7 +4,6 @@
package io.flutter.embedding.engine.plugins.shim;
-import android.app.Activity;
import android.support.annotation.NonNull;
import java.util.HashMap;
@@ -18,8 +17,6 @@
import io.flutter.embedding.engine.plugins.activity.ActivityAware;
import io.flutter.embedding.engine.plugins.activity.ActivityPluginBinding;
import io.flutter.plugin.common.PluginRegistry;
-import io.flutter.plugin.platform.PlatformViewsController;
-import io.flutter.view.FlutterView;
/**
* A {@link PluginRegistry} that is shimmed to use the new Android embedding and plugin API behind
@@ -41,24 +38,11 @@ public class ShimPluginRegistry implements PluginRegistry {
private static final String TAG = "ShimPluginRegistry";
private final FlutterEngine flutterEngine;
- private final PlatformViewsController platformViewsController;
private final Map
diff --git a/shell/platform/android/io/flutter/embedding/engine/plugins/shim/ShimRegistrar.java b/shell/platform/android/io/flutter/embedding/engine/plugins/shim/ShimRegistrar.java
index e6bb8e50c7860..39e5ea5764353 100644
--- a/shell/platform/android/io/flutter/embedding/engine/plugins/shim/ShimRegistrar.java
+++ b/shell/platform/android/io/flutter/embedding/engine/plugins/shim/ShimRegistrar.java
@@ -74,7 +74,7 @@ public TextureRegistry textures() {
@Override
public PlatformViewRegistry platformViewRegistry() {
- return null;
+ return pluginBinding != null ? pluginBinding.getFlutterEngine().getPlatformViewsController().getRegistry() : null;
}
@Override