diff --git a/ci/licenses_golden/licenses_flutter b/ci/licenses_golden/licenses_flutter index ee13f32a19bca..f857ff633fb84 100644 --- a/ci/licenses_golden/licenses_flutter +++ b/ci/licenses_golden/licenses_flutter @@ -532,6 +532,8 @@ FILE: ../../../flutter/shell/platform/android/io/flutter/embedding/engine/plugin FILE: ../../../flutter/shell/platform/android/io/flutter/embedding/engine/plugins/service/ServiceAware.java FILE: ../../../flutter/shell/platform/android/io/flutter/embedding/engine/plugins/service/ServiceControlSurface.java FILE: ../../../flutter/shell/platform/android/io/flutter/embedding/engine/plugins/service/ServicePluginBinding.java +FILE: ../../../flutter/shell/platform/android/io/flutter/embedding/engine/plugins/shim/ShimPluginRegistry.java +FILE: ../../../flutter/shell/platform/android/io/flutter/embedding/engine/plugins/shim/ShimRegistrar.java FILE: ../../../flutter/shell/platform/android/io/flutter/embedding/engine/renderer/FlutterRenderer.java FILE: ../../../flutter/shell/platform/android/io/flutter/embedding/engine/renderer/OnFirstFrameRenderedListener.java FILE: ../../../flutter/shell/platform/android/io/flutter/embedding/engine/systemchannels/AccessibilityChannel.java diff --git a/shell/platform/android/BUILD.gn b/shell/platform/android/BUILD.gn index 71dd3fb30e866..f116492783508 100644 --- a/shell/platform/android/BUILD.gn +++ b/shell/platform/android/BUILD.gn @@ -149,6 +149,8 @@ action("flutter_shell_java") { "io/flutter/embedding/engine/plugins/service/ServiceAware.java", "io/flutter/embedding/engine/plugins/service/ServiceControlSurface.java", "io/flutter/embedding/engine/plugins/service/ServicePluginBinding.java", + "io/flutter/embedding/engine/plugins/shim/ShimPluginRegistry.java", + "io/flutter/embedding/engine/plugins/shim/ShimRegistrar.java", "io/flutter/embedding/engine/renderer/FlutterRenderer.java", "io/flutter/embedding/engine/renderer/OnFirstFrameRenderedListener.java", "io/flutter/embedding/engine/systemchannels/AccessibilityChannel.java", diff --git a/shell/platform/android/io/flutter/embedding/android/FlutterActivity.java b/shell/platform/android/io/flutter/embedding/android/FlutterActivity.java index d77a06f1fcc88..c4c4c39ab4b92 100644 --- a/shell/platform/android/io/flutter/embedding/android/FlutterActivity.java +++ b/shell/platform/android/io/flutter/embedding/android/FlutterActivity.java @@ -151,7 +151,7 @@ public Intent build(@NonNull Context context) { } @Override - public void onCreate(Bundle savedInstanceState) { + protected void onCreate(Bundle savedInstanceState) { Log.d(TAG, "onCreate()"); super.onCreate(savedInstanceState); setContentView(createFragmentContainer()); diff --git a/shell/platform/android/io/flutter/embedding/engine/FlutterEngine.java b/shell/platform/android/io/flutter/embedding/engine/FlutterEngine.java index 452804df581b9..599f6f163c5dc 100644 --- a/shell/platform/android/io/flutter/embedding/engine/FlutterEngine.java +++ b/shell/platform/android/io/flutter/embedding/engine/FlutterEngine.java @@ -9,6 +9,9 @@ import android.content.Context; import android.support.annotation.NonNull; +import java.util.HashSet; +import java.util.Set; + import io.flutter.embedding.engine.dart.DartExecutor; import io.flutter.embedding.engine.plugins.PluginRegistry; import io.flutter.embedding.engine.plugins.activity.ActivityControlSurface; @@ -84,11 +87,13 @@ public class FlutterEngine implements LifecycleOwner { @NonNull private final TextInputChannel textInputChannel; + private final Set engineLifecycleListeners = new HashSet<>(); private final EngineLifecycleListener engineLifecycleListener = new EngineLifecycleListener() { @SuppressWarnings("unused") public void onPreEngineRestart() { - // TODO(mattcarroll): work into plugin API. should probably loop through each plugin. -// pluginRegistry.onPreEngineRestart(); + for (EngineLifecycleListener lifecycleListener : engineLifecycleListeners) { + lifecycleListener.onPreEngineRestart(); + } } }; @@ -165,6 +170,22 @@ public void destroy() { flutterJNI.detachFromNativeAndReleaseResources(); } + /** + * Adds a {@code listener} to be notified of Flutter engine lifecycle events, e.g., + * {@code onPreEngineStart()}. + */ + public void addEngineLifecycleListener(@NonNull EngineLifecycleListener listener) { + engineLifecycleListeners.add(listener); + } + + /** + * Removes a {@code listener} that was previously added with + * {@link #addEngineLifecycleListener(EngineLifecycleListener)}. + */ + public void removeEngineLifecycleListener(@NonNull EngineLifecycleListener listener) { + engineLifecycleListeners.remove(listener); + } + /** * The Dart execution context associated with this {@code FlutterEngine}. * 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 new file mode 100644 index 0000000000000..7d582d53a7dd1 --- /dev/null +++ b/shell/platform/android/io/flutter/embedding/engine/plugins/shim/ShimPluginRegistry.java @@ -0,0 +1,92 @@ +// 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.plugins.shim; + +import android.app.Activity; +import android.support.annotation.NonNull; + +import java.util.HashMap; +import java.util.Map; + +import io.flutter.embedding.engine.FlutterEngine; +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 + * the scenes. + *

+ * The following is an example usage of {@code ShimPluginRegistry} within a {@code FlutterActivity}: + * {@code + * // Create the FlutterEngine that will back the Flutter UI. + * FlutterEngine flutterEngine = new FlutterEngine(context); + * + * // Create a ShimPluginRegistry and wrap the FlutterEngine with the shim. + * ShimPluginRegistry shimPluginRegistry = new ShimPluginRegistry(flutterEngine, platformViewsController); + * + * // Use the GeneratedPluginRegistrant to add every plugin that's in the pubspec. + * GeneratedPluginRegistrant.registerWith(shimPluginRegistry); + * } + */ +public class ShimPluginRegistry implements PluginRegistry { + private final FlutterEngine flutterEngine; + private final PlatformViewsController platformViewsController; + private final Map pluginMap = new HashMap<>(); + private final FlutterEngine.EngineLifecycleListener engineLifecycleListener = new FlutterEngine.EngineLifecycleListener() { + @Override + public void onPreEngineRestart() { + ShimPluginRegistry.this.onPreEngineRestart(); + } + }; + + public ShimPluginRegistry( + @NonNull FlutterEngine flutterEngine, + @NonNull PlatformViewsController platformViewsController + ) { + this.flutterEngine = flutterEngine; + this.flutterEngine.addEngineLifecycleListener(engineLifecycleListener); + this.platformViewsController = platformViewsController; + } + + @Override + public Registrar registrarFor(String pluginKey) { + if (pluginMap.containsKey(pluginKey)) { + throw new IllegalStateException("Plugin key " + pluginKey + " is already in use"); + } + pluginMap.put(pluginKey, null); + ShimRegistrar registrar = new ShimRegistrar(pluginKey, pluginMap); + flutterEngine.getPlugins().add(registrar); + return registrar; + } + + @Override + public boolean hasPlugin(String pluginKey) { + return pluginMap.containsKey(pluginKey); + } + + @Override + public T valuePublishedByPlugin(String pluginKey) { + return (T) pluginMap.get(pluginKey); + } + + //----- From FlutterPluginRegistry that aren't in the PluginRegistry interface ----// + public void attach(FlutterView flutterView, Activity activity) { + platformViewsController.attach(activity, flutterEngine.getRenderer(), flutterEngine.getDartExecutor()); + } + + public void detach() { + platformViewsController.detach(); + platformViewsController.onFlutterViewDestroyed(); + } + + private void onPreEngineRestart() { + platformViewsController.onPreEngineRestart(); + } + + public PlatformViewsController getPlatformViewsController() { + return platformViewsController; + } +} 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 new file mode 100644 index 0000000000000..cab4ca9c03305 --- /dev/null +++ b/shell/platform/android/io/flutter/embedding/engine/plugins/shim/ShimRegistrar.java @@ -0,0 +1,203 @@ +// 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.plugins.shim; + +import android.app.Activity; +import android.content.Context; +import android.support.annotation.NonNull; + +import java.util.HashSet; +import java.util.Map; +import java.util.Set; + +import io.flutter.embedding.engine.plugins.FlutterPlugin; +import io.flutter.embedding.engine.plugins.activity.ActivityAware; +import io.flutter.embedding.engine.plugins.activity.ActivityPluginBinding; +import io.flutter.plugin.common.BinaryMessenger; +import io.flutter.plugin.common.PluginRegistry; +import io.flutter.plugin.platform.PlatformViewRegistry; +import io.flutter.view.FlutterMain; +import io.flutter.view.FlutterView; +import io.flutter.view.TextureRegistry; + +/** + * A {@link PluginRegistry.Registrar} that is shimmed to use the new Android embedding and plugin + * API behind the scenes. + *

+ * Instances of {@code ShimRegistrar}s are vended internally by a {@link ShimPluginRegistry}. + */ +class ShimRegistrar implements PluginRegistry.Registrar, FlutterPlugin, ActivityAware { + private static final String TAG = "ShimRegistrar"; + + private final Map globalRegistrarMap; + private final String pluginId; + private final Set viewDestroyListeners = new HashSet<>(); + private final Set requestPermissionsResultListeners = new HashSet<>(); + private final Set activityResultListeners = new HashSet<>(); + private final Set newIntentListeners = new HashSet<>(); + private final Set userLeaveHintListeners = new HashSet<>(); + private FlutterPlugin.FlutterPluginBinding pluginBinding; + private ActivityPluginBinding activityPluginBinding; + + public ShimRegistrar(@NonNull String pluginId, @NonNull Map globalRegistrarMap) { + this.pluginId = pluginId; + this.globalRegistrarMap = globalRegistrarMap; + } + + @Override + public Activity activity() { + return activityPluginBinding != null ? activityPluginBinding.getActivity() : null; + } + + @Override + public Context context() { + return pluginBinding != null ? pluginBinding.getApplicationContext() : null; + } + + @Override + public Context activeContext() { + return activityPluginBinding == null ? context() : activity(); + } + + @Override + public BinaryMessenger messenger() { + return pluginBinding != null ? pluginBinding.getFlutterEngine().getDartExecutor() : null; + } + + @Override + public TextureRegistry textures() { + return pluginBinding != null ? pluginBinding.getFlutterEngine().getRenderer() : null; + } + + @Override + public PlatformViewRegistry platformViewRegistry() { + return null; + } + + @Override + public FlutterView view() { + throw new UnsupportedOperationException("The new embedding does not support the old FlutterView."); + } + + @Override + public String lookupKeyForAsset(String asset) { + return FlutterMain.getLookupKeyForAsset(asset); + } + + @Override + public String lookupKeyForAsset(String asset, String packageName) { + return FlutterMain.getLookupKeyForAsset(asset, packageName); + } + + @Override + public PluginRegistry.Registrar publish(Object value) { + globalRegistrarMap.put(pluginId, value); + return this; + } + + @Override + public PluginRegistry.Registrar addRequestPermissionsResultListener(PluginRegistry.RequestPermissionsResultListener listener) { + requestPermissionsResultListeners.add(listener); + + if (activityPluginBinding != null) { + activityPluginBinding.addRequestPermissionsResultListener(listener); + } + + return this; + } + + @Override + public PluginRegistry.Registrar addActivityResultListener(PluginRegistry.ActivityResultListener listener) { + activityResultListeners.add(listener); + + if (activityPluginBinding != null) { + activityPluginBinding.addActivityResultListener(listener); + } + + return this; + } + + @Override + public PluginRegistry.Registrar addNewIntentListener(PluginRegistry.NewIntentListener listener) { + newIntentListeners.add(listener); + + if (activityPluginBinding != null) { + activityPluginBinding.addOnNewIntentListener(listener); + } + + return this; + } + + @Override + public PluginRegistry.Registrar addUserLeaveHintListener(PluginRegistry.UserLeaveHintListener listener) { + userLeaveHintListeners.add(listener); + + if (activityPluginBinding != null) { + activityPluginBinding.addOnUserLeaveHintListener(listener); + } + + return this; + } + + @Override + @NonNull + public PluginRegistry.Registrar addViewDestroyListener(@NonNull PluginRegistry.ViewDestroyListener listener) { + viewDestroyListeners.add(listener); + return this; + } + + @Override + public void onAttachedToEngine(@NonNull FlutterPluginBinding binding) { + pluginBinding = binding; + } + + @Override + public void onDetachedFromEngine(@NonNull FlutterPluginBinding binding) { + for (PluginRegistry.ViewDestroyListener listener : viewDestroyListeners) { + // The following invocation might produce unexpected behavior in old plugins because + // we have no FlutterNativeView to pass to onViewDestroy(). This is a limitation of this shim. + listener.onViewDestroy(null); + } + + pluginBinding = null; + } + + @Override + public void onAttachedToActivity(@NonNull ActivityPluginBinding binding) { + activityPluginBinding = binding; + addExistingListenersToActivityPluginBinding(); + } + + @Override + public void onDetachedFromActivityForConfigChanges() { + activityPluginBinding = null; + } + + @Override + public void onReattachedToActivityForConfigChanges(@NonNull ActivityPluginBinding binding) { + activityPluginBinding = binding; + addExistingListenersToActivityPluginBinding(); + } + + @Override + public void onDetachedFromActivity() { + activityPluginBinding = null; + } + + private void addExistingListenersToActivityPluginBinding() { + for (PluginRegistry.RequestPermissionsResultListener listener : requestPermissionsResultListeners) { + activityPluginBinding.addRequestPermissionsResultListener(listener); + } + for (PluginRegistry.ActivityResultListener listener : activityResultListeners) { + activityPluginBinding.addActivityResultListener(listener); + } + for (PluginRegistry.NewIntentListener listener : newIntentListeners) { + activityPluginBinding.addOnNewIntentListener(listener); + } + for (PluginRegistry.UserLeaveHintListener listener : userLeaveHintListeners) { + activityPluginBinding.addOnUserLeaveHintListener(listener); + } + } +}