diff --git a/ci/licenses_golden/licenses_flutter b/ci/licenses_golden/licenses_flutter index 611cdc4dba0bf..8b5fea520bf03 100755 --- a/ci/licenses_golden/licenses_flutter +++ b/ci/licenses_golden/licenses_flutter @@ -754,6 +754,7 @@ FILE: ../../../flutter/shell/platform/android/io/flutter/embedding/engine/plugin 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/plugins/util/GeneratedPluginRegister.java FILE: ../../../flutter/shell/platform/android/io/flutter/embedding/engine/renderer/FlutterRenderer.java FILE: ../../../flutter/shell/platform/android/io/flutter/embedding/engine/renderer/FlutterUiDisplayListener.java FILE: ../../../flutter/shell/platform/android/io/flutter/embedding/engine/renderer/RenderSurface.java diff --git a/shell/platform/android/BUILD.gn b/shell/platform/android/BUILD.gn index 23300cf01a4d3..3443dba51d3ae 100644 --- a/shell/platform/android/BUILD.gn +++ b/shell/platform/android/BUILD.gn @@ -177,6 +177,7 @@ android_java_sources = [ "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/plugins/util/GeneratedPluginRegister.java", "io/flutter/embedding/engine/renderer/FlutterRenderer.java", "io/flutter/embedding/engine/renderer/FlutterUiDisplayListener.java", "io/flutter/embedding/engine/renderer/RenderSurface.java", diff --git a/shell/platform/android/io/flutter/embedding/android/FlutterActivity.java b/shell/platform/android/io/flutter/embedding/android/FlutterActivity.java index acc208a8dc7a3..7446522a5b6c7 100644 --- a/shell/platform/android/io/flutter/embedding/android/FlutterActivity.java +++ b/shell/platform/android/io/flutter/embedding/android/FlutterActivity.java @@ -42,9 +42,9 @@ import io.flutter.embedding.engine.FlutterEngine; import io.flutter.embedding.engine.FlutterShellArgs; import io.flutter.embedding.engine.plugins.activity.ActivityControlSurface; +import io.flutter.embedding.engine.plugins.util.GeneratedPluginRegister; import io.flutter.plugin.platform.PlatformPlugin; import io.flutter.view.FlutterMain; -import java.lang.reflect.Method; /** * {@code Activity} which displays a fullscreen Flutter UI. @@ -872,7 +872,7 @@ public PlatformPlugin providePlatformPlugin( */ @Override public void configureFlutterEngine(@NonNull FlutterEngine flutterEngine) { - registerPlugins(flutterEngine); + GeneratedPluginRegister.registerGeneratedPlugins(flutterEngine); } /** @@ -962,34 +962,4 @@ public boolean shouldRestoreAndSaveState() { } return true; } - - /** - * Registers all plugins that an app lists in its pubspec.yaml. - * - *

The Flutter tool generates a class called GeneratedPluginRegistrant, which includes the code - * necessary to register every plugin in the pubspec.yaml with a given {@code FlutterEngine}. The - * GeneratedPluginRegistrant must be generated per app, because each app uses different sets of - * plugins. Therefore, the Android embedding cannot place a compile-time dependency on this - * generated class. This method uses reflection to attempt to locate the generated file and then - * use it at runtime. - * - *

This method fizzles if the GeneratedPluginRegistrant cannot be found or invoked. This - * situation should never occur, but if any eventuality comes up that prevents an app from using - * this behavior, that app can still write code that explicitly registers plugins. - */ - private static void registerPlugins(@NonNull FlutterEngine flutterEngine) { - try { - Class generatedPluginRegistrant = - Class.forName("io.flutter.plugins.GeneratedPluginRegistrant"); - Method registrationMethod = - generatedPluginRegistrant.getDeclaredMethod("registerWith", FlutterEngine.class); - registrationMethod.invoke(null, flutterEngine); - } catch (Exception e) { - Log.w( - TAG, - "Tried to automatically register plugins with FlutterEngine (" - + flutterEngine - + ") but could not find and invoke the GeneratedPluginRegistrant."); - } - } } diff --git a/shell/platform/android/io/flutter/embedding/android/FlutterFragmentActivity.java b/shell/platform/android/io/flutter/embedding/android/FlutterFragmentActivity.java index 0d0c0d203bc8b..2a1cb94241fb1 100644 --- a/shell/platform/android/io/flutter/embedding/android/FlutterFragmentActivity.java +++ b/shell/platform/android/io/flutter/embedding/android/FlutterFragmentActivity.java @@ -39,6 +39,7 @@ import io.flutter.embedding.android.FlutterActivityLaunchConfigs.BackgroundMode; import io.flutter.embedding.engine.FlutterEngine; import io.flutter.embedding.engine.FlutterShellArgs; +import io.flutter.embedding.engine.plugins.util.GeneratedPluginRegister; import io.flutter.plugin.platform.PlatformPlugin; import io.flutter.view.FlutterMain; @@ -554,13 +555,18 @@ public FlutterEngine provideFlutterEngine(@NonNull Context context) { } /** - * Hook for subclasses to easily configure a {@code FlutterEngine}, e.g., register plugins. + * Hook for subclasses to easily configure a {@code FlutterEngine}. * *

This method is called after {@link #provideFlutterEngine(Context)}. + * + *

All plugins listed in the app's pubspec are registered in the base implementation of this + * method. To avoid automatic plugin registration, override this method without invoking super(). + * To keep automatic plugin registration and further configure the flutterEngine, override this + * method, invoke super(), and then configure the flutterEngine as desired. */ @Override public void configureFlutterEngine(@NonNull FlutterEngine flutterEngine) { - // No-op. Hook for subclasses. + GeneratedPluginRegister.registerGeneratedPlugins(flutterEngine); } /** diff --git a/shell/platform/android/io/flutter/embedding/engine/plugins/util/GeneratedPluginRegister.java b/shell/platform/android/io/flutter/embedding/engine/plugins/util/GeneratedPluginRegister.java new file mode 100644 index 0000000000000..5bb0ff5a6f37b --- /dev/null +++ b/shell/platform/android/io/flutter/embedding/engine/plugins/util/GeneratedPluginRegister.java @@ -0,0 +1,43 @@ +// 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.util; + +import androidx.annotation.NonNull; +import io.flutter.Log; +import io.flutter.embedding.engine.FlutterEngine; +import java.lang.reflect.Method; + +public class GeneratedPluginRegister { + private static final String TAG = "GeneratedPluginsRegister"; + /** + * Registers all plugins that an app lists in its pubspec.yaml. + * + *

The Flutter tool generates a class called GeneratedPluginRegistrant, which includes the code + * necessary to register every plugin in the pubspec.yaml with a given {@code FlutterEngine}. The + * GeneratedPluginRegistrant must be generated per app, because each app uses different sets of + * plugins. Therefore, the Android embedding cannot place a compile-time dependency on this + * generated class. This method uses reflection to attempt to locate the generated file and then + * use it at runtime. + * + *

This method fizzles if the GeneratedPluginRegistrant cannot be found or invoked. This + * situation should never occur, but if any eventuality comes up that prevents an app from using + * this behavior, that app can still write code that explicitly registers plugins. + */ + public static void registerGeneratedPlugins(@NonNull FlutterEngine flutterEngine) { + try { + Class generatedPluginRegistrant = + Class.forName("io.flutter.plugins.GeneratedPluginRegistrant"); + Method registrationMethod = + generatedPluginRegistrant.getDeclaredMethod("registerWith", FlutterEngine.class); + registrationMethod.invoke(null, flutterEngine); + } catch (Exception e) { + Log.w( + TAG, + "Tried to automatically register plugins with FlutterEngine (" + + flutterEngine + + ") but could not find and invoke the GeneratedPluginRegistrant."); + } + } +} diff --git a/shell/platform/android/test/io/flutter/embedding/android/FlutterFragmentActivityTest.java b/shell/platform/android/test/io/flutter/embedding/android/FlutterFragmentActivityTest.java index 88ef48cbdd8e9..cd311a835588e 100644 --- a/shell/platform/android/test/io/flutter/embedding/android/FlutterFragmentActivityTest.java +++ b/shell/platform/android/test/io/flutter/embedding/android/FlutterFragmentActivityTest.java @@ -1,18 +1,40 @@ package io.flutter.embedding.android; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; +import android.content.Context; import android.content.Intent; import androidx.annotation.NonNull; +import androidx.annotation.Nullable; import io.flutter.embedding.android.FlutterActivityLaunchConfigs.BackgroundMode; +import io.flutter.embedding.engine.FlutterEngine; +import io.flutter.embedding.engine.FlutterJNI; +import io.flutter.embedding.engine.loader.FlutterLoader; +import io.flutter.plugins.GeneratedPluginRegistrant; +import java.util.List; +import org.junit.After; +import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; +import org.robolectric.Robolectric; import org.robolectric.RobolectricTestRunner; import org.robolectric.annotation.Config; @Config(manifest = Config.NONE) @RunWith(RobolectricTestRunner.class) public class FlutterFragmentActivityTest { + @Before + public void setUp() { + GeneratedPluginRegistrant.clearRegisteredEngines(); + } + + @After + public void tearDown() { + GeneratedPluginRegistrant.clearRegisteredEngines(); + } @Test public void createFlutterFragment__defaultRenderModeSurface() { @@ -44,6 +66,39 @@ protected RenderMode getRenderMode() { assertEquals(activity.createFlutterFragment().getRenderMode(), RenderMode.texture); } + @Test + public void itRegistersPluginsAtConfigurationTime() { + FlutterFragmentActivity activity = + Robolectric.buildActivity(FlutterFragmentActivityWithProvidedEngine.class).get(); + assertTrue(GeneratedPluginRegistrant.getRegisteredEngines().isEmpty()); + + // Calling onCreate on the FlutterFragmentActivity will create a FlutterFragment and + // commit it to the fragment manager. This attaches the fragment to the FlutterFragmentActivity + // creating and configuring the engine. + activity.onCreate(null); + + List registeredEngines = GeneratedPluginRegistrant.getRegisteredEngines(); + assertEquals(1, registeredEngines.size()); + assertEquals(activity.getFlutterEngine(), registeredEngines.get(0)); + } + + static class FlutterFragmentActivityWithProvidedEngine extends FlutterFragmentActivity { + @Override + protected FlutterFragment createFlutterFragment() { + return FlutterFragment.createDefault(); + } + + @Nullable + @Override + public FlutterEngine provideFlutterEngine(@NonNull Context context) { + FlutterJNI flutterJNI = mock(FlutterJNI.class); + when(flutterJNI.isAttached()).thenReturn(true); + + return new FlutterEngine( + context, mock(FlutterLoader.class), flutterJNI, new String[] {}, false); + } + } + private static class FakeFlutterFragmentActivity extends FlutterFragmentActivity { @Override public Intent getIntent() {