From 2a7cb615dd9ba796de3a963f37c0caffbf8a3848 Mon Sep 17 00:00:00 2001 From: Gary Qian Date: Tue, 28 Jun 2022 00:29:04 -0700 Subject: [PATCH] Add registration calls for ComponentCallbacks2 (flutter/engine#34206) --- .../FlutterActivityAndFragmentDelegate.java | 5 ++ .../embedding/android/FlutterFragment.java | 22 ++++- .../android/FlutterFragmentTest.java | 80 ++++++++++++++++--- 3 files changed, 92 insertions(+), 15 deletions(-) diff --git a/engine/src/flutter/shell/platform/android/io/flutter/embedding/android/FlutterActivityAndFragmentDelegate.java b/engine/src/flutter/shell/platform/android/io/flutter/embedding/android/FlutterActivityAndFragmentDelegate.java index 4ee757ac019..93baa6e6b90 100644 --- a/engine/src/flutter/shell/platform/android/io/flutter/embedding/android/FlutterActivityAndFragmentDelegate.java +++ b/engine/src/flutter/shell/platform/android/io/flutter/embedding/android/FlutterActivityAndFragmentDelegate.java @@ -71,6 +71,11 @@ import java.util.List; private static final String PLUGINS_RESTORATION_BUNDLE_KEY = "plugins"; private static final int FLUTTER_SPLASH_VIEW_FALLBACK_ID = 486947586; + /** Factory to obtain a FlutterActivityAndFragmentDelegate instance. */ + public interface DelegateFactory { + FlutterActivityAndFragmentDelegate createDelegate(FlutterActivityAndFragmentDelegate.Host host); + } + // The FlutterActivity or FlutterFragment that is delegating most of its calls // to this FlutterActivityAndFragmentDelegate. @NonNull private Host host; diff --git a/engine/src/flutter/shell/platform/android/io/flutter/embedding/android/FlutterFragment.java b/engine/src/flutter/shell/platform/android/io/flutter/embedding/android/FlutterFragment.java index b9ea5ec21f0..4b0b11e0b2b 100644 --- a/engine/src/flutter/shell/platform/android/io/flutter/embedding/android/FlutterFragment.java +++ b/engine/src/flutter/shell/platform/android/io/flutter/embedding/android/FlutterFragment.java @@ -94,7 +94,9 @@ import java.util.List; * Activity}, as well as forwarding lifecycle calls from an {@code Activity} or a {@code Fragment}. */ public class FlutterFragment extends Fragment - implements FlutterActivityAndFragmentDelegate.Host, ComponentCallbacks2 { + implements FlutterActivityAndFragmentDelegate.Host, + ComponentCallbacks2, + FlutterActivityAndFragmentDelegate.DelegateFactory { /** * The ID of the {@code FlutterView} created by this activity. * @@ -732,6 +734,14 @@ public class FlutterFragment extends Fragment // implementation for details about why it exists. @VisibleForTesting @Nullable /* package */ FlutterActivityAndFragmentDelegate delegate; + @NonNull private FlutterActivityAndFragmentDelegate.DelegateFactory delegateFactory = this; + + /** Default delegate factory that creates a simple FlutterActivityAndFragmentDelegate instance. */ + public FlutterActivityAndFragmentDelegate createDelegate( + FlutterActivityAndFragmentDelegate.Host host) { + return new FlutterActivityAndFragmentDelegate(host); + } + private final OnBackPressedCallback onBackPressedCallback = new OnBackPressedCallback(true) { @Override @@ -757,8 +767,10 @@ public class FlutterFragment extends Fragment // TODO(mattcarroll): remove this when tests allow for it // (https://github.com/flutter/flutter/issues/43798) @VisibleForTesting - /* package */ void setDelegate(@NonNull FlutterActivityAndFragmentDelegate delegate) { - this.delegate = delegate; + /* package */ void setDelegateFactory( + @NonNull FlutterActivityAndFragmentDelegate.DelegateFactory delegateFactory) { + this.delegateFactory = delegateFactory; + delegate = delegateFactory.createDelegate(this); } /** @@ -773,11 +785,12 @@ public class FlutterFragment extends Fragment @Override public void onAttach(@NonNull Context context) { super.onAttach(context); - delegate = new FlutterActivityAndFragmentDelegate(this); + delegate = delegateFactory.createDelegate(this); delegate.onAttach(context); if (getArguments().getBoolean(ARG_SHOULD_AUTOMATICALLY_HANDLE_ON_BACK_PRESSED, false)) { requireActivity().getOnBackPressedDispatcher().addCallback(this, onBackPressedCallback); } + context.registerComponentCallbacks(this); } @Override @@ -873,6 +886,7 @@ public class FlutterFragment extends Fragment @Override public void onDetach() { + getContext().unregisterComponentCallbacks(this); super.onDetach(); if (delegate != null) { delegate.onDetach(); diff --git a/engine/src/flutter/shell/platform/android/test/io/flutter/embedding/android/FlutterFragmentTest.java b/engine/src/flutter/shell/platform/android/test/io/flutter/embedding/android/FlutterFragmentTest.java index 402d3dbcb18..33b7b25504c 100644 --- a/engine/src/flutter/shell/platform/android/test/io/flutter/embedding/android/FlutterFragmentTest.java +++ b/engine/src/flutter/shell/platform/android/test/io/flutter/embedding/android/FlutterFragmentTest.java @@ -6,9 +6,11 @@ import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; +import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; +import static org.mockito.Mockito.spy; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -36,10 +38,25 @@ public class FlutterFragmentTest { private final Context ctx = ApplicationProvider.getApplicationContext(); boolean isDelegateAttached; + class TestDelegateFactory implements FlutterActivityAndFragmentDelegate.DelegateFactory { + FlutterActivityAndFragmentDelegate delegate; + + TestDelegateFactory(FlutterActivityAndFragmentDelegate delegate) { + this.delegate = delegate; + } + + public FlutterActivityAndFragmentDelegate createDelegate( + FlutterActivityAndFragmentDelegate.Host host) { + return delegate; + } + } + @Test public void itCreatesDefaultFragmentWithExpectedDefaults() { FlutterFragment fragment = FlutterFragment.createDefault(); - fragment.setDelegate(new FlutterActivityAndFragmentDelegate(fragment)); + TestDelegateFactory delegateFactory = + new TestDelegateFactory(new FlutterActivityAndFragmentDelegate(fragment)); + fragment.setDelegateFactory(delegateFactory); assertEquals("main", fragment.getDartEntrypointFunctionName()); assertNull(fragment.getDartEntrypointLibraryUri()); @@ -68,7 +85,9 @@ public class FlutterFragmentTest { .renderMode(RenderMode.texture) .transparencyMode(TransparencyMode.opaque) .build(); - fragment.setDelegate(new FlutterActivityAndFragmentDelegate(fragment)); + TestDelegateFactory delegateFactory = + new TestDelegateFactory(new FlutterActivityAndFragmentDelegate(fragment)); + fragment.setDelegateFactory(delegateFactory); assertEquals("custom_entrypoint", fragment.getDartEntrypointFunctionName()); assertEquals("package:foo/bar.dart", fragment.getDartEntrypointLibraryUri()); @@ -127,6 +146,7 @@ public class FlutterFragmentTest { public void itCanBeDetachedFromTheEngineAndStopSendingFurtherEvents() { FlutterActivityAndFragmentDelegate mockDelegate = mock(FlutterActivityAndFragmentDelegate.class); + TestDelegateFactory delegateFactory = new TestDelegateFactory(mockDelegate); FlutterFragment fragment = FlutterFragment.withCachedEngine("my_cached_engine") .destroyEngineWithFragment(true) @@ -136,7 +156,7 @@ public class FlutterFragmentTest { when(mockDelegate.isAttached()).thenAnswer(invocation -> isDelegateAttached); doAnswer(invocation -> isDelegateAttached = false).when(mockDelegate).onDetach(); - fragment.setDelegate(mockDelegate); + fragment.setDelegateFactory(delegateFactory); fragment.onStart(); fragment.onResume(); fragment.onPostResume(); @@ -175,13 +195,14 @@ public class FlutterFragmentTest { isDelegateAttached = true; when(mockDelegate.isAttached()).thenAnswer(invocation -> isDelegateAttached); doAnswer(invocation -> isDelegateAttached = false).when(mockDelegate).onDetach(); + TestDelegateFactory delegateFactory = new TestDelegateFactory(mockDelegate); FlutterFragment fragment = FlutterFragment.withCachedEngine("my_cached_engine") .destroyEngineWithFragment(true) .build(); - fragment.setDelegate(mockDelegate); + fragment.setDelegateFactory(delegateFactory); fragment.onStart(); fragment.onResume(); fragment.onPostResume(); @@ -201,13 +222,16 @@ public class FlutterFragmentTest { isDelegateAttached = true; when(mockDelegate.isAttached()).thenAnswer(invocation -> isDelegateAttached); doAnswer(invocation -> isDelegateAttached = false).when(mockDelegate).onDetach(); + TestDelegateFactory delegateFactory = new TestDelegateFactory(mockDelegate); FlutterFragment fragment = - FlutterFragment.withCachedEngine("my_cached_engine") - .destroyEngineWithFragment(true) - .build(); + spy( + FlutterFragment.withCachedEngine("my_cached_engine") + .destroyEngineWithFragment(true) + .build()); + when(fragment.getContext()).thenReturn(mock(Context.class)); - fragment.setDelegate(mockDelegate); + fragment.setDelegateFactory(delegateFactory); fragment.onStart(); fragment.onResume(); fragment.onPostResume(); @@ -224,7 +248,8 @@ public class FlutterFragmentTest { public void itReturnsExclusiveAppComponent() { FlutterFragment fragment = FlutterFragment.createDefault(); FlutterActivityAndFragmentDelegate delegate = new FlutterActivityAndFragmentDelegate(fragment); - fragment.setDelegate(delegate); + TestDelegateFactory delegateFactory = new TestDelegateFactory(delegate); + fragment.setDelegateFactory(delegateFactory); assertEquals(fragment.getExclusiveAppComponent(), delegate); } @@ -255,7 +280,8 @@ public class FlutterFragmentTest { isDelegateAttached = true; when(mockDelegate.isAttached()).thenAnswer(invocation -> isDelegateAttached); doAnswer(invocation -> isDelegateAttached = false).when(mockDelegate).onDetach(); - fragment.setDelegate(mockDelegate); + TestDelegateFactory delegateFactory = new TestDelegateFactory(mockDelegate); + fragment.setDelegateFactory(delegateFactory); activity.onBackPressed(); @@ -294,11 +320,43 @@ public class FlutterFragmentTest { FlutterActivityAndFragmentDelegate mockDelegate = mock(FlutterActivityAndFragmentDelegate.class); - fragment.setDelegate(mockDelegate); + TestDelegateFactory delegateFactory = new TestDelegateFactory(mockDelegate); + fragment.setDelegateFactory(delegateFactory); assertTrue(fragment.popSystemNavigator()); verify(mockDelegate, never()).onBackPressed(); assertTrue(onBackPressedCalled.get()); } + + @Test + public void itRegistersComponentCallbacks() { + FlutterActivityAndFragmentDelegate mockDelegate = + mock(FlutterActivityAndFragmentDelegate.class); + isDelegateAttached = true; + when(mockDelegate.isAttached()).thenAnswer(invocation -> isDelegateAttached); + doAnswer(invocation -> isDelegateAttached = false).when(mockDelegate).onDetach(); + TestDelegateFactory delegateFactory = new TestDelegateFactory(mockDelegate); + + Context spyCtx = spy(ctx); + // We need to mock FlutterJNI to avoid triggering native code. + FlutterJNI flutterJNI = mock(FlutterJNI.class); + when(flutterJNI.isAttached()).thenReturn(true); + + FlutterEngine flutterEngine = + new FlutterEngine(spyCtx, new FlutterLoader(), flutterJNI, null, false); + FlutterEngineCache.getInstance().put("my_cached_engine", flutterEngine); + + FlutterFragment fragment = spy(FlutterFragment.withCachedEngine("my_cached_engine").build()); + when(fragment.getContext()).thenReturn(spyCtx); + fragment.setDelegateFactory(delegateFactory); + + fragment.onAttach(spyCtx); + verify(spyCtx, times(1)).registerComponentCallbacks(any()); + verify(spyCtx, never()).unregisterComponentCallbacks(any()); + + fragment.onDetach(); + verify(spyCtx, times(1)).registerComponentCallbacks(any()); + verify(spyCtx, times(1)).unregisterComponentCallbacks(any()); + } }