Remove Robolectric.setupActivity from engine junit tests (#167607)

Remove the deprecated Robolectric.setupActivity from the engine junit
tests. This replaces it with the ActivityScenario.

Activity Scenario documentation

https://developer.android.com/reference/androidx/test/core/app/ActivityScenario
Extension function that sets up and shuts down an activity before and
after each test.

https://developer.android.com/reference/androidx/test/ext/junit/rules/ActivityScenarioRule

Fixes #133151

## Pre-launch Checklist

- [X] I read the [Contributor Guide] and followed the process outlined
there for submitting PRs.
- [X] I read the [Tree Hygiene] wiki page, which explains my
responsibilities.
- [X] I read and followed the [Flutter Style Guide], including [Features
we expect every widget to implement].
- [X] I signed the [CLA].
- [X] I listed at least one issue that this PR fixes in the description
above.
- [X] I updated/added relevant documentation (doc comments with `///`).
- [X] I added new tests to check the change I am making, or this PR is
[test-exempt].
- [X] I followed the [breaking change policy] and added [Data Driven
Fixes] where supported.
- [X] All existing and new tests are passing.
This commit is contained in:
Matt Boetger 2025-04-24 01:23:10 +00:00 committed by GitHub
parent f5ae49a862
commit 3438da3cf4
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 1363 additions and 1177 deletions

View File

@ -29,6 +29,7 @@ import android.view.View;
import android.window.BackEvent;
import androidx.annotation.NonNull;
import androidx.lifecycle.Lifecycle;
import androidx.test.core.app.ActivityScenario;
import androidx.test.core.app.ApplicationProvider;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import io.flutter.FlutterInjector;
@ -73,9 +74,6 @@ public class FlutterActivityAndFragmentDelegateTest {
private FlutterActivityAndFragmentDelegate.Host mockHost;
private FlutterActivityAndFragmentDelegate.Host mockHost2;
@SuppressWarnings("deprecation")
// Robolectric.setupActivity
// TODO(reidbaker): https://github.com/flutter/flutter/issues/133151
@Before
public void setup() {
FlutterInjector.reset();
@ -86,7 +84,6 @@ public class FlutterActivityAndFragmentDelegateTest {
// Create a mocked Host, which is required by the delegate being tested.
mockHost = mock(FlutterActivityAndFragmentDelegate.Host.class);
when(mockHost.getContext()).thenReturn(ctx);
when(mockHost.getActivity()).thenReturn(Robolectric.setupActivity(Activity.class));
when(mockHost.getLifecycle()).thenReturn(mock(Lifecycle.class));
when(mockHost.getFlutterShellArgs()).thenReturn(new FlutterShellArgs(new String[] {}));
when(mockHost.getDartEntrypointFunctionName()).thenReturn("main");
@ -104,7 +101,6 @@ public class FlutterActivityAndFragmentDelegateTest {
mockHost2 = mock(FlutterActivityAndFragmentDelegate.Host.class);
when(mockHost2.getContext()).thenReturn(ctx);
when(mockHost2.getActivity()).thenReturn(Robolectric.setupActivity(Activity.class));
when(mockHost2.getLifecycle()).thenReturn(mock(Lifecycle.class));
when(mockHost2.getFlutterShellArgs()).thenReturn(new FlutterShellArgs(new String[] {}));
when(mockHost2.getDartEntrypointFunctionName()).thenReturn("main");
@ -126,79 +122,90 @@ public class FlutterActivityAndFragmentDelegateTest {
// ---- Test setup ----
// Create the real object that we're testing.
FlutterActivityAndFragmentDelegate delegate = new FlutterActivityAndFragmentDelegate(mockHost);
try (ActivityScenario<Activity> scenario = ActivityScenario.launch(Activity.class)) {
scenario.onActivity(
activity -> {
when(mockHost.getActivity()).thenReturn(activity);
// We're testing lifecycle behaviors, which require/expect that certain methods have already
// been executed by the time they run. Therefore, we run those expected methods first.
delegate.onAttach(ctx);
delegate.onCreateView(null, null, null, 0, true);
// We're testing lifecycle behaviors, which require/expect that certain methods have
// already
// been executed by the time they run. Therefore, we run those expected methods first.
delegate.onAttach(ctx);
delegate.onCreateView(null, null, null, 0, true);
// --- Execute the behavior under test ---
// By the time an Activity/Fragment is started, we don't expect any lifecycle messages
// to have been sent to Flutter.
delegate.onStart();
verify(mockFlutterEngine.getLifecycleChannel(), never()).aWindowIsFocused();
verify(mockFlutterEngine.getLifecycleChannel(), never()).noWindowsAreFocused();
verify(mockFlutterEngine.getLifecycleChannel(), never()).appIsResumed();
verify(mockFlutterEngine.getLifecycleChannel(), never()).appIsInactive();
verify(mockFlutterEngine.getLifecycleChannel(), never()).appIsPaused();
verify(mockFlutterEngine.getLifecycleChannel(), never()).appIsDetached();
// --- Execute the behavior under test ---
// By the time an Activity/Fragment is started, we don't expect any lifecycle messages
// to have been sent to Flutter.
delegate.onStart();
verify(mockFlutterEngine.getLifecycleChannel(), never()).aWindowIsFocused();
verify(mockFlutterEngine.getLifecycleChannel(), never()).noWindowsAreFocused();
verify(mockFlutterEngine.getLifecycleChannel(), never()).appIsResumed();
verify(mockFlutterEngine.getLifecycleChannel(), never()).appIsInactive();
verify(mockFlutterEngine.getLifecycleChannel(), never()).appIsPaused();
verify(mockFlutterEngine.getLifecycleChannel(), never()).appIsDetached();
// When the Activity/Fragment is resumed, a resumed message should have been sent to Flutter.
delegate.onResume();
verify(mockFlutterEngine.getLifecycleChannel(), never()).aWindowIsFocused();
verify(mockFlutterEngine.getLifecycleChannel(), never()).noWindowsAreFocused();
verify(mockFlutterEngine.getLifecycleChannel(), times(1)).appIsResumed();
verify(mockFlutterEngine.getLifecycleChannel(), never()).appIsInactive();
verify(mockFlutterEngine.getLifecycleChannel(), never()).appIsPaused();
verify(mockFlutterEngine.getLifecycleChannel(), never()).appIsDetached();
// When the Activity/Fragment is resumed, a resumed message should have been sent to
// Flutter.
delegate.onResume();
verify(mockFlutterEngine.getLifecycleChannel(), never()).aWindowIsFocused();
verify(mockFlutterEngine.getLifecycleChannel(), never()).noWindowsAreFocused();
verify(mockFlutterEngine.getLifecycleChannel(), times(1)).appIsResumed();
verify(mockFlutterEngine.getLifecycleChannel(), never()).appIsInactive();
verify(mockFlutterEngine.getLifecycleChannel(), never()).appIsPaused();
verify(mockFlutterEngine.getLifecycleChannel(), never()).appIsDetached();
// When the app loses focus because something else has it (e.g. notification
// windowshade or app switcher), it should go to inactive.
delegate.onWindowFocusChanged(false);
verify(mockFlutterEngine.getLifecycleChannel(), never()).aWindowIsFocused();
verify(mockFlutterEngine.getLifecycleChannel(), times(1)).noWindowsAreFocused();
verify(mockFlutterEngine.getLifecycleChannel(), times(1)).appIsResumed();
verify(mockFlutterEngine.getLifecycleChannel(), never()).appIsInactive();
verify(mockFlutterEngine.getLifecycleChannel(), never()).appIsPaused();
verify(mockFlutterEngine.getLifecycleChannel(), never()).appIsDetached();
// When the app loses focus because something else has it (e.g. notification
// windowshade or app switcher), it should go to inactive.
delegate.onWindowFocusChanged(false);
verify(mockFlutterEngine.getLifecycleChannel(), never()).aWindowIsFocused();
verify(mockFlutterEngine.getLifecycleChannel(), times(1)).noWindowsAreFocused();
verify(mockFlutterEngine.getLifecycleChannel(), times(1)).appIsResumed();
verify(mockFlutterEngine.getLifecycleChannel(), never()).appIsInactive();
verify(mockFlutterEngine.getLifecycleChannel(), never()).appIsPaused();
verify(mockFlutterEngine.getLifecycleChannel(), never()).appIsDetached();
// When the app regains focus, it should go to resumed again.
delegate.onWindowFocusChanged(true);
verify(mockFlutterEngine.getLifecycleChannel(), times(1)).aWindowIsFocused();
verify(mockFlutterEngine.getLifecycleChannel(), times(1)).noWindowsAreFocused();
verify(mockFlutterEngine.getLifecycleChannel(), times(1)).appIsResumed();
verify(mockFlutterEngine.getLifecycleChannel(), never()).appIsInactive();
verify(mockFlutterEngine.getLifecycleChannel(), never()).appIsPaused();
verify(mockFlutterEngine.getLifecycleChannel(), never()).appIsDetached();
// When the app regains focus, it should go to resumed again.
delegate.onWindowFocusChanged(true);
verify(mockFlutterEngine.getLifecycleChannel(), times(1)).aWindowIsFocused();
verify(mockFlutterEngine.getLifecycleChannel(), times(1)).noWindowsAreFocused();
verify(mockFlutterEngine.getLifecycleChannel(), times(1)).appIsResumed();
verify(mockFlutterEngine.getLifecycleChannel(), never()).appIsInactive();
verify(mockFlutterEngine.getLifecycleChannel(), never()).appIsPaused();
verify(mockFlutterEngine.getLifecycleChannel(), never()).appIsDetached();
// When the Activity/Fragment is paused, an inactive message should have been sent to Flutter.
delegate.onPause();
verify(mockFlutterEngine.getLifecycleChannel(), times(1)).aWindowIsFocused();
verify(mockFlutterEngine.getLifecycleChannel(), times(1)).noWindowsAreFocused();
verify(mockFlutterEngine.getLifecycleChannel(), times(1)).appIsResumed();
verify(mockFlutterEngine.getLifecycleChannel(), times(1)).appIsInactive();
verify(mockFlutterEngine.getLifecycleChannel(), never()).appIsPaused();
verify(mockFlutterEngine.getLifecycleChannel(), never()).appIsDetached();
// When the Activity/Fragment is paused, an inactive message should have been sent to
// Flutter.
delegate.onPause();
verify(mockFlutterEngine.getLifecycleChannel(), times(1)).aWindowIsFocused();
verify(mockFlutterEngine.getLifecycleChannel(), times(1)).noWindowsAreFocused();
verify(mockFlutterEngine.getLifecycleChannel(), times(1)).appIsResumed();
verify(mockFlutterEngine.getLifecycleChannel(), times(1)).appIsInactive();
verify(mockFlutterEngine.getLifecycleChannel(), never()).appIsPaused();
verify(mockFlutterEngine.getLifecycleChannel(), never()).appIsDetached();
// When the Activity/Fragment is stopped, a paused message should have been sent to Flutter.
// Notice that Flutter uses the term "paused" in a different way, and at a different time
// than the Android OS.
delegate.onStop();
verify(mockFlutterEngine.getLifecycleChannel(), times(1)).aWindowIsFocused();
verify(mockFlutterEngine.getLifecycleChannel(), times(1)).noWindowsAreFocused();
verify(mockFlutterEngine.getLifecycleChannel(), times(1)).appIsResumed();
verify(mockFlutterEngine.getLifecycleChannel(), times(1)).appIsInactive();
verify(mockFlutterEngine.getLifecycleChannel(), times(1)).appIsPaused();
verify(mockFlutterEngine.getLifecycleChannel(), never()).appIsDetached();
// When the Activity/Fragment is stopped, a paused message should have been sent to
// Flutter.
// Notice that Flutter uses the term "paused" in a different way, and at a different
// time
// than the Android OS.
delegate.onStop();
verify(mockFlutterEngine.getLifecycleChannel(), times(1)).aWindowIsFocused();
verify(mockFlutterEngine.getLifecycleChannel(), times(1)).noWindowsAreFocused();
verify(mockFlutterEngine.getLifecycleChannel(), times(1)).appIsResumed();
verify(mockFlutterEngine.getLifecycleChannel(), times(1)).appIsInactive();
verify(mockFlutterEngine.getLifecycleChannel(), times(1)).appIsPaused();
verify(mockFlutterEngine.getLifecycleChannel(), never()).appIsDetached();
// When activity detaches, a detached message should have been sent to Flutter.
delegate.onDetach();
verify(mockFlutterEngine.getLifecycleChannel(), times(1)).aWindowIsFocused();
verify(mockFlutterEngine.getLifecycleChannel(), times(1)).noWindowsAreFocused();
verify(mockFlutterEngine.getLifecycleChannel(), times(1)).appIsResumed();
verify(mockFlutterEngine.getLifecycleChannel(), times(1)).appIsInactive();
verify(mockFlutterEngine.getLifecycleChannel(), times(1)).appIsPaused();
verify(mockFlutterEngine.getLifecycleChannel(), times(1)).appIsDetached();
// When activity detaches, a detached message should have been sent to Flutter.
delegate.onDetach();
verify(mockFlutterEngine.getLifecycleChannel(), times(1)).aWindowIsFocused();
verify(mockFlutterEngine.getLifecycleChannel(), times(1)).noWindowsAreFocused();
verify(mockFlutterEngine.getLifecycleChannel(), times(1)).appIsResumed();
verify(mockFlutterEngine.getLifecycleChannel(), times(1)).appIsInactive();
verify(mockFlutterEngine.getLifecycleChannel(), times(1)).appIsPaused();
verify(mockFlutterEngine.getLifecycleChannel(), times(1)).appIsDetached();
});
}
}
@Test
@ -206,27 +213,34 @@ public class FlutterActivityAndFragmentDelegateTest {
// ---- Test setup ----
// Create the real object that we're testing.
FlutterActivityAndFragmentDelegate delegate = new FlutterActivityAndFragmentDelegate(mockHost);
try (ActivityScenario<Activity> scenario = ActivityScenario.launch(Activity.class)) {
scenario.onActivity(
activity -> {
when(mockHost.getActivity()).thenReturn(activity);
when(mockHost.shouldDispatchAppLifecycleState()).thenReturn(false);
when(mockHost.shouldDispatchAppLifecycleState()).thenReturn(false);
// We're testing lifecycle behaviors, which require/expect that certain methods have already
// been executed by the time they run. Therefore, we run those expected methods first.
delegate.onAttach(ctx);
delegate.onCreateView(null, null, null, 0, true);
delegate.onStart();
delegate.onResume();
delegate.onWindowFocusChanged(false);
delegate.onWindowFocusChanged(true);
delegate.onPause();
delegate.onStop();
delegate.onDetach();
// We're testing lifecycle behaviors, which require/expect that certain methods have
// already
// been executed by the time they run. Therefore, we run those expected methods first.
delegate.onAttach(ctx);
delegate.onCreateView(null, null, null, 0, true);
delegate.onStart();
delegate.onResume();
delegate.onWindowFocusChanged(false);
delegate.onWindowFocusChanged(true);
delegate.onPause();
delegate.onStop();
delegate.onDetach();
verify(mockFlutterEngine.getLifecycleChannel(), never()).aWindowIsFocused();
verify(mockFlutterEngine.getLifecycleChannel(), never()).noWindowsAreFocused();
verify(mockFlutterEngine.getLifecycleChannel(), never()).appIsResumed();
verify(mockFlutterEngine.getLifecycleChannel(), never()).appIsPaused();
verify(mockFlutterEngine.getLifecycleChannel(), never()).appIsInactive();
verify(mockFlutterEngine.getLifecycleChannel(), never()).appIsDetached();
verify(mockFlutterEngine.getLifecycleChannel(), never()).aWindowIsFocused();
verify(mockFlutterEngine.getLifecycleChannel(), never()).noWindowsAreFocused();
verify(mockFlutterEngine.getLifecycleChannel(), never()).appIsResumed();
verify(mockFlutterEngine.getLifecycleChannel(), never()).appIsPaused();
verify(mockFlutterEngine.getLifecycleChannel(), never()).appIsInactive();
verify(mockFlutterEngine.getLifecycleChannel(), never()).appIsDetached();
});
}
}
@Test
@ -291,12 +305,19 @@ public class FlutterActivityAndFragmentDelegateTest {
// Create the real object that we're testing.
FlutterActivityAndFragmentDelegate delegate = new FlutterActivityAndFragmentDelegate(mockHost);
try (ActivityScenario<Activity> scenario = ActivityScenario.launch(Activity.class)) {
scenario.onActivity(
activity -> {
when(mockHost.getActivity()).thenReturn(activity);
// --- Execute the behavior under test ---
// The FlutterEngine existence is verified in onAttach()
delegate.onAttach(ctx);
// Expect IllegalStateException.
// --- Execute the behavior under test ---
// The FlutterEngine existence is verified in onAttach()
delegate.onAttach(ctx);
});
} catch (IllegalStateException e) {
// Expect IllegalStateException.
throw e;
}
}
// Bug: b/271100292
@ -407,13 +428,19 @@ public class FlutterActivityAndFragmentDelegateTest {
// ---- Test setup ----
// Create the real object that we're testing.
FlutterActivityAndFragmentDelegate delegate = new FlutterActivityAndFragmentDelegate(mockHost);
try (ActivityScenario<Activity> scenario = ActivityScenario.launch(Activity.class)) {
scenario.onActivity(
activity -> {
when(mockHost.getActivity()).thenReturn(activity);
// --- Execute the behavior under test ---
// The FlutterEngine is created in onAttach().
delegate.onAttach(ctx);
// --- Execute the behavior under test ---
// The FlutterEngine is created in onAttach().
delegate.onAttach(ctx);
// Verify that the host was asked to configure our FlutterEngine.
verify(mockHost, times(1)).configureFlutterEngine(mockFlutterEngine);
// Verify that the host was asked to configure our FlutterEngine.
verify(mockHost, times(1)).configureFlutterEngine(mockFlutterEngine);
});
}
}
@Test
@ -430,36 +457,40 @@ public class FlutterActivityAndFragmentDelegateTest {
verify(mockHost, times(1)).onFlutterSurfaceViewCreated(isNotNull());
}
@SuppressWarnings("deprecation")
// Robolectric.setupActivity
// TODO(reidbaker): https://github.com/flutter/flutter/issues/133151
@Test
public void itGivesHostAnOpportunityToConfigureFlutterTextureView() {
// ---- Test setup ----
Host customMockHost = mock(Host.class);
when(customMockHost.getContext()).thenReturn(ctx);
when(customMockHost.getActivity()).thenReturn(Robolectric.setupActivity(Activity.class));
when(customMockHost.getLifecycle()).thenReturn(mock(Lifecycle.class));
when(customMockHost.getFlutterShellArgs()).thenReturn(new FlutterShellArgs(new String[] {}));
when(customMockHost.getDartEntrypointFunctionName()).thenReturn("main");
when(customMockHost.getAppBundlePath()).thenReturn("/fake/path");
when(customMockHost.getInitialRoute()).thenReturn("/");
when(customMockHost.getRenderMode()).thenReturn(RenderMode.texture);
when(customMockHost.getTransparencyMode()).thenReturn(TransparencyMode.transparent);
when(customMockHost.provideFlutterEngine(any(Context.class))).thenReturn(mockFlutterEngine);
when(customMockHost.shouldAttachEngineToActivity()).thenReturn(true);
when(customMockHost.shouldDestroyEngineWithHost()).thenReturn(true);
try (ActivityScenario<Activity> scenario = ActivityScenario.launch(Activity.class)) {
scenario.onActivity(
activity -> {
when(customMockHost.getActivity()).thenReturn(activity);
when(customMockHost.getLifecycle()).thenReturn(mock(Lifecycle.class));
when(customMockHost.getFlutterShellArgs())
.thenReturn(new FlutterShellArgs(new String[] {}));
when(customMockHost.getDartEntrypointFunctionName()).thenReturn("main");
when(customMockHost.getAppBundlePath()).thenReturn("/fake/path");
when(customMockHost.getInitialRoute()).thenReturn("/");
when(customMockHost.getRenderMode()).thenReturn(RenderMode.texture);
when(customMockHost.getTransparencyMode()).thenReturn(TransparencyMode.transparent);
when(customMockHost.provideFlutterEngine(any(Context.class)))
.thenReturn(mockFlutterEngine);
when(customMockHost.shouldAttachEngineToActivity()).thenReturn(true);
when(customMockHost.shouldDestroyEngineWithHost()).thenReturn(true);
// Create the real object that we're testing.
FlutterActivityAndFragmentDelegate delegate =
new FlutterActivityAndFragmentDelegate(customMockHost);
// Create the real object that we're testing.
FlutterActivityAndFragmentDelegate delegate =
new FlutterActivityAndFragmentDelegate(customMockHost);
// --- Execute the behavior under test ---
delegate.onAttach(ctx);
delegate.onCreateView(null, null, null, 0, false);
// --- Execute the behavior under test ---
delegate.onAttach(ctx);
delegate.onCreateView(null, null, null, 0, false);
// Verify that the host was asked to configure a FlutterTextureView.
verify(customMockHost, times(1)).onFlutterTextureViewCreated(isNotNull());
// Verify that the host was asked to configure a FlutterTextureView.
verify(customMockHost, times(1)).onFlutterTextureViewCreated(isNotNull());
});
}
}
@Test
@ -467,14 +498,20 @@ public class FlutterActivityAndFragmentDelegateTest {
// ---- Test setup ----
// Create the real object that we're testing.
FlutterActivityAndFragmentDelegate delegate = new FlutterActivityAndFragmentDelegate(mockHost);
try (ActivityScenario<Activity> scenario = ActivityScenario.launch(Activity.class)) {
scenario.onActivity(
activity -> {
when(mockHost.getActivity()).thenReturn(activity);
// --- Execute the behavior under test ---
// The FlutterEngine is created in onAttach().
delegate.onAttach(ctx);
delegate.onDetach();
// --- Execute the behavior under test ---
// The FlutterEngine is created in onAttach().
delegate.onAttach(ctx);
delegate.onDetach();
// Verify that the host was asked to configure our FlutterEngine.
verify(mockHost, times(1)).cleanUpFlutterEngine(mockFlutterEngine);
// Verify that the host was asked to configure our FlutterEngine.
verify(mockHost, times(1)).cleanUpFlutterEngine(mockFlutterEngine);
});
}
}
@Test
@ -608,20 +645,26 @@ public class FlutterActivityAndFragmentDelegateTest {
// Create the real object that we're testing.
FlutterActivityAndFragmentDelegate delegate = new FlutterActivityAndFragmentDelegate(mockHost);
try (ActivityScenario<Activity> scenario = ActivityScenario.launch(Activity.class)) {
scenario.onActivity(
activity -> {
when(mockHost.getActivity()).thenReturn(activity);
// --- Execute the behavior under test ---
// Flutter is attached to the surrounding Activity in onAttach.
delegate.onAttach(ctx);
// --- Execute the behavior under test ---
// Flutter is attached to the surrounding Activity in onAttach.
delegate.onAttach(ctx);
// Verify that the ActivityControlSurface was told to attach to an Activity.
verify(mockFlutterEngine.getActivityControlSurface(), times(1))
.attachToActivity(any(ExclusiveAppComponent.class), any(Lifecycle.class));
// Verify that the ActivityControlSurface was told to attach to an Activity.
verify(mockFlutterEngine.getActivityControlSurface(), times(1))
.attachToActivity(any(ExclusiveAppComponent.class), any(Lifecycle.class));
// Flutter is detached from the surrounding Activity in onDetach.
delegate.onDetach();
// Flutter is detached from the surrounding Activity in onDetach.
delegate.onDetach();
// Verify that the ActivityControlSurface was told to detach from the Activity.
verify(mockFlutterEngine.getActivityControlSurface(), times(1)).detachFromActivity();
// Verify that the ActivityControlSurface was told to detach from the Activity.
verify(mockFlutterEngine.getActivityControlSurface(), times(1)).detachFromActivity();
});
}
}
// "Attaching" to the surrounding Activity refers to Flutter being able to control
@ -1114,20 +1157,26 @@ public class FlutterActivityAndFragmentDelegateTest {
// Create the real object that we're testing.
FlutterActivityAndFragmentDelegate delegate = new FlutterActivityAndFragmentDelegate(mockHost);
try (ActivityScenario<Activity> scenario = ActivityScenario.launch(Activity.class)) {
scenario.onActivity(
activity -> {
when(mockHost.getActivity()).thenReturn(activity);
// --- Execute the behavior under test ---
// Push the delegate through all lifecycle methods all the way to destruction.
delegate.onAttach(ctx);
delegate.onCreateView(null, null, null, 0, true);
delegate.onStart();
delegate.onResume();
delegate.onPause();
delegate.onStop();
delegate.onDestroyView();
delegate.onDetach();
// --- Execute the behavior under test ---
// Push the delegate through all lifecycle methods all the way to destruction.
delegate.onAttach(ctx);
delegate.onCreateView(null, null, null, 0, true);
delegate.onStart();
delegate.onResume();
delegate.onPause();
delegate.onStop();
delegate.onDestroyView();
delegate.onDetach();
// --- Verify that the cached engine was destroyed ---
verify(mockFlutterEngine, times(1)).destroy();
// --- Verify that the cached engine was destroyed ---
verify(mockFlutterEngine, times(1)).destroy();
});
}
}
@Test
@ -1138,20 +1187,26 @@ public class FlutterActivityAndFragmentDelegateTest {
// Create the real object that we're testing.
FlutterActivityAndFragmentDelegate delegate = new FlutterActivityAndFragmentDelegate(mockHost);
try (ActivityScenario<Activity> scenario = ActivityScenario.launch(Activity.class)) {
scenario.onActivity(
activity -> {
when(mockHost.getActivity()).thenReturn(activity);
// --- Execute the behavior under test ---
// Push the delegate through all lifecycle methods all the way to destruction.
delegate.onAttach(ctx);
delegate.onCreateView(null, null, null, 0, true);
delegate.onStart();
delegate.onResume();
delegate.onPause();
delegate.onStop();
delegate.onDestroyView();
delegate.onDetach();
// --- Execute the behavior under test ---
// Push the delegate through all lifecycle methods all the way to destruction.
delegate.onAttach(ctx);
delegate.onCreateView(null, null, null, 0, true);
delegate.onStart();
delegate.onResume();
delegate.onPause();
delegate.onStop();
delegate.onDestroyView();
delegate.onDetach();
// --- Verify that the cached engine was destroyed ---
verify(mockFlutterEngine, never()).destroy();
// --- Verify that the cached engine was destroyed ---
verify(mockFlutterEngine, never()).destroy();
});
}
}
@Test
@ -1169,21 +1224,27 @@ public class FlutterActivityAndFragmentDelegateTest {
// Create the real object that we're testing.
FlutterActivityAndFragmentDelegate delegate = new FlutterActivityAndFragmentDelegate(mockHost);
try (ActivityScenario<Activity> scenario = ActivityScenario.launch(Activity.class)) {
scenario.onActivity(
activity -> {
when(mockHost.getActivity()).thenReturn(activity);
// --- Execute the behavior under test ---
// Push the delegate through all lifecycle methods all the way to destruction.
delegate.onAttach(ctx);
delegate.onCreateView(null, null, null, 0, true);
delegate.onStart();
delegate.onResume();
delegate.onPause();
delegate.onStop();
delegate.onDestroyView();
delegate.onDetach();
// --- Execute the behavior under test ---
// Push the delegate through all lifecycle methods all the way to destruction.
delegate.onAttach(ctx);
delegate.onCreateView(null, null, null, 0, true);
delegate.onStart();
delegate.onResume();
delegate.onPause();
delegate.onStop();
delegate.onDestroyView();
delegate.onDetach();
// --- Verify that the cached engine was destroyed ---
verify(cachedEngine, times(1)).destroy();
assertNull(FlutterEngineCache.getInstance().get("my_flutter_engine"));
// --- Verify that the cached engine was destroyed ---
verify(cachedEngine, times(1)).destroy();
assertNull(FlutterEngineCache.getInstance().get("my_flutter_engine"));
});
}
}
@Test
@ -1201,20 +1262,26 @@ public class FlutterActivityAndFragmentDelegateTest {
// Create the real object that we're testing.
FlutterActivityAndFragmentDelegate delegate = new FlutterActivityAndFragmentDelegate(mockHost);
try (ActivityScenario<Activity> scenario = ActivityScenario.launch(Activity.class)) {
scenario.onActivity(
activity -> {
when(mockHost.getActivity()).thenReturn(activity);
// --- Execute the behavior under test ---
// Push the delegate through all lifecycle methods all the way to destruction.
delegate.onAttach(ctx);
delegate.onCreateView(null, null, null, 0, true);
delegate.onStart();
delegate.onResume();
delegate.onPause();
delegate.onStop();
delegate.onDestroyView();
delegate.onDetach();
// --- Execute the behavior under test ---
// Push the delegate through all lifecycle methods all the way to destruction.
delegate.onAttach(ctx);
delegate.onCreateView(null, null, null, 0, true);
delegate.onStart();
delegate.onResume();
delegate.onPause();
delegate.onStop();
delegate.onDestroyView();
delegate.onDetach();
// --- Verify that the cached engine was NOT destroyed ---
verify(cachedEngine, never()).destroy();
// --- Verify that the cached engine was NOT destroyed ---
verify(cachedEngine, never()).destroy();
});
}
}
@Test
@ -1385,55 +1452,75 @@ public class FlutterActivityAndFragmentDelegateTest {
// Create the real objects that we're testing.
FlutterActivityAndFragmentDelegate delegate = new FlutterActivityAndFragmentDelegate(mockHost);
FlutterActivityAndFragmentDelegate delegate2 =
new FlutterActivityAndFragmentDelegate(mockHost2);
try (ActivityScenario<Activity> scenario = ActivityScenario.launch(Activity.class)) {
scenario.onActivity(
activity -> {
when(mockHost.getActivity()).thenReturn(activity);
// This test is written to recreate the following scenario:
// 1. We have a FlutterFragment_A attached to a singleton cached engine.
// 2. An intent arrives that spawns FlutterFragment_B.
// 3. FlutterFragment_B starts and steals the engine from FlutterFragment_A while attaching.
// Via a call to FlutterActivityAndFragmentDelegate.detachFromFlutterEngine().
// 4. FlutterFragment_A is forcibly detached from the engine.
// 5. FlutterFragment_B is attached to the engine.
// 6. FlutterFragment_A is detached from the engine.
// Note that the second detach for FlutterFragment_A is done unconditionally when the Fragment
// is being
// torn down.
FlutterActivityAndFragmentDelegate delegate2 =
new FlutterActivityAndFragmentDelegate(mockHost2);
try (ActivityScenario<Activity> scenario2 = ActivityScenario.launch(Activity.class)) {
scenario2.onActivity(
activity2 -> {
when(mockHost2.getActivity()).thenReturn(activity2);
// At this point the engine's life cycle channel receives a message (triggered by
// FlutterFragment_A's second detach)
// that indicates the app is detached. This breaks FlutterFragment_B.
// This test is written to recreate the following scenario:
// 1. We have a FlutterFragment_A attached to a singleton cached engine.
// 2. An intent arrives that spawns FlutterFragment_B.
// 3. FlutterFragment_B starts and steals the engine from FlutterFragment_A
// while attaching.
// Via a call to
// FlutterActivityAndFragmentDelegate.detachFromFlutterEngine().
// 4. FlutterFragment_A is forcibly detached from the engine.
// 5. FlutterFragment_B is attached to the engine.
// 6. FlutterFragment_A is detached from the engine.
// Note that the second detach for FlutterFragment_A is done unconditionally
// when the Fragment
// is being
// torn down.
// Below is a sequence of calls that mimicks the calls that the above scenario would trigger
// without
// relying on an intent to trigger the behaviour.
// At this point the engine's life cycle channel receives a message (triggered
// by
// FlutterFragment_A's second detach)
// that indicates the app is detached. This breaks FlutterFragment_B.
// FlutterFragment_A is attached to the engine.
delegate.onAttach(ctx);
// Below is a sequence of calls that mimicks the calls that the above scenario
// would trigger
// without
// relying on an intent to trigger the behaviour.
// NOTE: The following two calls happen in a slightly different order in reality. That is, via,
// a call to host.detachFromFlutterEngine, delegate2.onAttach ends up invoking
// delegate.onDetach.
// To keep this regression test simple, we call them directly.
// FlutterFragment_A is attached to the engine.
delegate.onAttach(ctx);
// Detach FlutterFragment_A.
delegate.onDetach();
// NOTE: The following two calls happen in a slightly different order in
// reality. That is, via,
// a call to host.detachFromFlutterEngine, delegate2.onAttach ends up invoking
// delegate.onDetach.
// To keep this regression test simple, we call them directly.
verify(cachedEngine.getLifecycleChannel(), times(1)).appIsDetached();
// Detach FlutterFragment_A.
delegate.onDetach();
// Attaches to the engine FlutterFragment_B.
delegate2.onAttach(ctx);
delegate2.onResume();
verify(cachedEngine.getLifecycleChannel(), times(1)).appIsDetached();
verify(cachedEngine.getLifecycleChannel(), times(1)).appIsResumed();
verify(cachedEngine.getLifecycleChannel(), times(1)).appIsDetached();
// Attaches to the engine FlutterFragment_B.
delegate2.onAttach(ctx);
delegate2.onResume();
// A second Detach of FlutterFragment_A happens when the Fragment is detached.
delegate.onDetach();
verify(cachedEngine.getLifecycleChannel(), times(1)).appIsResumed();
verify(cachedEngine.getLifecycleChannel(), times(1)).appIsDetached();
// IMPORTANT: The bug we fixed would have resulted in the engine thinking the app
// is detached twice instead of once.
verify(cachedEngine.getLifecycleChannel(), times(1)).appIsDetached();
// A second Detach of FlutterFragment_A happens when the Fragment is detached.
delegate.onDetach();
// IMPORTANT: The bug we fixed would have resulted in the engine thinking the
// app
// is detached twice instead of once.
verify(cachedEngine.getLifecycleChannel(), times(1)).appIsDetached();
});
}
});
}
}
/**

View File

@ -25,6 +25,7 @@ import android.os.Bundle;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.lifecycle.Lifecycle;
import androidx.test.core.app.ActivityScenario;
import androidx.test.core.app.ApplicationProvider;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import io.flutter.embedding.engine.FlutterEngine;
@ -66,43 +67,49 @@ public class FlutterAndroidComponentTest {
cachedEngine.getPlugins().add(mockPlugin);
// Create a fake Host, which is required by the delegate.
FakeHost fakeHost = new FakeHost(cachedEngine);
fakeHost.shouldDestroyEngineWithHost = true;
try (ActivityScenario<Activity> scenario = ActivityScenario.launch(Activity.class)) {
scenario.onActivity(
activity -> {
FakeHost fakeHost = new FakeHost(cachedEngine, activity);
fakeHost.shouldDestroyEngineWithHost = true;
// Create the real object that we're testing.
FlutterActivityAndFragmentDelegate delegate = new FlutterActivityAndFragmentDelegate(fakeHost);
// Create the real object that we're testing.
FlutterActivityAndFragmentDelegate delegate =
new FlutterActivityAndFragmentDelegate(fakeHost);
// --- Execute the behavior under test ---
// Push the delegate through all lifecycle methods all the way to destruction.
delegate.onAttach(ctx);
// --- Execute the behavior under test ---
// Push the delegate through all lifecycle methods all the way to destruction.
delegate.onAttach(ctx);
// Verify that the plugin is attached to the FlutterEngine.
ArgumentCaptor<FlutterPlugin.FlutterPluginBinding> pluginBindingCaptor =
ArgumentCaptor.forClass(FlutterPlugin.FlutterPluginBinding.class);
verify(mockPlugin, times(1)).onAttachedToEngine(pluginBindingCaptor.capture());
FlutterPlugin.FlutterPluginBinding binding = pluginBindingCaptor.getValue();
assertNotNull(binding.getApplicationContext());
assertNotNull(binding.getBinaryMessenger());
assertNotNull(binding.getTextureRegistry());
assertNotNull(binding.getPlatformViewRegistry());
// Verify that the plugin is attached to the FlutterEngine.
ArgumentCaptor<FlutterPlugin.FlutterPluginBinding> pluginBindingCaptor =
ArgumentCaptor.forClass(FlutterPlugin.FlutterPluginBinding.class);
verify(mockPlugin, times(1)).onAttachedToEngine(pluginBindingCaptor.capture());
FlutterPlugin.FlutterPluginBinding binding = pluginBindingCaptor.getValue();
assertNotNull(binding.getApplicationContext());
assertNotNull(binding.getBinaryMessenger());
assertNotNull(binding.getTextureRegistry());
assertNotNull(binding.getPlatformViewRegistry());
delegate.onRestoreInstanceState(null);
delegate.onCreateView(null, null, null, 0, true);
delegate.onStart();
delegate.onResume();
delegate.onPause();
delegate.onStop();
delegate.onDestroyView();
delegate.onDetach();
delegate.onRestoreInstanceState(null);
delegate.onCreateView(null, null, null, 0, true);
delegate.onStart();
delegate.onResume();
delegate.onPause();
delegate.onStop();
delegate.onDestroyView();
delegate.onDetach();
// Verify the plugin was detached from the FlutterEngine.
pluginBindingCaptor = ArgumentCaptor.forClass(FlutterPlugin.FlutterPluginBinding.class);
verify(mockPlugin, times(1)).onDetachedFromEngine(pluginBindingCaptor.capture());
binding = pluginBindingCaptor.getValue();
assertNotNull(binding.getApplicationContext());
assertNotNull(binding.getBinaryMessenger());
assertNotNull(binding.getTextureRegistry());
assertNotNull(binding.getPlatformViewRegistry());
// Verify the plugin was detached from the FlutterEngine.
pluginBindingCaptor = ArgumentCaptor.forClass(FlutterPlugin.FlutterPluginBinding.class);
verify(mockPlugin, times(1)).onDetachedFromEngine(pluginBindingCaptor.capture());
binding = pluginBindingCaptor.getValue();
assertNotNull(binding.getApplicationContext());
assertNotNull(binding.getBinaryMessenger());
assertNotNull(binding.getTextureRegistry());
assertNotNull(binding.getPlatformViewRegistry());
});
}
}
@Test
@ -139,42 +146,49 @@ public class FlutterAndroidComponentTest {
cachedEngine.getPlugins().add(mockPlugin);
// Create a fake Host, which is required by the delegate.
FlutterActivityAndFragmentDelegate.Host fakeHost = new FakeHost(cachedEngine);
try (ActivityScenario<Activity> scenario = ActivityScenario.launch(Activity.class)) {
scenario.onActivity(
activity -> {
FlutterActivityAndFragmentDelegate.Host fakeHost = new FakeHost(cachedEngine, activity);
FlutterActivityAndFragmentDelegate delegate = new FlutterActivityAndFragmentDelegate(fakeHost);
FlutterActivityAndFragmentDelegate delegate =
new FlutterActivityAndFragmentDelegate(fakeHost);
// --- Execute the behavior under test ---
// Push the delegate through all lifecycle methods all the way to destruction.
delegate.onAttach(ctx);
// --- Execute the behavior under test ---
// Push the delegate through all lifecycle methods all the way to destruction.
delegate.onAttach(ctx);
// Verify plugin was given an ActivityPluginBinding.
ArgumentCaptor<ActivityPluginBinding> pluginBindingCaptor =
ArgumentCaptor.forClass(ActivityPluginBinding.class);
verify(activityAwarePlugin, times(1)).onAttachedToActivity(pluginBindingCaptor.capture());
ActivityPluginBinding binding = pluginBindingCaptor.getValue();
assertNotNull(binding.getActivity());
assertNotNull(binding.getLifecycle());
// Verify plugin was given an ActivityPluginBinding.
ArgumentCaptor<ActivityPluginBinding> pluginBindingCaptor =
ArgumentCaptor.forClass(ActivityPluginBinding.class);
verify(activityAwarePlugin, times(1))
.onAttachedToActivity(pluginBindingCaptor.capture());
ActivityPluginBinding binding = pluginBindingCaptor.getValue();
assertNotNull(binding.getActivity());
assertNotNull(binding.getLifecycle());
delegate.onRestoreInstanceState(null);
delegate.onRestoreInstanceState(null);
// Verify that after Activity creation, the plugin was allowed to restore state.
verify(mockSaveStateListener, times(1)).onRestoreInstanceState(isNull());
// Verify that after Activity creation, the plugin was allowed to restore state.
verify(mockSaveStateListener, times(1)).onRestoreInstanceState(isNull());
delegate.onCreateView(null, null, null, 0, true);
delegate.onStart();
delegate.onResume();
delegate.onPause();
delegate.onStop();
delegate.onSaveInstanceState(mock(Bundle.class));
delegate.onCreateView(null, null, null, 0, true);
delegate.onStart();
delegate.onResume();
delegate.onPause();
delegate.onStop();
delegate.onSaveInstanceState(mock(Bundle.class));
// Verify that the plugin was allowed to save state.
verify(mockSaveStateListener, times(1)).onSaveInstanceState(any(Bundle.class));
// Verify that the plugin was allowed to save state.
verify(mockSaveStateListener, times(1)).onSaveInstanceState(any(Bundle.class));
delegate.onDestroyView();
delegate.onDetach();
delegate.onDestroyView();
delegate.onDetach();
// Verify that the plugin was detached from the Activity.
verify(activityAwarePlugin, times(1)).onDetachedFromActivity();
// Verify that the plugin was detached from the Activity.
verify(activityAwarePlugin, times(1)).onDetachedFromActivity();
});
}
}
@Test
@ -188,25 +202,30 @@ public class FlutterAndroidComponentTest {
FlutterEngineCache.getInstance().put("my_flutter_engine", cachedEngine);
// Create a fake Host, which is required by the delegate.
FakeHost fakeHost = new FakeHost(cachedEngine);
try (ActivityScenario<Activity> scenario = ActivityScenario.launch(Activity.class)) {
scenario.onActivity(
activity -> {
FakeHost fakeHost = new FakeHost(cachedEngine, activity);
// Create the real object that we're testing.
FlutterActivityAndFragmentDelegate delegate =
spy(new FlutterActivityAndFragmentDelegate(fakeHost));
// Create the real object that we're testing.
FlutterActivityAndFragmentDelegate delegate =
spy(new FlutterActivityAndFragmentDelegate(fakeHost));
// --- Execute the behavior under test ---
// Push the delegate through all lifecycle methods all the way to destruction.
delegate.onAttach(ctx);
delegate.onRestoreInstanceState(null);
delegate.onCreateView(null, null, null, 0, true);
delegate.onStart();
delegate.onResume();
delegate.onPause();
delegate.onStop();
delegate.onDestroyView();
delegate.onDetach();
// --- Execute the behavior under test ---
// Push the delegate through all lifecycle methods all the way to destruction.
delegate.onAttach(ctx);
delegate.onRestoreInstanceState(null);
delegate.onCreateView(null, null, null, 0, true);
delegate.onStart();
delegate.onResume();
delegate.onPause();
delegate.onStop();
delegate.onDestroyView();
delegate.onDetach();
verify(delegate, never()).detachFromFlutterEngine();
verify(delegate, never()).detachFromFlutterEngine();
});
}
}
@Test
@ -253,12 +272,13 @@ public class FlutterAndroidComponentTest {
private static class FakeHost implements FlutterActivityAndFragmentDelegate.Host {
final FlutterEngine cachedEngine;
Activity activity;
Activity cachedActivity;
boolean shouldDestroyEngineWithHost = false;
Lifecycle lifecycle = mock(Lifecycle.class);
private FakeHost(@NonNull FlutterEngine flutterEngine) {
private FakeHost(@NonNull FlutterEngine flutterEngine, @NonNull Activity activity) {
cachedEngine = flutterEngine;
cachedActivity = activity;
}
@NonNull
@ -267,16 +287,10 @@ public class FlutterAndroidComponentTest {
return ApplicationProvider.getApplicationContext();
}
@SuppressWarnings("deprecation")
// Robolectric.setupActivity
// TODO(reidbaker): https://github.com/flutter/flutter/issues/133151
@Nullable
@NonNull
@Override
public Activity getActivity() {
if (activity == null) {
activity = Robolectric.setupActivity(Activity.class);
}
return activity;
return cachedActivity;
}
@NonNull

View File

@ -22,6 +22,7 @@ import static org.mockito.Mockito.when;
import android.content.Context;
import androidx.activity.OnBackPressedCallback;
import androidx.fragment.app.FragmentActivity;
import androidx.test.core.app.ActivityScenario;
import androidx.test.core.app.ApplicationProvider;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import io.flutter.embedding.engine.FlutterEngine;
@ -33,7 +34,6 @@ import java.util.Arrays;
import java.util.concurrent.atomic.AtomicBoolean;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.robolectric.Robolectric;
@RunWith(AndroidJUnit4.class)
public class FlutterFragmentTest {
@ -285,12 +285,6 @@ public class FlutterFragmentTest {
assertEquals(fragment.getExclusiveAppComponent(), delegate);
}
@SuppressWarnings("deprecation")
private FragmentActivity getMockFragmentActivity() {
// TODO(reidbaker): https://github.com/flutter/flutter/issues/133151
return Robolectric.setupActivity(FragmentActivity.class);
}
@Test
public void itDelegatesOnBackPressedWithSetFrameworkHandlesBack() {
// We need to mock FlutterJNI to avoid triggering native code.
@ -307,36 +301,39 @@ public class FlutterFragmentTest {
// sends backs to the framework if setFrameworkHandlesBack is true.
.shouldAutomaticallyHandleOnBackPressed(true)
.build();
FragmentActivity activity = getMockFragmentActivity();
activity
.getSupportFragmentManager()
.beginTransaction()
.add(android.R.id.content, fragment)
.commitNow();
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);
fragment.setDelegateFactory(delegateFactory);
try (ActivityScenario<FragmentActivity> scenario =
ActivityScenario.launch(FragmentActivity.class)) {
scenario.onActivity(
activity -> {
activity
.getSupportFragmentManager()
.beginTransaction()
.add(android.R.id.content, fragment)
.commitNow();
// Calling onBackPressed now will still be handled by Android (the default),
// until setFrameworkHandlesBack is set to true.
activity.getOnBackPressedDispatcher().onBackPressed();
verify(mockDelegate, times(0)).onBackPressed();
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);
fragment.setDelegateFactory(delegateFactory);
// Setting setFrameworkHandlesBack to true means the delegate will receive
// the back and Android won't handle it.
fragment.setFrameworkHandlesBack(true);
activity.getOnBackPressedDispatcher().onBackPressed();
verify(mockDelegate, times(1)).onBackPressed();
// Calling onBackPressed now will still be handled by Android (the default),
// until setFrameworkHandlesBack is set to true.
activity.getOnBackPressedDispatcher().onBackPressed();
verify(mockDelegate, times(0)).onBackPressed();
// Setting setFrameworkHandlesBack to true means the delegate will receive
// the back and Android won't handle it.
fragment.setFrameworkHandlesBack(true);
activity.getOnBackPressedDispatcher().onBackPressed();
verify(mockDelegate, times(1)).onBackPressed();
});
}
}
@SuppressWarnings("deprecation")
// Robolectric.setupActivity
// TODO(reidbaker): https://github.com/flutter/flutter/issues/133151
@Test
public void itHandlesPopSystemNavigationAutomaticallyWhenEnabled() {
// We need to mock FlutterJNI to avoid triggering native code.
@ -351,41 +348,47 @@ public class FlutterFragmentTest {
FlutterFragment.withCachedEngine("my_cached_engine")
.shouldAutomaticallyHandleOnBackPressed(true)
.build();
FragmentActivity activity = getMockFragmentActivity();
activity
.getSupportFragmentManager()
.beginTransaction()
.add(android.R.id.content, fragment)
.commitNow();
final AtomicBoolean onBackPressedCalled = new AtomicBoolean(false);
OnBackPressedCallback callback =
new OnBackPressedCallback(true) {
@Override
public void handleOnBackPressed() {
onBackPressedCalled.set(true);
}
};
activity.getOnBackPressedDispatcher().addCallback(callback);
FlutterActivityAndFragmentDelegate mockDelegate =
mock(FlutterActivityAndFragmentDelegate.class);
TestDelegateFactory delegateFactory = new TestDelegateFactory(mockDelegate);
fragment.setDelegateFactory(delegateFactory);
try (ActivityScenario<FragmentActivity> scenario =
ActivityScenario.launch(FragmentActivity.class)) {
scenario.onActivity(
activity -> {
activity
.getSupportFragmentManager()
.beginTransaction()
.add(android.R.id.content, fragment)
.commitNow();
final AtomicBoolean onBackPressedCalled = new AtomicBoolean(false);
OnBackPressedCallback callback =
new OnBackPressedCallback(true) {
@Override
public void handleOnBackPressed() {
onBackPressedCalled.set(true);
}
};
activity.getOnBackPressedDispatcher().addCallback(callback);
assertTrue(callback.isEnabled());
FlutterActivityAndFragmentDelegate mockDelegate =
mock(FlutterActivityAndFragmentDelegate.class);
TestDelegateFactory delegateFactory = new TestDelegateFactory(mockDelegate);
fragment.setDelegateFactory(delegateFactory);
assertTrue(fragment.popSystemNavigator());
assertTrue(callback.isEnabled());
verify(mockDelegate, never()).onBackPressed();
assertTrue(onBackPressedCalled.get());
assertTrue(callback.isEnabled());
assertTrue(fragment.popSystemNavigator());
callback.setEnabled(false);
assertFalse(callback.isEnabled());
assertTrue(fragment.popSystemNavigator());
verify(mockDelegate, never()).onBackPressed();
assertTrue(onBackPressedCalled.get());
assertTrue(callback.isEnabled());
verify(mockDelegate, never()).onBackPressed();
assertFalse(callback.isEnabled());
callback.setEnabled(false);
assertFalse(callback.isEnabled());
assertTrue(fragment.popSystemNavigator());
verify(mockDelegate, never()).onBackPressed();
assertFalse(callback.isEnabled());
});
}
}
@Test

View File

@ -42,6 +42,7 @@ import android.view.View;
import android.view.WindowInsets;
import android.widget.FrameLayout;
import androidx.core.util.Consumer;
import androidx.test.core.app.ActivityScenario;
import androidx.test.core.app.ApplicationProvider;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.window.layout.FoldingFeature;
@ -68,7 +69,6 @@ import org.mockito.MockitoAnnotations;
import org.mockito.Spy;
import org.mockito.invocation.InvocationOnMock;
import org.mockito.stubbing.Answer;
import org.robolectric.Robolectric;
import org.robolectric.Shadows;
import org.robolectric.annotation.Config;
import org.robolectric.annotation.Implementation;
@ -248,16 +248,22 @@ public class FlutterViewTest {
// TODO(reidbaker): https://github.com/flutter/flutter/issues/133151
@Test
public void onConfigurationChanged_notifiesEngineOfDisplaySize() {
FlutterView flutterView = new FlutterView(Robolectric.setupActivity(Activity.class));
FlutterEngine flutterEngine = spy(new FlutterEngine(ctx, mockFlutterLoader, mockFlutterJni));
try (ActivityScenario<Activity> scenario = ActivityScenario.launch(Activity.class)) {
scenario.onActivity(
activity -> {
FlutterView flutterView = new FlutterView(activity);
FlutterEngine flutterEngine =
spy(new FlutterEngine(ctx, mockFlutterLoader, mockFlutterJni));
Configuration configuration = ctx.getResources().getConfiguration();
Configuration configuration = ctx.getResources().getConfiguration();
flutterView.attachToFlutterEngine(flutterEngine);
flutterView.onConfigurationChanged(configuration);
flutterView.attachToFlutterEngine(flutterEngine);
flutterView.onConfigurationChanged(configuration);
verify(flutterEngine, times(1))
.updateDisplayMetrics(any(Float.class), any(Float.class), any(Float.class));
verify(flutterEngine, times(1))
.updateDisplayMetrics(any(Float.class), any(Float.class), any(Float.class));
});
}
}
public void itSendsTextHidePasswordToFrameworkOnAttach() {
@ -743,179 +749,193 @@ public class FlutterViewTest {
@Config(minSdk = 28)
public void onApplyWindowInsetsSetsDisplayCutouts() {
// Use an Activity context so that FlutterView.onAttachedToWindow completes.
Context context = Robolectric.setupActivity(Activity.class);
FlutterView flutterView = spy(new FlutterView(context));
assertEquals(0, flutterView.getSystemUiVisibility());
when(flutterView.getWindowSystemUiVisibility()).thenReturn(0);
when(flutterView.getContext()).thenReturn(context);
try (ActivityScenario<Activity> scenario = ActivityScenario.launch(Activity.class)) {
scenario.onActivity(
activity -> {
FlutterView flutterView = spy(new FlutterView(activity));
assertEquals(0, flutterView.getSystemUiVisibility());
when(flutterView.getWindowSystemUiVisibility()).thenReturn(0);
when(flutterView.getContext()).thenReturn(activity);
FlutterEngine flutterEngine = spy(new FlutterEngine(ctx, mockFlutterLoader, mockFlutterJni));
FlutterRenderer flutterRenderer = spy(new FlutterRenderer(mockFlutterJni));
when(flutterEngine.getRenderer()).thenReturn(flutterRenderer);
FlutterEngine flutterEngine =
spy(new FlutterEngine(ctx, mockFlutterLoader, mockFlutterJni));
FlutterRenderer flutterRenderer = spy(new FlutterRenderer(mockFlutterJni));
when(flutterEngine.getRenderer()).thenReturn(flutterRenderer);
// When we attach a new FlutterView to the engine without any system insets,
// the viewport metrics default to 0.
flutterView.attachToFlutterEngine(flutterEngine);
ArgumentCaptor<FlutterRenderer.ViewportMetrics> viewportMetricsCaptor =
ArgumentCaptor.forClass(FlutterRenderer.ViewportMetrics.class);
verify(flutterRenderer).setViewportMetrics(viewportMetricsCaptor.capture());
assertEquals(0, viewportMetricsCaptor.getValue().viewPaddingTop);
// When we attach a new FlutterView to the engine without any system insets,
// the viewport metrics default to 0.
flutterView.attachToFlutterEngine(flutterEngine);
ArgumentCaptor<FlutterRenderer.ViewportMetrics> viewportMetricsCaptor =
ArgumentCaptor.forClass(FlutterRenderer.ViewportMetrics.class);
verify(flutterRenderer).setViewportMetrics(viewportMetricsCaptor.capture());
assertEquals(0, viewportMetricsCaptor.getValue().viewPaddingTop);
// Capture flutterView.setWindowInfoListenerDisplayFeatures.
WindowInfoRepositoryCallbackAdapterWrapper windowInfoRepo =
mock(WindowInfoRepositoryCallbackAdapterWrapper.class);
doReturn(windowInfoRepo).when(flutterView).createWindowInfoRepo();
ArgumentCaptor<Consumer<WindowLayoutInfo>> consumerCaptor =
ArgumentCaptor.forClass(Consumer.class);
flutterView.onAttachedToWindow();
verify(windowInfoRepo).addWindowLayoutInfoListener(any(), any(), consumerCaptor.capture());
Consumer<WindowLayoutInfo> consumer = consumerCaptor.getValue();
// Capture flutterView.setWindowInfoListenerDisplayFeatures.
WindowInfoRepositoryCallbackAdapterWrapper windowInfoRepo =
mock(WindowInfoRepositoryCallbackAdapterWrapper.class);
doReturn(windowInfoRepo).when(flutterView).createWindowInfoRepo();
ArgumentCaptor<Consumer<WindowLayoutInfo>> consumerCaptor =
ArgumentCaptor.forClass(Consumer.class);
flutterView.onAttachedToWindow();
verify(windowInfoRepo)
.addWindowLayoutInfoListener(any(), any(), consumerCaptor.capture());
Consumer<WindowLayoutInfo> consumer = consumerCaptor.getValue();
// Set display features in flutterView to ensure they are not overridden by display cutouts.
FoldingFeature displayFeature = mock(FoldingFeature.class);
Rect featureBounds = new Rect(10, 20, 30, 40);
when(displayFeature.getBounds()).thenReturn(featureBounds);
when(displayFeature.getOcclusionType()).thenReturn(FoldingFeature.OcclusionType.FULL);
when(displayFeature.getState()).thenReturn(FoldingFeature.State.FLAT);
WindowLayoutInfo windowLayout = new WindowLayoutInfo(Collections.singletonList(displayFeature));
clearInvocations(flutterRenderer);
consumer.accept(windowLayout);
// Set display features in flutterView to ensure they are not overridden by display
// cutouts.
FoldingFeature displayFeature = mock(FoldingFeature.class);
Rect featureBounds = new Rect(10, 20, 30, 40);
when(displayFeature.getBounds()).thenReturn(featureBounds);
when(displayFeature.getOcclusionType()).thenReturn(FoldingFeature.OcclusionType.FULL);
when(displayFeature.getState()).thenReturn(FoldingFeature.State.FLAT);
WindowLayoutInfo windowLayout =
new WindowLayoutInfo(Collections.singletonList(displayFeature));
clearInvocations(flutterRenderer);
consumer.accept(windowLayout);
// Assert the display feature is set.
verify(flutterRenderer).setViewportMetrics(viewportMetricsCaptor.capture());
List<FlutterRenderer.DisplayFeature> features =
viewportMetricsCaptor.getValue().getDisplayFeatures();
assertEquals(1, features.size());
assertEquals(FlutterRenderer.DisplayFeatureType.HINGE, features.get(0).type);
assertEquals(FlutterRenderer.DisplayFeatureState.POSTURE_FLAT, features.get(0).state);
assertEquals(featureBounds, features.get(0).bounds);
// Assert the display feature is set.
verify(flutterRenderer).setViewportMetrics(viewportMetricsCaptor.capture());
List<FlutterRenderer.DisplayFeature> features =
viewportMetricsCaptor.getValue().getDisplayFeatures();
assertEquals(1, features.size());
assertEquals(FlutterRenderer.DisplayFeatureType.HINGE, features.get(0).type);
assertEquals(FlutterRenderer.DisplayFeatureState.POSTURE_FLAT, features.get(0).state);
assertEquals(featureBounds, features.get(0).bounds);
// Then we simulate the system applying a window inset.
List<Rect> cutoutBoundingRects =
Arrays.asList(new Rect(0, 200, 300, 400), new Rect(150, 0, 300, 150));
WindowInsets windowInsets = setupMockDisplayCutout(cutoutBoundingRects);
// Then we simulate the system applying a window inset.
List<Rect> cutoutBoundingRects =
Arrays.asList(new Rect(0, 200, 300, 400), new Rect(150, 0, 300, 150));
WindowInsets windowInsets = setupMockDisplayCutout(cutoutBoundingRects);
clearInvocations(flutterRenderer);
flutterView.onApplyWindowInsets(windowInsets);
verify(flutterRenderer).setViewportMetrics(viewportMetricsCaptor.capture());
clearInvocations(flutterRenderer);
flutterView.onApplyWindowInsets(windowInsets);
verify(flutterRenderer).setViewportMetrics(viewportMetricsCaptor.capture());
features = viewportMetricsCaptor.getValue().getDisplayFeatures();
features = viewportMetricsCaptor.getValue().getDisplayFeatures();
// Assert the old display feature is still present.
assertEquals(1, features.size());
assertEquals(FlutterRenderer.DisplayFeatureType.HINGE, features.get(0).type);
assertEquals(FlutterRenderer.DisplayFeatureState.POSTURE_FLAT, features.get(0).state);
assertEquals(featureBounds, features.get(0).bounds);
// Assert the old display feature is still present.
assertEquals(1, features.size());
assertEquals(FlutterRenderer.DisplayFeatureType.HINGE, features.get(0).type);
assertEquals(FlutterRenderer.DisplayFeatureState.POSTURE_FLAT, features.get(0).state);
assertEquals(featureBounds, features.get(0).bounds);
List<FlutterRenderer.DisplayFeature> cutouts =
viewportMetricsCaptor.getValue().getDisplayCutouts();
// Asserts for display cutouts.
assertEquals(2, cutouts.size());
for (int i = 0; i < 2; i++) {
assertEquals(cutoutBoundingRects.get(i), cutouts.get(i).bounds);
assertEquals(FlutterRenderer.DisplayFeatureType.CUTOUT, cutouts.get(i).type);
assertEquals(FlutterRenderer.DisplayFeatureState.UNKNOWN, cutouts.get(i).state);
List<FlutterRenderer.DisplayFeature> cutouts =
viewportMetricsCaptor.getValue().getDisplayCutouts();
// Asserts for display cutouts.
assertEquals(2, cutouts.size());
for (int i = 0; i < 2; i++) {
assertEquals(cutoutBoundingRects.get(i), cutouts.get(i).bounds);
assertEquals(FlutterRenderer.DisplayFeatureType.CUTOUT, cutouts.get(i).type);
assertEquals(FlutterRenderer.DisplayFeatureState.UNKNOWN, cutouts.get(i).state);
}
});
}
}
@SuppressWarnings("deprecation")
// Robolectric.setupActivity
// TODO(reidbaker): https://github.com/flutter/flutter/issues/133151
// getDefaultDisplay
// TODO(jesswrd): https://github.com/flutter/flutter/issues/99421
@Test
public void itRegistersAndUnregistersToWindowManager() {
Context context = Robolectric.setupActivity(Activity.class);
FlutterView flutterView = spy(new FlutterView(context));
WindowInfoRepositoryCallbackAdapterWrapper windowInfoRepo =
mock(WindowInfoRepositoryCallbackAdapterWrapper.class);
// For reasoning behing using doReturn instead of when, read "Important gotcha" at
// https://www.javadoc.io/doc/org.mockito/mockito-core/1.10.19/org/mockito/Mockito.html#13
doReturn(windowInfoRepo).when(flutterView).createWindowInfoRepo();
try (ActivityScenario<Activity> scenario = ActivityScenario.launch(Activity.class)) {
scenario.onActivity(
activity -> {
FlutterView flutterView = spy(new FlutterView(activity));
WindowInfoRepositoryCallbackAdapterWrapper windowInfoRepo =
mock(WindowInfoRepositoryCallbackAdapterWrapper.class);
// For reasoning behing using doReturn instead of when, read "Important gotcha" at
// https://www.javadoc.io/doc/org.mockito/mockito-core/1.10.19/org/mockito/Mockito.html#13
doReturn(windowInfoRepo).when(flutterView).createWindowInfoRepo();
// When a new FlutterView is attached to the window
flutterView.onAttachedToWindow();
// When a new FlutterView is attached to the window
flutterView.onAttachedToWindow();
// Then the WindowManager callback is registered
verify(windowInfoRepo, times(1)).addWindowLayoutInfoListener(any(), any(), any());
// Then the WindowManager callback is registered
verify(windowInfoRepo, times(1)).addWindowLayoutInfoListener(any(), any(), any());
// When the FlutterView is detached from the window
flutterView.onDetachedFromWindow();
// When the FlutterView is detached from the window
flutterView.onDetachedFromWindow();
// Then the WindowManager callback is unregistered
verify(windowInfoRepo, times(1)).removeWindowLayoutInfoListener(any());
// Then the WindowManager callback is unregistered
verify(windowInfoRepo, times(1)).removeWindowLayoutInfoListener(any());
});
}
}
@SuppressWarnings("deprecation")
// Robolectric.setupActivity
// TODO(reidbaker): https://github.com/flutter/flutter/issues/133151
// getDefaultDisplay
// TODO(jesswrd): https://github.com/flutter/flutter/issues/99421
@Test
public void itSendsHingeDisplayFeatureToFlutter() {
Context context = Robolectric.setupActivity(Activity.class);
FlutterView flutterView = spy(new FlutterView(context));
when(flutterView.getContext()).thenReturn(context);
WindowInfoRepositoryCallbackAdapterWrapper windowInfoRepo =
mock(WindowInfoRepositoryCallbackAdapterWrapper.class);
doReturn(windowInfoRepo).when(flutterView).createWindowInfoRepo();
FlutterEngine flutterEngine = spy(new FlutterEngine(ctx, mockFlutterLoader, mockFlutterJni));
FlutterRenderer flutterRenderer = spy(new FlutterRenderer(mockFlutterJni));
when(flutterEngine.getRenderer()).thenReturn(flutterRenderer);
try (ActivityScenario<Activity> scenario = ActivityScenario.launch(Activity.class)) {
scenario.onActivity(
activity -> {
FlutterView flutterView = spy(new FlutterView(activity));
when(flutterView.getContext()).thenReturn(activity);
WindowInfoRepositoryCallbackAdapterWrapper windowInfoRepo =
mock(WindowInfoRepositoryCallbackAdapterWrapper.class);
doReturn(windowInfoRepo).when(flutterView).createWindowInfoRepo();
FlutterEngine flutterEngine =
spy(new FlutterEngine(ctx, mockFlutterLoader, mockFlutterJni));
FlutterRenderer flutterRenderer = spy(new FlutterRenderer(mockFlutterJni));
when(flutterEngine.getRenderer()).thenReturn(flutterRenderer);
// Display features should be empty on attaching to engine.
flutterView.attachToFlutterEngine(flutterEngine);
ArgumentCaptor<FlutterRenderer.ViewportMetrics> viewportMetricsCaptor =
ArgumentCaptor.forClass(FlutterRenderer.ViewportMetrics.class);
verify(flutterRenderer).setViewportMetrics(viewportMetricsCaptor.capture());
assertEquals(Collections.emptyList(), viewportMetricsCaptor.getValue().getDisplayFeatures());
clearInvocations(flutterRenderer);
// Display features should be empty on attaching to engine.
flutterView.attachToFlutterEngine(flutterEngine);
ArgumentCaptor<FlutterRenderer.ViewportMetrics> viewportMetricsCaptor =
ArgumentCaptor.forClass(FlutterRenderer.ViewportMetrics.class);
verify(flutterRenderer).setViewportMetrics(viewportMetricsCaptor.capture());
assertEquals(
Collections.emptyList(), viewportMetricsCaptor.getValue().getDisplayFeatures());
clearInvocations(flutterRenderer);
// Test that display features do not override cutouts.
List<Rect> cutoutBoundingRects = Collections.singletonList(new Rect(0, 200, 300, 400));
WindowInsets windowInsets = setupMockDisplayCutout(cutoutBoundingRects);
flutterView.onApplyWindowInsets(windowInsets);
verify(flutterRenderer).setViewportMetrics(viewportMetricsCaptor.capture());
assertEquals(1, viewportMetricsCaptor.getValue().getDisplayCutouts().size());
assertEquals(
cutoutBoundingRects.get(0),
viewportMetricsCaptor.getValue().getDisplayCutouts().get(0).bounds);
clearInvocations(flutterRenderer);
// Test that display features do not override cutouts.
List<Rect> cutoutBoundingRects = Collections.singletonList(new Rect(0, 200, 300, 400));
WindowInsets windowInsets = setupMockDisplayCutout(cutoutBoundingRects);
flutterView.onApplyWindowInsets(windowInsets);
verify(flutterRenderer).setViewportMetrics(viewportMetricsCaptor.capture());
assertEquals(1, viewportMetricsCaptor.getValue().getDisplayCutouts().size());
assertEquals(
cutoutBoundingRects.get(0),
viewportMetricsCaptor.getValue().getDisplayCutouts().get(0).bounds);
clearInvocations(flutterRenderer);
FoldingFeature displayFeature = mock(FoldingFeature.class);
Rect featureRect = new Rect(0, 0, 100, 100);
when(displayFeature.getBounds()).thenReturn(featureRect);
when(displayFeature.getOcclusionType()).thenReturn(FoldingFeature.OcclusionType.FULL);
when(displayFeature.getState()).thenReturn(FoldingFeature.State.FLAT);
FoldingFeature displayFeature = mock(FoldingFeature.class);
Rect featureRect = new Rect(0, 0, 100, 100);
when(displayFeature.getBounds()).thenReturn(featureRect);
when(displayFeature.getOcclusionType()).thenReturn(FoldingFeature.OcclusionType.FULL);
when(displayFeature.getState()).thenReturn(FoldingFeature.State.FLAT);
WindowLayoutInfo testWindowLayout =
new WindowLayoutInfo(Collections.singletonList(displayFeature));
WindowLayoutInfo testWindowLayout =
new WindowLayoutInfo(Collections.singletonList(displayFeature));
// When FlutterView is attached to the engine and window, and a hinge display feature exists
flutterView.onAttachedToWindow();
ArgumentCaptor<Consumer<WindowLayoutInfo>> wmConsumerCaptor =
ArgumentCaptor.forClass(Consumer.class);
verify(windowInfoRepo).addWindowLayoutInfoListener(any(), any(), wmConsumerCaptor.capture());
Consumer<WindowLayoutInfo> wmConsumer = wmConsumerCaptor.getValue();
clearInvocations(flutterRenderer);
wmConsumer.accept(testWindowLayout);
// When FlutterView is attached to the engine and window, and a hinge display feature
// exists
flutterView.onAttachedToWindow();
ArgumentCaptor<Consumer<WindowLayoutInfo>> wmConsumerCaptor =
ArgumentCaptor.forClass(Consumer.class);
verify(windowInfoRepo)
.addWindowLayoutInfoListener(any(), any(), wmConsumerCaptor.capture());
Consumer<WindowLayoutInfo> wmConsumer = wmConsumerCaptor.getValue();
clearInvocations(flutterRenderer);
wmConsumer.accept(testWindowLayout);
// Then the Renderer receives the display feature
verify(flutterRenderer).setViewportMetrics(viewportMetricsCaptor.capture());
assertEquals(1, viewportMetricsCaptor.getValue().getDisplayFeatures().size());
FlutterRenderer.DisplayFeature feature =
viewportMetricsCaptor.getValue().getDisplayFeatures().get(0);
assertEquals(FlutterRenderer.DisplayFeatureType.HINGE, feature.type);
assertEquals(FlutterRenderer.DisplayFeatureState.POSTURE_FLAT, feature.state);
assertEquals(featureRect, feature.bounds);
// Then the Renderer receives the display feature
verify(flutterRenderer).setViewportMetrics(viewportMetricsCaptor.capture());
assertEquals(1, viewportMetricsCaptor.getValue().getDisplayFeatures().size());
FlutterRenderer.DisplayFeature feature =
viewportMetricsCaptor.getValue().getDisplayFeatures().get(0);
assertEquals(FlutterRenderer.DisplayFeatureType.HINGE, feature.type);
assertEquals(FlutterRenderer.DisplayFeatureState.POSTURE_FLAT, feature.state);
assertEquals(featureRect, feature.bounds);
// Assert the display cutout is unaffected.
assertEquals(1, viewportMetricsCaptor.getValue().getDisplayCutouts().size());
FlutterRenderer.DisplayFeature cutout =
viewportMetricsCaptor.getValue().getDisplayCutouts().get(0);
assertEquals(cutoutBoundingRects.get(0), cutout.bounds);
assertEquals(FlutterRenderer.DisplayFeatureType.CUTOUT, cutout.type);
assertEquals(FlutterRenderer.DisplayFeatureState.UNKNOWN, cutout.state);
// Assert the display cutout is unaffected.
assertEquals(1, viewportMetricsCaptor.getValue().getDisplayCutouts().size());
FlutterRenderer.DisplayFeature cutout =
viewportMetricsCaptor.getValue().getDisplayCutouts().get(0);
assertEquals(cutoutBoundingRects.get(0), cutout.bounds);
assertEquals(FlutterRenderer.DisplayFeatureType.CUTOUT, cutout.type);
assertEquals(FlutterRenderer.DisplayFeatureState.UNKNOWN, cutout.state);
});
}
}
@Test

View File

@ -11,6 +11,7 @@ import static org.mockito.Mockito.verify;
import android.annotation.TargetApi;
import android.app.Activity;
import android.view.PointerIcon;
import androidx.test.core.app.ActivityScenario;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import io.flutter.embedding.android.FlutterView;
import io.flutter.embedding.engine.dart.DartExecutor;
@ -21,7 +22,6 @@ import java.util.HashMap;
import org.json.JSONException;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.robolectric.Robolectric;
import org.robolectric.annotation.Config;
@Config(
@ -31,34 +31,36 @@ import org.robolectric.annotation.Config;
@TargetApi(API_LEVELS.API_24)
public class MouseCursorPluginTest {
@SuppressWarnings("deprecation")
// Robolectric.setupActivity.
// TODO(reidbaker): https://github.com/flutter/flutter/issues/133151
@Test
public void mouseCursorPlugin_SetsSystemCursorOnRequest() throws JSONException {
// Migrate to ActivityScenario by following https://github.com/robolectric/robolectric/pull/4736
// Initialize a general MouseCursorPlugin.
FlutterView testView = spy(new FlutterView(Robolectric.setupActivity(Activity.class)));
MouseCursorChannel mouseCursorChannel = new MouseCursorChannel(mock(DartExecutor.class));
try (ActivityScenario<Activity> scenario = ActivityScenario.launch(Activity.class)) {
scenario.onActivity(
activity -> {
FlutterView testView = spy(new FlutterView(activity));
MouseCursorChannel mouseCursorChannel =
new MouseCursorChannel(mock(DartExecutor.class));
MouseCursorPlugin mouseCursorPlugin = new MouseCursorPlugin(testView, mouseCursorChannel);
MouseCursorPlugin mouseCursorPlugin =
new MouseCursorPlugin(testView, mouseCursorChannel);
final StoredResult methodResult = new StoredResult();
mouseCursorChannel.synthesizeMethodCall(
new MethodCall(
"activateSystemCursor",
new HashMap<String, Object>() {
private static final long serialVersionUID = 1L;
final StoredResult methodResult = new StoredResult();
mouseCursorChannel.synthesizeMethodCall(
new MethodCall(
"activateSystemCursor",
new HashMap<String, Object>() {
private static final long serialVersionUID = 1L;
{
put("device", 1);
put("kind", "text");
}
}),
methodResult);
verify(testView, times(1)).getSystemPointerIcon(PointerIcon.TYPE_TEXT);
verify(testView, times(1)).setPointerIcon(any(PointerIcon.class));
assertEquals(methodResult.result, Boolean.TRUE);
{
put("device", 1);
put("kind", "text");
}
}),
methodResult);
verify(testView, times(1)).getSystemPointerIcon(PointerIcon.TYPE_TEXT);
verify(testView, times(1)).setPointerIcon(any(PointerIcon.class));
assertEquals(methodResult.result, Boolean.TRUE);
});
}
}
}

View File

@ -39,6 +39,7 @@ import android.view.WindowInsetsController;
import android.view.WindowManager;
import androidx.activity.OnBackPressedCallback;
import androidx.fragment.app.FragmentActivity;
import androidx.test.core.app.ActivityScenario;
import androidx.test.core.app.ApplicationProvider;
import io.flutter.embedding.engine.systemchannels.PlatformChannel;
import io.flutter.embedding.engine.systemchannels.PlatformChannel.Brightness;
@ -645,51 +646,56 @@ public class PlatformPluginTest {
verify(mockActivity, never()).finish();
}
@SuppressWarnings("deprecation")
// Robolectric.setupActivity.
// TODO(reidbaker): https://github.com/flutter/flutter/issues/133151
@Test
public void popSystemNavigatorFlutterFragment() {
// Migrate to ActivityScenario by following https://github.com/robolectric/robolectric/pull/4736
FragmentActivity activity = spy(Robolectric.setupActivity(FragmentActivity.class));
final AtomicBoolean onBackPressedCalled = new AtomicBoolean(false);
OnBackPressedCallback backCallback =
new OnBackPressedCallback(true) {
@Override
public void handleOnBackPressed() {
onBackPressedCalled.set(true);
}
};
activity.getOnBackPressedDispatcher().addCallback(backCallback);
try (ActivityScenario<FragmentActivity> scenario =
ActivityScenario.launch(FragmentActivity.class)) {
scenario.onActivity(
fragmentActivity -> {
FragmentActivity activity = spy(fragmentActivity);
final AtomicBoolean onBackPressedCalled = new AtomicBoolean(false);
OnBackPressedCallback backCallback =
new OnBackPressedCallback(true) {
@Override
public void handleOnBackPressed() {
onBackPressedCalled.set(true);
}
};
activity.getOnBackPressedDispatcher().addCallback(backCallback);
PlatformPluginDelegate mockPlatformPluginDelegate = mock(PlatformPluginDelegate.class);
when(mockPlatformPluginDelegate.popSystemNavigator()).thenReturn(false);
PlatformPlugin platformPlugin =
new PlatformPlugin(activity, mockPlatformChannel, mockPlatformPluginDelegate);
PlatformPluginDelegate mockPlatformPluginDelegate = mock(PlatformPluginDelegate.class);
when(mockPlatformPluginDelegate.popSystemNavigator()).thenReturn(false);
PlatformPlugin platformPlugin =
new PlatformPlugin(activity, mockPlatformChannel, mockPlatformPluginDelegate);
platformPlugin.mPlatformMessageHandler.popSystemNavigator();
platformPlugin.mPlatformMessageHandler.popSystemNavigator();
verify(activity, never()).finish();
verify(mockPlatformPluginDelegate, times(1)).popSystemNavigator();
assertTrue(onBackPressedCalled.get());
verify(activity, never()).finish();
verify(mockPlatformPluginDelegate, times(1)).popSystemNavigator();
assertTrue(onBackPressedCalled.get());
});
}
}
@SuppressWarnings("deprecation")
// Robolectric.setupActivity.
// TODO(reidbaker): https://github.com/flutter/flutter/issues/133151
@Test
public void doesNotDoAnythingByDefaultIfFragmentPopSystemNavigatorOverridden() {
FragmentActivity activity = spy(Robolectric.setupActivity(FragmentActivity.class));
PlatformPluginDelegate mockPlatformPluginDelegate = mock(PlatformPluginDelegate.class);
when(mockPlatformPluginDelegate.popSystemNavigator()).thenReturn(true);
PlatformPlugin platformPlugin =
new PlatformPlugin(activity, mockPlatformChannel, mockPlatformPluginDelegate);
try (ActivityScenario<FragmentActivity> scenario =
ActivityScenario.launch(FragmentActivity.class)) {
scenario.onActivity(
fragmentActivity -> {
FragmentActivity activity = spy(fragmentActivity);
PlatformPluginDelegate mockPlatformPluginDelegate = mock(PlatformPluginDelegate.class);
when(mockPlatformPluginDelegate.popSystemNavigator()).thenReturn(true);
PlatformPlugin platformPlugin =
new PlatformPlugin(activity, mockPlatformChannel, mockPlatformPluginDelegate);
platformPlugin.mPlatformMessageHandler.popSystemNavigator();
platformPlugin.mPlatformMessageHandler.popSystemNavigator();
verify(mockPlatformPluginDelegate, times(1)).popSystemNavigator();
// No longer perform the default action when overridden.
verify(activity, never()).finish();
verify(mockPlatformPluginDelegate, times(1)).popSystemNavigator();
// No longer perform the default action when overridden.
verify(activity, never()).finish();
});
}
}
@Test

View File

@ -7,6 +7,10 @@
<activity android:name="io.flutter.embedding.android.FlutterFragmentActivity" />
<activity android:name="io.flutter.embedding.android.FlutterFragmentActivityTest$FlutterFragmentActivityWithProvidedEngine" />
<activity android:name="androidx.fragment.app.FragmentActivity" />
<activity android:name="android.app.Activity" />
</application>