diff --git a/shell/platform/android/io/flutter/embedding/engine/FlutterEnginePluginRegistry.java b/shell/platform/android/io/flutter/embedding/engine/FlutterEnginePluginRegistry.java index 64a11fa9431..7e9d39b3075 100644 --- a/shell/platform/android/io/flutter/embedding/engine/FlutterEnginePluginRegistry.java +++ b/shell/platform/android/io/flutter/embedding/engine/FlutterEnginePluginRegistry.java @@ -686,7 +686,7 @@ class FlutterEnginePluginRegistry boolean onActivityResult(int requestCode, int resultCode, @Nullable Intent data) { boolean didConsumeResult = false; for (io.flutter.plugin.common.PluginRegistry.ActivityResultListener listener : - onActivityResultListeners) { + new HashSet<>(onActivityResultListeners)) { didConsumeResult = listener.onActivityResult(requestCode, resultCode, data) || didConsumeResult; } diff --git a/shell/platform/android/test/io/flutter/embedding/engine/FlutterEnginePluginRegistryTest.java b/shell/platform/android/test/io/flutter/embedding/engine/FlutterEnginePluginRegistryTest.java index 846299d02f7..ee653348004 100644 --- a/shell/platform/android/test/io/flutter/embedding/engine/FlutterEnginePluginRegistryTest.java +++ b/shell/platform/android/test/io/flutter/embedding/engine/FlutterEnginePluginRegistryTest.java @@ -6,11 +6,18 @@ import static org.junit.Assert.assertFalse; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; +import android.app.Activity; import android.content.Context; +import android.content.Intent; import androidx.annotation.NonNull; +import androidx.lifecycle.Lifecycle; import io.flutter.embedding.engine.loader.FlutterLoader; 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.PluginRegistry; import io.flutter.plugin.platform.PlatformViewsController; +import java.util.concurrent.atomic.AtomicBoolean; import org.junit.Test; import org.junit.runner.RunWith; import org.robolectric.RobolectricTestRunner; @@ -58,6 +65,49 @@ public class FlutterEnginePluginRegistryTest { assertEquals(0, fakePlugin2.detachmentCallCount); } + @Test + public void activityResultListenerCanBeRemovedFromListener() { + 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); + Activity activity = mock(Activity.class); + Lifecycle lifecycle = mock(Lifecycle.class); + Intent intent = mock(Intent.class); + AtomicBoolean isFirstCall = new AtomicBoolean(true); + + // setup the environment to get the required internal data + FlutterEnginePluginRegistry registry = + new FlutterEnginePluginRegistry(context, flutterEngine, flutterLoader); + FakeActivityAwareFlutterPlugin fakePlugin = new FakeActivityAwareFlutterPlugin(); + registry.add(fakePlugin); + registry.attachToActivity(activity, lifecycle); + + // The binding is now available via `fakePlugin.binding`: Create and add the listeners + FakeActivityResultListener listener1 = + new FakeActivityResultListener(isFirstCall, fakePlugin.binding); + FakeActivityResultListener listener2 = + new FakeActivityResultListener(isFirstCall, fakePlugin.binding); + + fakePlugin.binding.addActivityResultListener(listener1); + fakePlugin.binding.addActivityResultListener(listener2); + + // fire the onActivityResult which should invoke both listeners + registry.onActivityResult(0, 0, intent); + + assertEquals(1, listener1.callCount); + assertEquals(1, listener2.callCount); + + // fire it again to check if the first called listener was removed + registry.onActivityResult(0, 0, intent); + + // The order of the listeners in the HashSet is random: So just check the sum of calls + assertEquals(3, listener1.callCount + listener2.callCount); + } + private static class FakeFlutterPlugin implements FlutterPlugin { public int attachmentCallCount = 0; public int detachmentCallCount = 0; @@ -72,4 +122,50 @@ public class FlutterEnginePluginRegistryTest { detachmentCallCount += 1; } } + + private static class FakeActivityAwareFlutterPlugin implements FlutterPlugin, ActivityAware { + public ActivityPluginBinding binding; + + @Override + public void onAttachedToEngine(@NonNull FlutterPluginBinding binding) {} + + @Override + public void onDetachedFromEngine(@NonNull FlutterPluginBinding binding) {} + + @Override + public void onAttachedToActivity(final ActivityPluginBinding binding) { + this.binding = binding; + } + + @Override + public void onDetachedFromActivityForConfigChanges() {} + + @Override + public void onReattachedToActivityForConfigChanges(final ActivityPluginBinding binding) {} + + @Override + public void onDetachedFromActivity() {} + } + + private static class FakeActivityResultListener implements PluginRegistry.ActivityResultListener { + public int callCount = 0; + private final AtomicBoolean isFirstCall; + private final ActivityPluginBinding binding; + + public FakeActivityResultListener(AtomicBoolean isFirstCall, ActivityPluginBinding binding) { + this.isFirstCall = isFirstCall; + this.binding = binding; + } + + @Override + public boolean onActivityResult( + final int requestCode, final int resultCode, final Intent data) { + callCount++; + if (isFirstCall.get()) { + isFirstCall.set(false); + binding.removeActivityResultListener(this); + } + return false; + } + } }