Prevent that removeActivityResultListener() is causing an ConcurrentModificationException (#19702)

This commit is contained in:
rekire 2020-07-16 02:59:08 +02:00 committed by GitHub
parent 8c511aae14
commit f21cd8aedf
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 97 additions and 1 deletions

View File

@ -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;
}

View File

@ -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;
}
}
}