diff --git a/shell/platform/android/BUILD.gn b/shell/platform/android/BUILD.gn index 52642d7c619e3..943b04ca4c71a 100644 --- a/shell/platform/android/BUILD.gn +++ b/shell/platform/android/BUILD.gn @@ -421,6 +421,7 @@ action("robolectric_tests") { "test/io/flutter/embedding/android/FlutterViewTest.java", "test/io/flutter/embedding/android/RobolectricFlutterActivity.java", "test/io/flutter/embedding/engine/FlutterEngineCacheTest.java", + "test/io/flutter/embedding/engine/FlutterEnginePluginRegistryTest.java", "test/io/flutter/embedding/engine/FlutterEngineTest.java", "test/io/flutter/embedding/engine/FlutterJNITest.java", "test/io/flutter/embedding/engine/FlutterShellArgsTest.java", diff --git a/shell/platform/android/io/flutter/embedding/engine/FlutterEnginePluginRegistry.java b/shell/platform/android/io/flutter/embedding/engine/FlutterEnginePluginRegistry.java index 7fd97165f719b..0b11d86186601 100644 --- a/shell/platform/android/io/flutter/embedding/engine/FlutterEnginePluginRegistry.java +++ b/shell/platform/android/io/flutter/embedding/engine/FlutterEnginePluginRegistry.java @@ -118,6 +118,12 @@ public void destroy() { @Override public void add(@NonNull FlutterPlugin plugin) { + if (has(plugin.getClass())) { + Log.w(TAG, "Attempted to register plugin (" + plugin +") but it was " + + "already registered with this FlutterEngine (" + flutterEngine + ")."); + return; + } + Log.v(TAG, "Adding plugin: " + plugin); // Add the plugin to our generic set of plugins and notify the plugin // that is has been attached to an engine. diff --git a/shell/platform/android/test/io/flutter/FlutterTestSuite.java b/shell/platform/android/test/io/flutter/FlutterTestSuite.java index efbff14534d40..df54f4cf4f44b 100644 --- a/shell/platform/android/test/io/flutter/FlutterTestSuite.java +++ b/shell/platform/android/test/io/flutter/FlutterTestSuite.java @@ -13,6 +13,7 @@ import io.flutter.embedding.android.FlutterFragmentTest; import io.flutter.embedding.android.FlutterViewTest; import io.flutter.embedding.engine.FlutterEngineCacheTest; +import io.flutter.embedding.engine.FlutterEnginePluginRegistryTest; import io.flutter.embedding.engine.FlutterJNITest; import io.flutter.embedding.engine.RenderingComponentTest; import io.flutter.embedding.engine.plugins.shim.ShimPluginRegistryTest; @@ -35,6 +36,7 @@ FlutterActivityTest.class, FlutterAndroidComponentTest.class, FlutterEngineCacheTest.class, + FlutterEnginePluginRegistryTest.class, FlutterEngineTest.class, FlutterFragmentTest.class, FlutterJNITest.class, diff --git a/shell/platform/android/test/io/flutter/embedding/engine/FlutterEnginePluginRegistryTest.java b/shell/platform/android/test/io/flutter/embedding/engine/FlutterEnginePluginRegistryTest.java new file mode 100644 index 0000000000000..15a44a475aaf7 --- /dev/null +++ b/shell/platform/android/test/io/flutter/embedding/engine/FlutterEnginePluginRegistryTest.java @@ -0,0 +1,80 @@ +package io.flutter.embedding.engine; + +import android.content.Context; +import android.support.annotation.NonNull; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.robolectric.RobolectricTestRunner; +import org.robolectric.annotation.Config; + +import io.flutter.embedding.engine.loader.FlutterLoader; +import io.flutter.embedding.engine.plugins.FlutterPlugin; +import io.flutter.plugin.platform.PlatformViewsController; + +import static junit.framework.TestCase.assertTrue; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +// Run with Robolectric so that Log calls don't crash. +@Config(manifest=Config.NONE) +@RunWith(RobolectricTestRunner.class) +public class FlutterEnginePluginRegistryTest { + @Test + public void itDoesNotRegisterTheSamePluginTwice() { + Context context = mock(Context.class); + + FlutterEngine flutterEngine = mock(FlutterEngine.class); + PlatformViewsController platformViewsController = mock(PlatformViewsController.class); + when(flutterEngine.getPlatformViewsController()).thenReturn(platformViewsController); + + FlutterLoader flutterLoader = mock(FlutterLoader.class); + + FakeFlutterPlugin fakePlugin1 = new FakeFlutterPlugin(); + FakeFlutterPlugin fakePlugin2 = new FakeFlutterPlugin(); + + FlutterEnginePluginRegistry registry = new FlutterEnginePluginRegistry( + context, + flutterEngine, + flutterLoader + ); + + // Verify that the registry doesn't think it contains our plugin yet. + assertFalse(registry.has(fakePlugin1.getClass())); + + // Add our plugin to the registry. + registry.add(fakePlugin1); + + // Verify that the registry now thinks it contains our plugin. + assertTrue(registry.has(fakePlugin1.getClass())); + assertEquals(1, fakePlugin1.attachmentCallCount); + + // Add a different instance of the same plugin class. + registry.add(fakePlugin2); + + // Verify that the registry did not detach the 1st plugin, and + // it did not attach the 2nd plugin. + assertEquals(1, fakePlugin1.attachmentCallCount); + assertEquals(0, fakePlugin1.detachmentCallCount); + + assertEquals(0, fakePlugin2.attachmentCallCount); + assertEquals(0, fakePlugin2.detachmentCallCount); + } + + private static class FakeFlutterPlugin implements FlutterPlugin { + public int attachmentCallCount = 0; + public int detachmentCallCount = 0; + + @Override + public void onAttachedToEngine(@NonNull FlutterPluginBinding binding) { + attachmentCallCount += 1; + } + + @Override + public void onDetachedFromEngine(@NonNull FlutterPluginBinding binding) { + detachmentCallCount += 1; + } + } +}