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

Add plugin shim to facilitate old plugins in new embedding (#33478). #9120

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions ci/licenses_golden/licenses_flutter
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 2 additions & 0 deletions shell/platform/android/BUILD.gn
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -84,11 +87,13 @@ public class FlutterEngine implements LifecycleOwner {
@NonNull
private final TextInputChannel textInputChannel;

private final Set<EngineLifecycleListener> 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();
}
}
};

Expand Down Expand Up @@ -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}.
*
Expand Down
Original file line number Diff line number Diff line change
@@ -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.
* <p>
* 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<String, Object> 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> 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;
}
}
Original file line number Diff line number Diff line change
@@ -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.
* <p>
* 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<String, Object> globalRegistrarMap;
private final String pluginId;
private final Set<PluginRegistry.ViewDestroyListener> viewDestroyListeners = new HashSet<>();
private final Set<PluginRegistry.RequestPermissionsResultListener> requestPermissionsResultListeners = new HashSet<>();
private final Set<PluginRegistry.ActivityResultListener> activityResultListeners = new HashSet<>();
private final Set<PluginRegistry.NewIntentListener> newIntentListeners = new HashSet<>();
private final Set<PluginRegistry.UserLeaveHintListener> userLeaveHintListeners = new HashSet<>();
private FlutterPlugin.FlutterPluginBinding pluginBinding;
private ActivityPluginBinding activityPluginBinding;

public ShimRegistrar(@NonNull String pluginId, @NonNull Map<String, Object> 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);
}
}
}