Flutter Engine
The Flutter Engine
Loading...
Searching...
No Matches
FlutterActivityAndFragmentDelegateTest.java
Go to the documentation of this file.
1package io.flutter.embedding.android;
2
3import static android.content.ComponentCallbacks2.*;
4import static org.junit.Assert.assertEquals;
5import static org.junit.Assert.assertFalse;
6import static org.junit.Assert.assertNotNull;
7import static org.junit.Assert.assertNull;
8import static org.junit.Assert.assertThrows;
9import static org.junit.Assert.assertTrue;
10import static org.mockito.ArgumentMatchers.any;
11import static org.mockito.ArgumentMatchers.eq;
12import static org.mockito.ArgumentMatchers.isNotNull;
13import static org.mockito.ArgumentMatchers.isNull;
14import static org.mockito.Mockito.mock;
15import static org.mockito.Mockito.never;
16import static org.mockito.Mockito.times;
17import static org.mockito.Mockito.verify;
18import static org.mockito.Mockito.when;
19
20import android.app.Activity;
21import android.content.Context;
22import android.content.Intent;
23import android.net.Uri;
24import android.view.View;
25import android.window.BackEvent;
26import androidx.annotation.NonNull;
27import androidx.lifecycle.Lifecycle;
28import androidx.test.core.app.ApplicationProvider;
29import androidx.test.ext.junit.runners.AndroidJUnit4;
30import io.flutter.FlutterInjector;
31import io.flutter.embedding.android.FlutterActivityAndFragmentDelegate.Host;
32import io.flutter.embedding.engine.FlutterEngine;
33import io.flutter.embedding.engine.FlutterEngineCache;
34import io.flutter.embedding.engine.FlutterEngineGroup;
35import io.flutter.embedding.engine.FlutterEngineGroupCache;
36import io.flutter.embedding.engine.FlutterShellArgs;
37import io.flutter.embedding.engine.dart.DartExecutor;
38import io.flutter.embedding.engine.loader.FlutterLoader;
39import io.flutter.embedding.engine.plugins.activity.ActivityControlSurface;
40import io.flutter.embedding.engine.renderer.FlutterRenderer;
41import io.flutter.embedding.engine.renderer.FlutterUiDisplayListener;
42import io.flutter.embedding.engine.systemchannels.AccessibilityChannel;
43import io.flutter.embedding.engine.systemchannels.BackGestureChannel;
44import io.flutter.embedding.engine.systemchannels.LifecycleChannel;
45import io.flutter.embedding.engine.systemchannels.LocalizationChannel;
46import io.flutter.embedding.engine.systemchannels.MouseCursorChannel;
47import io.flutter.embedding.engine.systemchannels.NavigationChannel;
48import io.flutter.embedding.engine.systemchannels.SettingsChannel;
49import io.flutter.embedding.engine.systemchannels.SystemChannel;
50import io.flutter.embedding.engine.systemchannels.TextInputChannel;
51import io.flutter.plugin.localization.LocalizationPlugin;
52import io.flutter.plugin.platform.PlatformViewsController;
53import java.util.ArrayList;
54import java.util.Arrays;
55import java.util.List;
56import org.junit.Before;
57import org.junit.Test;
58import org.junit.runner.RunWith;
59import org.mockito.ArgumentCaptor;
60import org.robolectric.Robolectric;
61import org.robolectric.android.controller.ActivityController;
62import org.robolectric.annotation.Config;
63
64@Config(manifest = Config.NONE)
65@RunWith(AndroidJUnit4.class)
67 private final Context ctx = ApplicationProvider.getApplicationContext();
68 private FlutterEngine mockFlutterEngine;
69 private FlutterActivityAndFragmentDelegate.Host mockHost;
70 private FlutterActivityAndFragmentDelegate.Host mockHost2;
71
72 @SuppressWarnings("deprecation")
73 // Robolectric.setupActivity
74 // TODO(reidbaker): https://github.com/flutter/flutter/issues/133151
75 @Before
76 public void setup() {
78 // Create a mocked FlutterEngine for the various interactions required by the delegate
79 // being tested.
80 mockFlutterEngine = mockFlutterEngine();
81
82 // Create a mocked Host, which is required by the delegate being tested.
83 mockHost = mock(FlutterActivityAndFragmentDelegate.Host.class);
84 when(mockHost.getContext()).thenReturn(ctx);
85 when(mockHost.getActivity()).thenReturn(Robolectric.setupActivity(Activity.class));
86 when(mockHost.getLifecycle()).thenReturn(mock(Lifecycle.class));
87 when(mockHost.getFlutterShellArgs()).thenReturn(new FlutterShellArgs(new String[] {}));
88 when(mockHost.getDartEntrypointFunctionName()).thenReturn("main");
89 when(mockHost.getDartEntrypointArgs()).thenReturn(null);
90 when(mockHost.getAppBundlePath()).thenReturn("/fake/path");
91 when(mockHost.getInitialRoute()).thenReturn("/");
92 when(mockHost.getRenderMode()).thenReturn(RenderMode.surface);
93 when(mockHost.getTransparencyMode()).thenReturn(TransparencyMode.transparent);
94 when(mockHost.provideFlutterEngine(any(Context.class))).thenReturn(mockFlutterEngine);
95 when(mockHost.shouldAttachEngineToActivity()).thenReturn(true);
96 when(mockHost.shouldHandleDeeplinking()).thenReturn(false);
97 when(mockHost.shouldDestroyEngineWithHost()).thenReturn(true);
98 when(mockHost.shouldDispatchAppLifecycleState()).thenReturn(true);
99 when(mockHost.attachToEngineAutomatically()).thenReturn(true);
100
101 mockHost2 = mock(FlutterActivityAndFragmentDelegate.Host.class);
102 when(mockHost2.getContext()).thenReturn(ctx);
103 when(mockHost2.getActivity()).thenReturn(Robolectric.setupActivity(Activity.class));
104 when(mockHost2.getLifecycle()).thenReturn(mock(Lifecycle.class));
105 when(mockHost2.getFlutterShellArgs()).thenReturn(new FlutterShellArgs(new String[] {}));
106 when(mockHost2.getDartEntrypointFunctionName()).thenReturn("main");
107 when(mockHost2.getDartEntrypointArgs()).thenReturn(null);
108 when(mockHost2.getAppBundlePath()).thenReturn("/fake/path");
109 when(mockHost2.getInitialRoute()).thenReturn("/");
110 when(mockHost2.getRenderMode()).thenReturn(RenderMode.surface);
111 when(mockHost2.getTransparencyMode()).thenReturn(TransparencyMode.transparent);
112 when(mockHost2.provideFlutterEngine(any(Context.class))).thenReturn(mockFlutterEngine);
113 when(mockHost2.shouldAttachEngineToActivity()).thenReturn(true);
114 when(mockHost2.shouldHandleDeeplinking()).thenReturn(false);
115 when(mockHost2.shouldDestroyEngineWithHost()).thenReturn(true);
116 when(mockHost2.shouldDispatchAppLifecycleState()).thenReturn(true);
117 when(mockHost2.attachToEngineAutomatically()).thenReturn(true);
118 }
119
120 @Test
122 // ---- Test setup ----
123 // Create the real object that we're testing.
125
126 // We're testing lifecycle behaviors, which require/expect that certain methods have already
127 // been executed by the time they run. Therefore, we run those expected methods first.
128 delegate.onAttach(ctx);
129 delegate.onCreateView(null, null, null, 0, true);
130
131 // --- Execute the behavior under test ---
132 // By the time an Activity/Fragment is started, we don't expect any lifecycle messages
133 // to have been sent to Flutter.
134 delegate.onStart();
135 verify(mockFlutterEngine.getLifecycleChannel(), never()).aWindowIsFocused();
136 verify(mockFlutterEngine.getLifecycleChannel(), never()).noWindowsAreFocused();
137 verify(mockFlutterEngine.getLifecycleChannel(), never()).appIsResumed();
138 verify(mockFlutterEngine.getLifecycleChannel(), never()).appIsInactive();
139 verify(mockFlutterEngine.getLifecycleChannel(), never()).appIsPaused();
140 verify(mockFlutterEngine.getLifecycleChannel(), never()).appIsDetached();
141
142 // When the Activity/Fragment is resumed, a resumed message should have been sent to Flutter.
143 delegate.onResume();
144 verify(mockFlutterEngine.getLifecycleChannel(), never()).aWindowIsFocused();
145 verify(mockFlutterEngine.getLifecycleChannel(), never()).noWindowsAreFocused();
146 verify(mockFlutterEngine.getLifecycleChannel(), times(1)).appIsResumed();
147 verify(mockFlutterEngine.getLifecycleChannel(), never()).appIsInactive();
148 verify(mockFlutterEngine.getLifecycleChannel(), never()).appIsPaused();
149 verify(mockFlutterEngine.getLifecycleChannel(), never()).appIsDetached();
150
151 // When the app loses focus because something else has it (e.g. notification
152 // windowshade or app switcher), it should go to inactive.
153 delegate.onWindowFocusChanged(false);
154 verify(mockFlutterEngine.getLifecycleChannel(), never()).aWindowIsFocused();
155 verify(mockFlutterEngine.getLifecycleChannel(), times(1)).noWindowsAreFocused();
156 verify(mockFlutterEngine.getLifecycleChannel(), times(1)).appIsResumed();
157 verify(mockFlutterEngine.getLifecycleChannel(), never()).appIsInactive();
158 verify(mockFlutterEngine.getLifecycleChannel(), never()).appIsPaused();
159 verify(mockFlutterEngine.getLifecycleChannel(), never()).appIsDetached();
160
161 // When the app regains focus, it should go to resumed again.
162 delegate.onWindowFocusChanged(true);
163 verify(mockFlutterEngine.getLifecycleChannel(), times(1)).aWindowIsFocused();
164 verify(mockFlutterEngine.getLifecycleChannel(), times(1)).noWindowsAreFocused();
165 verify(mockFlutterEngine.getLifecycleChannel(), times(1)).appIsResumed();
166 verify(mockFlutterEngine.getLifecycleChannel(), never()).appIsInactive();
167 verify(mockFlutterEngine.getLifecycleChannel(), never()).appIsPaused();
168 verify(mockFlutterEngine.getLifecycleChannel(), never()).appIsDetached();
169
170 // When the Activity/Fragment is paused, an inactive message should have been sent to Flutter.
171 delegate.onPause();
172 verify(mockFlutterEngine.getLifecycleChannel(), times(1)).aWindowIsFocused();
173 verify(mockFlutterEngine.getLifecycleChannel(), times(1)).noWindowsAreFocused();
174 verify(mockFlutterEngine.getLifecycleChannel(), times(1)).appIsResumed();
175 verify(mockFlutterEngine.getLifecycleChannel(), times(1)).appIsInactive();
176 verify(mockFlutterEngine.getLifecycleChannel(), never()).appIsPaused();
177 verify(mockFlutterEngine.getLifecycleChannel(), never()).appIsDetached();
178
179 // When the Activity/Fragment is stopped, a paused message should have been sent to Flutter.
180 // Notice that Flutter uses the term "paused" in a different way, and at a different time
181 // than the Android OS.
182 delegate.onStop();
183 verify(mockFlutterEngine.getLifecycleChannel(), times(1)).aWindowIsFocused();
184 verify(mockFlutterEngine.getLifecycleChannel(), times(1)).noWindowsAreFocused();
185 verify(mockFlutterEngine.getLifecycleChannel(), times(1)).appIsResumed();
186 verify(mockFlutterEngine.getLifecycleChannel(), times(1)).appIsInactive();
187 verify(mockFlutterEngine.getLifecycleChannel(), times(1)).appIsPaused();
188 verify(mockFlutterEngine.getLifecycleChannel(), never()).appIsDetached();
189
190 // When activity detaches, a detached message should have been sent to Flutter.
191 delegate.onDetach();
192 verify(mockFlutterEngine.getLifecycleChannel(), times(1)).aWindowIsFocused();
193 verify(mockFlutterEngine.getLifecycleChannel(), times(1)).noWindowsAreFocused();
194 verify(mockFlutterEngine.getLifecycleChannel(), times(1)).appIsResumed();
195 verify(mockFlutterEngine.getLifecycleChannel(), times(1)).appIsInactive();
196 verify(mockFlutterEngine.getLifecycleChannel(), times(1)).appIsPaused();
197 verify(mockFlutterEngine.getLifecycleChannel(), times(1)).appIsDetached();
198 }
199
200 @Test
202 // ---- Test setup ----
203 // Create the real object that we're testing.
205
206 when(mockHost.shouldDispatchAppLifecycleState()).thenReturn(false);
207
208 // We're testing lifecycle behaviors, which require/expect that certain methods have already
209 // been executed by the time they run. Therefore, we run those expected methods first.
210 delegate.onAttach(ctx);
211 delegate.onCreateView(null, null, null, 0, true);
212 delegate.onStart();
213 delegate.onResume();
214 delegate.onWindowFocusChanged(false);
215 delegate.onWindowFocusChanged(true);
216 delegate.onPause();
217 delegate.onStop();
218 delegate.onDetach();
219
220 verify(mockFlutterEngine.getLifecycleChannel(), never()).aWindowIsFocused();
221 verify(mockFlutterEngine.getLifecycleChannel(), never()).noWindowsAreFocused();
222 verify(mockFlutterEngine.getLifecycleChannel(), never()).appIsResumed();
223 verify(mockFlutterEngine.getLifecycleChannel(), never()).appIsPaused();
224 verify(mockFlutterEngine.getLifecycleChannel(), never()).appIsInactive();
225 verify(mockFlutterEngine.getLifecycleChannel(), never()).appIsDetached();
226 }
227
228 @Test
230 // ---- Test setup ----
231 // Create the real object that we're testing.
233
234 // --- Execute the behavior under test ---
235 // The FlutterEngine is created in onAttach().
236 delegate.onAttach(ctx);
237
238 // Verify that the host was asked to provide a FlutterEngine.
239 verify(mockHost, times(1)).provideFlutterEngine(any(Context.class));
240
241 // Verify that the delegate's FlutterEngine is our mock FlutterEngine.
242 assertEquals(
243 "The delegate failed to use the host's FlutterEngine.",
244 mockFlutterEngine,
245 delegate.getFlutterEngine());
246 }
247
248 @Test
250 // ---- Test setup ----
251 // Place a FlutterEngine in the static cache.
252 FlutterEngine cachedEngine = mockFlutterEngine();
253 FlutterEngineCache.getInstance().put("my_flutter_engine", cachedEngine);
254
255 // Adjust fake host to request cached engine.
256 when(mockHost.getCachedEngineId()).thenReturn("my_flutter_engine");
257
258 // Create the real object that we're testing.
260
261 // --- Execute the behavior under test ---
262 // The FlutterEngine is obtained in onAttach().
263 delegate.onAttach(ctx);
264 delegate.onCreateView(null, null, null, 0, true);
265 delegate.onStart();
266 delegate.onResume();
267
268 // --- Verify that the cached engine was used ---
269 // Verify that the non-cached engine was not used.
270 verify(mockFlutterEngine.getDartExecutor(), never())
271 .executeDartEntrypoint(any(DartExecutor.DartEntrypoint.class));
272
273 // We should never instruct a cached engine to execute Dart code - it should already be
274 // executing it.
275 verify(cachedEngine.getDartExecutor(), never())
276 .executeDartEntrypoint(any(DartExecutor.DartEntrypoint.class));
277
278 // If the cached engine is being used, it should have sent a resumed lifecycle event.
279 verify(cachedEngine.getLifecycleChannel(), times(1)).appIsResumed();
280 }
281
282 @Test(expected = IllegalStateException.class)
283 public void itThrowsExceptionIfCachedEngineDoesNotExist() {
284 // ---- Test setup ----
285 // Adjust fake host to request cached engine that does not exist.
286 when(mockHost.getCachedEngineId()).thenReturn("my_flutter_engine");
287
288 // Create the real object that we're testing.
290
291 // --- Execute the behavior under test ---
292 // The FlutterEngine existence is verified in onAttach()
293 delegate.onAttach(ctx);
294
295 // Expect IllegalStateException.
296 }
297
298 // Bug: b/271100292
299 @Test
301 // ---- Test setup ----
302 FlutterLoader mockFlutterLoader = mock(FlutterLoader.class);
303 Activity mockActivity = mock(Activity.class);
304 Intent mockIntent = mock(Intent.class);
305 when(mockFlutterLoader.findAppBundlePath()).thenReturn("default_flutter_assets/path");
307 new FlutterInjector.Builder().setFlutterLoader(mockFlutterLoader).build());
308 FlutterEngineGroup flutterEngineGroup = mock(FlutterEngineGroup.class);
309 FlutterEngineGroupCache.getInstance().put("my_flutter_engine_group", flutterEngineGroup);
310
311 List<String> entryPointArgs = new ArrayList<>();
312 entryPointArgs.add("entrypoint-arg");
313
314 // Adjust fake host to request cached engine group.
315 when(mockHost.getInitialRoute()).thenReturn(null);
316 when(mockHost.getCachedEngineGroupId()).thenReturn("my_flutter_engine_group");
317 when(mockHost.provideFlutterEngine(any(Context.class))).thenReturn(null);
318 when(mockHost.shouldAttachEngineToActivity()).thenReturn(false);
319 when(mockHost.getDartEntrypointArgs()).thenReturn(entryPointArgs);
320 when(mockHost.shouldHandleDeeplinking()).thenReturn(true);
321 when(mockHost.getActivity()).thenReturn(mockActivity);
322 when(mockActivity.getIntent()).thenReturn(mockIntent);
323 when(mockIntent.getData()).thenReturn(Uri.parse("foo://example.com/initial_route"));
324
325 // Create the real object that we're testing.
327
328 // --- Execute the behavior under test ---
329 // The FlutterEngine is obtained in onAttach().
330 delegate.onAttach(ctx);
331
332 DartExecutor.DartEntrypoint entrypoint = new DartExecutor.DartEntrypoint("/fake/path", "main");
333 ArgumentCaptor<FlutterEngineGroup.Options> optionsCaptor =
334 ArgumentCaptor.forClass(FlutterEngineGroup.Options.class);
335 verify(flutterEngineGroup, times(1)).createAndRunEngine(optionsCaptor.capture());
336 assertEquals("foo://example.com/initial_route", optionsCaptor.getValue().getInitialRoute());
337 }
338
339 @Test
341 // ---- Test setup ----
342 FlutterLoader mockFlutterLoader = mock(FlutterLoader.class);
343 when(mockFlutterLoader.findAppBundlePath()).thenReturn("default_flutter_assets/path");
345 new FlutterInjector.Builder().setFlutterLoader(mockFlutterLoader).build());
346 FlutterEngineGroup flutterEngineGroup = mock(FlutterEngineGroup.class);
347 FlutterEngineGroupCache.getInstance().put("my_flutter_engine_group", flutterEngineGroup);
348
349 List<String> entryPointArgs = new ArrayList<>();
350 entryPointArgs.add("entrypoint-arg");
351
352 // Adjust fake host to request cached engine group.
353 when(mockHost.getCachedEngineGroupId()).thenReturn("my_flutter_engine_group");
354 when(mockHost.provideFlutterEngine(any(Context.class))).thenReturn(null);
355 when(mockHost.shouldAttachEngineToActivity()).thenReturn(false);
356 when(mockHost.getDartEntrypointArgs()).thenReturn(entryPointArgs);
357
358 // Create the real object that we're testing.
360
361 // --- Execute the behavior under test ---
362 // The FlutterEngine is obtained in onAttach().
363 delegate.onAttach(ctx);
364
365 // If the engine in FlutterEngineGroup is being used, it should have sent a resumed lifecycle
366 // event.
367 // Note: "/fake/path" and "main" come from `setUp()`.
368 DartExecutor.DartEntrypoint entrypoint = new DartExecutor.DartEntrypoint("/fake/path", "main");
369 ArgumentCaptor<FlutterEngineGroup.Options> optionsCaptor =
370 ArgumentCaptor.forClass(FlutterEngineGroup.Options.class);
371 verify(flutterEngineGroup, times(1)).createAndRunEngine(optionsCaptor.capture());
372 assertEquals(mockHost.getContext(), optionsCaptor.getValue().getContext());
373 assertEquals(entrypoint, optionsCaptor.getValue().getDartEntrypoint());
374 assertEquals(mockHost.getInitialRoute(), optionsCaptor.getValue().getInitialRoute());
375 assertNotNull(optionsCaptor.getValue().getDartEntrypointArgs());
376 assertEquals(1, optionsCaptor.getValue().getDartEntrypointArgs().size());
377 assertEquals("entrypoint-arg", optionsCaptor.getValue().getDartEntrypointArgs().get(0));
378 }
379
380 @Test(expected = IllegalStateException.class)
381 public void itThrowsExceptionIfNewEngineInGroupNotExist() {
382 // ---- Test setup ----
383 FlutterEngineGroupCache.getInstance().clear();
384
385 // Adjust fake host to request cached engine group that does not exist.
386 when(mockHost.getCachedEngineGroupId()).thenReturn("my_flutter_engine_group");
387 when(mockHost.getCachedEngineId()).thenReturn(null);
388 when(mockHost.provideFlutterEngine(any(Context.class))).thenReturn(null);
389 when(mockHost.shouldAttachEngineToActivity()).thenReturn(false);
390
391 // Create the real object that we're testing.
393
394 // --- Execute the behavior under test ---
395 // The FlutterEngine existence is verified in onAttach()
396 delegate.onAttach(ctx);
397
398 // Expect IllegalStateException.
399 }
400
401 @Test
403 // ---- Test setup ----
404 // Create the real object that we're testing.
406
407 // --- Execute the behavior under test ---
408 // The FlutterEngine is created in onAttach().
409 delegate.onAttach(ctx);
410
411 // Verify that the host was asked to configure our FlutterEngine.
412 verify(mockHost, times(1)).configureFlutterEngine(mockFlutterEngine);
413 }
414
415 @Test
417 // ---- Test setup ----
418 // Create the real object that we're testing.
420
421 // --- Execute the behavior under test ---
422 delegate.onAttach(ctx);
423 delegate.onCreateView(null, null, null, 0, true);
424
425 // Verify that the host was asked to configure a FlutterSurfaceView.
426 verify(mockHost, times(1)).onFlutterSurfaceViewCreated(isNotNull());
427 }
428
429 @SuppressWarnings("deprecation")
430 // Robolectric.setupActivity
431 // TODO(reidbaker): https://github.com/flutter/flutter/issues/133151
432 @Test
434 // ---- Test setup ----
435 Host customMockHost = mock(Host.class);
436 when(customMockHost.getContext()).thenReturn(ctx);
437 when(customMockHost.getActivity()).thenReturn(Robolectric.setupActivity(Activity.class));
438 when(customMockHost.getLifecycle()).thenReturn(mock(Lifecycle.class));
439 when(customMockHost.getFlutterShellArgs()).thenReturn(new FlutterShellArgs(new String[] {}));
440 when(customMockHost.getDartEntrypointFunctionName()).thenReturn("main");
441 when(customMockHost.getAppBundlePath()).thenReturn("/fake/path");
442 when(customMockHost.getInitialRoute()).thenReturn("/");
443 when(customMockHost.getRenderMode()).thenReturn(RenderMode.texture);
444 when(customMockHost.getTransparencyMode()).thenReturn(TransparencyMode.transparent);
445 when(customMockHost.provideFlutterEngine(any(Context.class))).thenReturn(mockFlutterEngine);
446 when(customMockHost.shouldAttachEngineToActivity()).thenReturn(true);
447 when(customMockHost.shouldDestroyEngineWithHost()).thenReturn(true);
448
449 // Create the real object that we're testing.
451 new FlutterActivityAndFragmentDelegate(customMockHost);
452
453 // --- Execute the behavior under test ---
454 delegate.onAttach(ctx);
455 delegate.onCreateView(null, null, null, 0, false);
456
457 // Verify that the host was asked to configure a FlutterTextureView.
458 verify(customMockHost, times(1)).onFlutterTextureViewCreated(isNotNull());
459 }
460
461 @Test
463 // ---- Test setup ----
464 // Create the real object that we're testing.
466
467 // --- Execute the behavior under test ---
468 // The FlutterEngine is created in onAttach().
469 delegate.onAttach(ctx);
470 delegate.onDetach();
471
472 // Verify that the host was asked to configure our FlutterEngine.
473 verify(mockHost, times(1)).cleanUpFlutterEngine(mockFlutterEngine);
474 }
475
476 @Test
478 // ---- Test setup ----
479 // Set initial route on our fake Host.
480 when(mockHost.getInitialRoute()).thenReturn("/my/route");
481
482 // Create the real object that we're testing.
484
485 // --- Execute the behavior under test ---
486 // The initial route is sent in onStart().
487 delegate.onAttach(ctx);
488 delegate.onCreateView(null, null, null, 0, true);
489 delegate.onStart();
490
491 // Verify that the navigation channel was given our initial route.
492 verify(mockFlutterEngine.getNavigationChannel(), times(1)).setInitialRoute("/my/route");
493 }
494
495 @Test
497 // ---- Test setup ----
498 // Set Dart entrypoint parameters on fake host.
499 when(mockHost.getAppBundlePath()).thenReturn("/my/bundle/path");
500 when(mockHost.getDartEntrypointFunctionName()).thenReturn("myEntrypoint");
501
502 // Create the DartEntrypoint that we expect to be executed.
503 DartExecutor.DartEntrypoint dartEntrypoint =
504 new DartExecutor.DartEntrypoint("/my/bundle/path", "myEntrypoint");
505
506 // Create the real object that we're testing.
508
509 // --- Execute the behavior under test ---
510 // Dart is executed in onStart().
511 delegate.onAttach(ctx);
512 delegate.onCreateView(null, null, null, 0, true);
513 delegate.onStart();
514
515 // Verify that the host's Dart entrypoint was used.
516 verify(mockFlutterEngine.getDartExecutor(), times(1))
517 .executeDartEntrypoint(eq(dartEntrypoint), isNull());
518 }
519
520 @Test
522 // ---- Test setup ----
523 // Set Dart entrypoint parameters on fake host.
524 when(mockHost.getAppBundlePath()).thenReturn("/my/bundle/path");
525 when(mockHost.getDartEntrypointFunctionName()).thenReturn("myEntrypoint");
526 List<String> dartEntrypointArgs = new ArrayList<String>(Arrays.asList("foo", "bar"));
527 when(mockHost.getDartEntrypointArgs()).thenReturn(dartEntrypointArgs);
528
529 // Create the DartEntrypoint that we expect to be executed
530 DartExecutor.DartEntrypoint dartEntrypoint =
531 new DartExecutor.DartEntrypoint("/my/bundle/path", "myEntrypoint");
532
533 // Create the real object that we're testing.
535
536 // --- Execute the behavior under test ---
537 // Dart is executed in onStart().
538 delegate.onAttach(ctx);
539 delegate.onCreateView(null, null, null, 0, true);
540 delegate.onStart();
541
542 // Verify that the host's Dart entrypoint was used.
543 verify(mockFlutterEngine.getDartExecutor(), times(1))
544 .executeDartEntrypoint(any(DartExecutor.DartEntrypoint.class), eq(dartEntrypointArgs));
545 }
546
547 @Test
549 when(mockHost.getAppBundlePath()).thenReturn("/my/bundle/path");
550 when(mockHost.getDartEntrypointFunctionName()).thenReturn("myEntrypoint");
551 when(mockHost.getDartEntrypointLibraryUri()).thenReturn("package:foo/bar.dart");
552
553 DartExecutor.DartEntrypoint expectedEntrypoint =
554 new DartExecutor.DartEntrypoint("/my/bundle/path", "package:foo/bar.dart", "myEntrypoint");
555
557
558 delegate.onAttach(ctx);
559 delegate.onCreateView(null, null, null, 0, true);
560 delegate.onStart();
561
562 verify(mockFlutterEngine.getDartExecutor(), times(1))
563 .executeDartEntrypoint(eq(expectedEntrypoint), isNull());
564 }
565
566 @Test
568 // ---- Test setup ----
569 FlutterLoader mockFlutterLoader = mock(FlutterLoader.class);
570 when(mockFlutterLoader.findAppBundlePath()).thenReturn("default_flutter_assets/path");
572 new FlutterInjector.Builder().setFlutterLoader(mockFlutterLoader).build());
573
574 // Set Dart entrypoint parameters on fake host.
575 when(mockHost.getAppBundlePath()).thenReturn(null);
576 when(mockHost.getDartEntrypointFunctionName()).thenReturn("myEntrypoint");
577
578 // Create the DartEntrypoint that we expect to be executed.
579 DartExecutor.DartEntrypoint dartEntrypoint =
580 new DartExecutor.DartEntrypoint("default_flutter_assets/path", "myEntrypoint");
581
582 // Create the real object that we're testing.
584
585 // --- Execute the behavior under test ---
586 // Dart is executed in onStart().
587 delegate.onAttach(ctx);
588 delegate.onCreateView(null, null, null, 0, true);
589 delegate.onStart();
590
591 // Verify that the host's Dart entrypoint was used.
592 verify(mockFlutterEngine.getDartExecutor(), times(1))
593 .executeDartEntrypoint(eq(dartEntrypoint), isNull());
594 }
595
596 // "Attaching" to the surrounding Activity refers to Flutter being able to control
597 // system chrome and other Activity-level details. If Flutter is not attached to the
598 // surrounding Activity, it cannot control those details. This includes plugins.
599 @Test
601 // ---- Test setup ----
602 // Declare that the host wants Flutter to attach to the surrounding Activity.
603 when(mockHost.shouldAttachEngineToActivity()).thenReturn(true);
604
605 // Create the real object that we're testing.
607
608 // --- Execute the behavior under test ---
609 // Flutter is attached to the surrounding Activity in onAttach.
610 delegate.onAttach(ctx);
611
612 // Verify that the ActivityControlSurface was told to attach to an Activity.
613 verify(mockFlutterEngine.getActivityControlSurface(), times(1))
614 .attachToActivity(any(ExclusiveAppComponent.class), any(Lifecycle.class));
615
616 // Flutter is detached from the surrounding Activity in onDetach.
617 delegate.onDetach();
618
619 // Verify that the ActivityControlSurface was told to detach from the Activity.
620 verify(mockFlutterEngine.getActivityControlSurface(), times(1)).detachFromActivity();
621 }
622
623 // "Attaching" to the surrounding Activity refers to Flutter being able to control
624 // system chrome and other Activity-level details. If Flutter is not attached to the
625 // surrounding Activity, it cannot control those details. This includes plugins.
626 @Test
628 // ---- Test setup ----
629 // Declare that the host does NOT want Flutter to attach to the surrounding Activity.
630 when(mockHost.shouldAttachEngineToActivity()).thenReturn(false);
631
632 // getActivity() returns null if the activity is not attached
633 when(mockHost.getActivity()).thenReturn(null);
634
635 // Create the real object that we're testing.
637
638 // --- Execute the behavior under test ---
639 // Flutter is attached to the surrounding Activity in onAttach.
640 delegate.onAttach(ctx);
641
642 // Make sure all of the other lifecycle methods can run safely as well
643 // without a valid Activity
644 delegate.onCreateView(null, null, null, 0, true);
645 delegate.onStart();
646 delegate.onResume();
647 delegate.onPause();
648 delegate.onStop();
649 delegate.onDestroyView();
650
651 // Flutter is detached from the surrounding Activity in onDetach.
652 delegate.onDetach();
653
654 // Verify that the ActivityControlSurface was NOT told to attach or detach to an Activity.
655 verify(mockFlutterEngine.getActivityControlSurface(), never())
656 .attachToActivity(any(ExclusiveAppComponent.class), any(Lifecycle.class));
657 verify(mockFlutterEngine.getActivityControlSurface(), never()).detachFromActivity();
658 }
659
660 @Test
662 // Create the real object that we're testing.
664
665 // --- Execute the behavior under test ---
666 // The FlutterEngine is set up in onAttach().
667 delegate.onAttach(ctx);
668
669 // Emulate the host and inform our delegate that the back button was pressed.
670 delegate.onBackPressed();
671
672 // Verify that the navigation channel tried to send a message to Flutter.
673 verify(mockFlutterEngine.getNavigationChannel(), times(1)).popRoute();
674 }
675
676 @Test
678 // Create the real object that we're testing.
680
681 // --- Execute the behavior under test ---
682 // The FlutterEngine is set up in onAttach().
683 delegate.onAttach(ctx);
684
685 // Emulate the host and inform our delegate of the start back gesture with a mocked BackEvent
686 BackEvent backEvent = mock(BackEvent.class);
687 delegate.startBackGesture(backEvent);
688
689 // Verify that the back gesture tried to send a message to Flutter.
690 verify(mockFlutterEngine.getBackGestureChannel(), times(1)).startBackGesture(backEvent);
691 }
692
693 @Test
695 // Create the real object that we're testing.
697
698 // --- Execute the behavior under test ---
699 // The FlutterEngine is set up in onAttach().
700 delegate.onAttach(ctx);
701
702 // Emulate the host and inform our delegate of the back gesture progress with a mocked BackEvent
703 BackEvent backEvent = mock(BackEvent.class);
704 delegate.updateBackGestureProgress(backEvent);
705
706 // Verify that the back gesture tried to send a message to Flutter.
707 verify(mockFlutterEngine.getBackGestureChannel(), times(1))
708 .updateBackGestureProgress(backEvent);
709 }
710
711 @Test
713 // Create the real object that we're testing.
715
716 // --- Execute the behavior under test ---
717 // The FlutterEngine is set up in onAttach().
718 delegate.onAttach(ctx);
719
720 // Emulate the host and inform our delegate when the back gesture is committed
721 delegate.commitBackGesture();
722
723 // Verify that the back gesture tried to send a message to Flutter.
724 verify(mockFlutterEngine.getBackGestureChannel(), times(1)).commitBackGesture();
725 }
726
727 @Test
729 // Create the real object that we're testing.
731
732 // --- Execute the behavior under test ---
733 // The FlutterEngine is set up in onAttach().
734 delegate.onAttach(ctx);
735
736 // Emulate the host and inform our delegate of the back gesture cancellation
737 delegate.cancelBackGesture();
738
739 // Verify that the back gesture tried to send a message to Flutter.
740 verify(mockFlutterEngine.getBackGestureChannel(), times(1)).cancelBackGesture();
741 }
742
743 @Test
745 // Create the real object that we're testing.
747
748 // --- Execute the behavior under test ---
749 // The FlutterEngine is set up in onAttach().
750 delegate.onAttach(ctx);
751
752 // Emulate the host and call the method that we expect to be forwarded.
753 delegate.onRequestPermissionsResult(0, new String[] {}, new int[] {});
754
755 // Verify that the call was forwarded to the engine.
756 verify(mockFlutterEngine.getActivityControlSurface(), times(1))
757 .onRequestPermissionsResult(any(Integer.class), any(String[].class), any(int[].class));
758 }
759
760 @Test
761 public void
763 Intent intent = FlutterActivity.createDefaultIntent(ctx);
764 intent.setData(Uri.parse("http://myApp/custom/route?query=test"));
765
766 ActivityController<FlutterActivity> activityController =
767 Robolectric.buildActivity(FlutterActivity.class, intent);
768 FlutterActivity flutterActivity = activityController.get();
769
770 when(mockHost.getActivity()).thenReturn(flutterActivity);
771 when(mockHost.getInitialRoute()).thenReturn(null);
772 when(mockHost.shouldHandleDeeplinking()).thenReturn(true);
773 // Create the real object that we're testing.
775
776 // --- Execute the behavior under test ---
777 // The FlutterEngine is set up in onAttach().
778 delegate.onAttach(ctx);
779 delegate.onCreateView(null, null, null, 0, true);
780 // Emulate app start.
781 delegate.onStart();
782
783 // Verify that the navigation channel was given the initial route message.
784 verify(mockFlutterEngine.getNavigationChannel(), times(1))
785 .setInitialRoute("http://myApp/custom/route?query=test");
786 }
787
788 @Test
789 public void
791 Intent intent = FlutterActivity.createDefaultIntent(ctx);
792 intent.setData(Uri.parse("http://myApp/custom/route?query=test#fragment"));
793
794 ActivityController<FlutterActivity> activityController =
795 Robolectric.buildActivity(FlutterActivity.class, intent);
796 FlutterActivity flutterActivity = activityController.get();
797
798 when(mockHost.getActivity()).thenReturn(flutterActivity);
799 when(mockHost.getInitialRoute()).thenReturn(null);
800 when(mockHost.shouldHandleDeeplinking()).thenReturn(true);
801 // Create the real object that we're testing.
803
804 // --- Execute the behavior under test ---
805 // The FlutterEngine is set up in onAttach().
806 delegate.onAttach(ctx);
807 delegate.onCreateView(null, null, null, 0, true);
808 // Emulate app start.
809 delegate.onStart();
810
811 // Verify that the navigation channel was given the initial route message.
812 verify(mockFlutterEngine.getNavigationChannel(), times(1))
813 .setInitialRoute("http://myApp/custom/route?query=test#fragment");
814 }
815
816 @Test
817 public void
819 Intent intent = FlutterActivity.createDefaultIntent(ctx);
820 intent.setData(Uri.parse("http://myApp/custom/route#fragment"));
821
822 ActivityController<FlutterActivity> activityController =
823 Robolectric.buildActivity(FlutterActivity.class, intent);
824 FlutterActivity flutterActivity = activityController.get();
825
826 when(mockHost.getActivity()).thenReturn(flutterActivity);
827 when(mockHost.getInitialRoute()).thenReturn(null);
828 when(mockHost.shouldHandleDeeplinking()).thenReturn(true);
829 // Create the real object that we're testing.
831
832 // --- Execute the behavior under test ---
833 // The FlutterEngine is set up in onAttach().
834 delegate.onAttach(ctx);
835 delegate.onCreateView(null, null, null, 0, true);
836 // Emulate app start.
837 delegate.onStart();
838
839 // Verify that the navigation channel was given the initial route message.
840 verify(mockFlutterEngine.getNavigationChannel(), times(1))
841 .setInitialRoute("http://myApp/custom/route#fragment");
842 }
843
844 @Test
845 public void
847 Intent intent = FlutterActivity.createDefaultIntent(ctx);
848 intent.setData(Uri.parse("http://myApp/custom/route"));
849
850 ActivityController<FlutterActivity> activityController =
851 Robolectric.buildActivity(FlutterActivity.class, intent);
852 FlutterActivity flutterActivity = activityController.get();
853
854 when(mockHost.getActivity()).thenReturn(flutterActivity);
855 when(mockHost.getInitialRoute()).thenReturn(null);
856 when(mockHost.shouldHandleDeeplinking()).thenReturn(true);
857 // Create the real object that we're testing.
859
860 // --- Execute the behavior under test ---
861 // The FlutterEngine is set up in onAttach().
862 delegate.onAttach(ctx);
863 delegate.onCreateView(null, null, null, 0, true);
864 // Emulate app start.
865 delegate.onStart();
866
867 // Verify that the navigation channel was given the initial route message.
868 verify(mockFlutterEngine.getNavigationChannel(), times(1))
869 .setInitialRoute("http://myApp/custom/route");
870 }
871
872 @Test
874 // Creates an empty intent without launch uri.
875 Intent intent = FlutterActivity.createDefaultIntent(ctx);
876
877 ActivityController<FlutterActivity> activityController =
878 Robolectric.buildActivity(FlutterActivity.class, intent);
879 FlutterActivity flutterActivity = activityController.get();
880
881 when(mockHost.getActivity()).thenReturn(flutterActivity);
882 when(mockHost.getInitialRoute()).thenReturn(null);
883 when(mockHost.shouldHandleDeeplinking()).thenReturn(true);
884 // Create the real object that we're testing.
886
887 // --- Execute the behavior under test ---
888 // The FlutterEngine is set up in onAttach().
889 delegate.onAttach(ctx);
890 delegate.onCreateView(null, null, null, 0, true);
891 // Emulate app start.
892 delegate.onStart();
893
894 // Verify that the navigation channel was given the default initial route message.
895 verify(mockFlutterEngine.getNavigationChannel(), times(1)).setInitialRoute("/");
896 }
897
898 @Test
900 when(mockHost.shouldHandleDeeplinking()).thenReturn(true);
901 // Create the real object that we're testing.
903
904 // --- Execute the behavior under test ---
905 // The FlutterEngine is set up in onAttach().
906 delegate.onAttach(ctx);
907 String expected = "http://myApp/custom/route?query=test";
908
909 Intent mockIntent = mock(Intent.class);
910 when(mockIntent.getData()).thenReturn(Uri.parse(expected));
911 // Emulate the host and call the method that we expect to be forwarded.
912 delegate.onNewIntent(mockIntent);
913
914 // Verify that the navigation channel was given the push route message.
915 verify(mockFlutterEngine.getNavigationChannel(), times(1)).pushRouteInformation(expected);
916 }
917
918 @Test
920 when(mockHost.shouldHandleDeeplinking()).thenReturn(true);
921 // Create the real object that we're testing.
923
924 // --- Execute the behavior under test ---
925 // The FlutterEngine is set up in onAttach().
926 delegate.onAttach(ctx);
927
928 Intent mockIntent = mock(Intent.class);
929
930 // mailto: URIs are non-hierarchical
931 when(mockIntent.getData()).thenReturn(Uri.parse("mailto:test@test.com"));
932
933 // Emulate the host and call the method
934 delegate.onNewIntent(mockIntent);
935
936 // Verify that the navigation channel was not given a push route message.
937 verify(mockFlutterEngine.getNavigationChannel(), times(1))
938 .pushRouteInformation("mailto:test@test.com");
939 }
940
941 @Test
943 when(mockHost.shouldHandleDeeplinking()).thenReturn(true);
944 // Create the real object that we're testing.
946
947 // --- Execute the behavior under test ---
948 // The FlutterEngine is set up in onAttach().
949 delegate.onAttach(ctx);
950 String expected = "http://myApp/custom/route?query=test#fragment";
951
952 Intent mockIntent = mock(Intent.class);
953 when(mockIntent.getData()).thenReturn(Uri.parse(expected));
954 // Emulate the host and call the method that we expect to be forwarded.
955 delegate.onNewIntent(mockIntent);
956
957 // Verify that the navigation channel was given the push route message.
958 verify(mockFlutterEngine.getNavigationChannel(), times(1)).pushRouteInformation(expected);
959 }
960
961 @Test
963 when(mockHost.shouldHandleDeeplinking()).thenReturn(true);
964 // Create the real object that we're testing.
966
967 // --- Execute the behavior under test ---
968 // The FlutterEngine is set up in onAttach().
969 delegate.onAttach(ctx);
970 String expected = "http://myApp/custom/route#fragment";
971
972 Intent mockIntent = mock(Intent.class);
973 when(mockIntent.getData()).thenReturn(Uri.parse(expected));
974 // Emulate the host and call the method that we expect to be forwarded.
975 delegate.onNewIntent(mockIntent);
976
977 // Verify that the navigation channel was given the push route message.
978 verify(mockFlutterEngine.getNavigationChannel(), times(1)).pushRouteInformation(expected);
979 }
980
981 @Test
983 when(mockHost.shouldHandleDeeplinking()).thenReturn(true);
984 // Create the real object that we're testing.
986
987 // --- Execute the behavior under test ---
988 // The FlutterEngine is set up in onAttach().
989 delegate.onAttach(ctx);
990 String expected = "http://myApp/custom/route#fragment";
991
992 Intent mockIntent = mock(Intent.class);
993 when(mockIntent.getData()).thenReturn(Uri.parse(expected));
994 // Emulate the host and call the method that we expect to be forwarded.
995 delegate.onNewIntent(mockIntent);
996
997 // Verify that the navigation channel was given the push route message.
998 verify(mockFlutterEngine.getNavigationChannel(), times(1)).pushRouteInformation(expected);
999 }
1000
1001 @Test
1003 // Create the real object that we're testing.
1005
1006 // --- Execute the behavior under test ---
1007 // The FlutterEngine is set up in onAttach().
1008 delegate.onAttach(ctx);
1009
1010 // Emulate the host and call the method that we expect to be forwarded.
1011 delegate.onNewIntent(mock(Intent.class));
1012
1013 // Verify that the call was forwarded to the engine.
1014 verify(mockFlutterEngine.getActivityControlSurface(), times(1)).onNewIntent(any(Intent.class));
1015 }
1016
1017 @Test
1019 // Create the real object that we're testing.
1021
1022 // --- Execute the behavior under test ---
1023 // The FlutterEngine is set up in onAttach().
1024 delegate.onAttach(ctx);
1025
1026 // Emulate the host and call the method that we expect to be forwarded.
1027 delegate.onActivityResult(0, 0, null);
1028
1029 // Verify that the call was forwarded to the engine.
1030 verify(mockFlutterEngine.getActivityControlSurface(), times(1))
1031 .onActivityResult(any(Integer.class), any(Integer.class), /*intent=*/ isNull());
1032 }
1033
1034 @Test
1036 // Create the real object that we're testing.
1038
1039 // --- Execute the behavior under test ---
1040 // The FlutterEngine is set up in onAttach().
1041 delegate.onAttach(ctx);
1042
1043 // Emulate the host and call the method that we expect to be forwarded.
1044 delegate.onUserLeaveHint();
1045
1046 // Verify that the call was forwarded to the engine.
1047 verify(mockFlutterEngine.getActivityControlSurface(), times(1)).onUserLeaveHint();
1048 }
1049
1050 @Test
1052 // Create the real object that we're testing.
1054
1055 // --- Execute the behavior under test ---
1056 // The FlutterEngine is set up in onAttach().
1057 delegate.onAttach(ctx);
1058
1059 // Test assumes no frames have been displayed.
1060 verify(mockHost, times(0)).onFlutterUiDisplayed();
1061
1062 // Emulate the host and call the method that we expect to be forwarded.
1063 delegate.onTrimMemory(TRIM_MEMORY_RUNNING_MODERATE);
1064 delegate.onTrimMemory(TRIM_MEMORY_RUNNING_LOW);
1065 verify(mockFlutterEngine.getDartExecutor(), times(0)).notifyLowMemoryWarning();
1066 verify(mockFlutterEngine.getSystemChannel(), times(0)).sendMemoryPressureWarning();
1067
1068 delegate.onTrimMemory(TRIM_MEMORY_RUNNING_CRITICAL);
1069 delegate.onTrimMemory(TRIM_MEMORY_BACKGROUND);
1070 delegate.onTrimMemory(TRIM_MEMORY_COMPLETE);
1071 delegate.onTrimMemory(TRIM_MEMORY_MODERATE);
1072 delegate.onTrimMemory(TRIM_MEMORY_UI_HIDDEN);
1073 verify(mockFlutterEngine.getDartExecutor(), times(0)).notifyLowMemoryWarning();
1074 verify(mockFlutterEngine.getSystemChannel(), times(0)).sendMemoryPressureWarning();
1075
1076 verify(mockHost, times(0)).onFlutterUiDisplayed();
1077
1078 delegate.onCreateView(null, null, null, 0, false);
1079 final FlutterRenderer renderer = mockFlutterEngine.getRenderer();
1080 ArgumentCaptor<FlutterUiDisplayListener> listenerCaptor =
1081 ArgumentCaptor.forClass(FlutterUiDisplayListener.class);
1082 // 2 times: once for engine attachment, once for view creation.
1083 verify(renderer, times(2)).addIsDisplayingFlutterUiListener(listenerCaptor.capture());
1084 listenerCaptor.getValue().onFlutterUiDisplayed();
1085
1086 verify(mockHost, times(1)).onFlutterUiDisplayed();
1087
1088 delegate.onTrimMemory(TRIM_MEMORY_RUNNING_MODERATE);
1089 verify(mockFlutterEngine.getDartExecutor(), times(0)).notifyLowMemoryWarning();
1090 verify(mockFlutterEngine.getSystemChannel(), times(0)).sendMemoryPressureWarning();
1091
1092 delegate.onTrimMemory(TRIM_MEMORY_RUNNING_LOW);
1093 delegate.onTrimMemory(TRIM_MEMORY_RUNNING_CRITICAL);
1094 delegate.onTrimMemory(TRIM_MEMORY_BACKGROUND);
1095 delegate.onTrimMemory(TRIM_MEMORY_COMPLETE);
1096 delegate.onTrimMemory(TRIM_MEMORY_MODERATE);
1097 delegate.onTrimMemory(TRIM_MEMORY_UI_HIDDEN);
1098 verify(mockFlutterEngine.getDartExecutor(), times(6)).notifyLowMemoryWarning();
1099 verify(mockFlutterEngine.getSystemChannel(), times(6)).sendMemoryPressureWarning();
1100 }
1101
1102 @Test
1104 // ---- Test setup ----
1105 // Adjust fake host to request engine destruction.
1106 when(mockHost.shouldDestroyEngineWithHost()).thenReturn(true);
1107
1108 // Create the real object that we're testing.
1110
1111 // --- Execute the behavior under test ---
1112 // Push the delegate through all lifecycle methods all the way to destruction.
1113 delegate.onAttach(ctx);
1114 delegate.onCreateView(null, null, null, 0, true);
1115 delegate.onStart();
1116 delegate.onResume();
1117 delegate.onPause();
1118 delegate.onStop();
1119 delegate.onDestroyView();
1120 delegate.onDetach();
1121
1122 // --- Verify that the cached engine was destroyed ---
1123 verify(mockFlutterEngine, times(1)).destroy();
1124 }
1125
1126 @Test
1128 // ---- Test setup ----
1129 // Adjust fake host to request engine destruction.
1130 when(mockHost.shouldDestroyEngineWithHost()).thenReturn(false);
1131
1132 // Create the real object that we're testing.
1134
1135 // --- Execute the behavior under test ---
1136 // Push the delegate through all lifecycle methods all the way to destruction.
1137 delegate.onAttach(ctx);
1138 delegate.onCreateView(null, null, null, 0, true);
1139 delegate.onStart();
1140 delegate.onResume();
1141 delegate.onPause();
1142 delegate.onStop();
1143 delegate.onDestroyView();
1144 delegate.onDetach();
1145
1146 // --- Verify that the cached engine was destroyed ---
1147 verify(mockFlutterEngine, never()).destroy();
1148 }
1149
1150 @Test
1152 // ---- Test setup ----
1153 // Place a FlutterEngine in the static cache.
1154 FlutterEngine cachedEngine = mockFlutterEngine();
1155 FlutterEngineCache.getInstance().put("my_flutter_engine", cachedEngine);
1156
1157 // Adjust fake host to request cached engine.
1158 when(mockHost.getCachedEngineId()).thenReturn("my_flutter_engine");
1159
1160 // Adjust fake host to request engine destruction.
1161 when(mockHost.shouldDestroyEngineWithHost()).thenReturn(true);
1162
1163 // Create the real object that we're testing.
1165
1166 // --- Execute the behavior under test ---
1167 // Push the delegate through all lifecycle methods all the way to destruction.
1168 delegate.onAttach(ctx);
1169 delegate.onCreateView(null, null, null, 0, true);
1170 delegate.onStart();
1171 delegate.onResume();
1172 delegate.onPause();
1173 delegate.onStop();
1174 delegate.onDestroyView();
1175 delegate.onDetach();
1176
1177 // --- Verify that the cached engine was destroyed ---
1178 verify(cachedEngine, times(1)).destroy();
1179 assertNull(FlutterEngineCache.getInstance().get("my_flutter_engine"));
1180 }
1181
1182 @Test
1184 // ---- Test setup ----
1185 // Place a FlutterEngine in the static cache.
1186 FlutterEngine cachedEngine = mockFlutterEngine();
1187 FlutterEngineCache.getInstance().put("my_flutter_engine", cachedEngine);
1188
1189 // Adjust fake host to request cached engine.
1190 when(mockHost.getCachedEngineId()).thenReturn("my_flutter_engine");
1191
1192 // Adjust fake host to request engine retention.
1193 when(mockHost.shouldDestroyEngineWithHost()).thenReturn(false);
1194
1195 // Create the real object that we're testing.
1197
1198 // --- Execute the behavior under test ---
1199 // Push the delegate through all lifecycle methods all the way to destruction.
1200 delegate.onAttach(ctx);
1201 delegate.onCreateView(null, null, null, 0, true);
1202 delegate.onStart();
1203 delegate.onResume();
1204 delegate.onPause();
1205 delegate.onStop();
1206 delegate.onDestroyView();
1207 delegate.onDetach();
1208
1209 // --- Verify that the cached engine was NOT destroyed ---
1210 verify(cachedEngine, never()).destroy();
1211 }
1212
1213 @Test
1215 // ---- Test setup ----
1217
1218 // We're testing lifecycle behaviors, which require/expect that certain methods have already
1219 // been executed by the time they run. Therefore, we run those expected methods first.
1220 delegate.onAttach(ctx);
1221
1222 // --- Execute the behavior under test ---
1223 boolean shouldDelayFirstAndroidViewDraw = true;
1224 delegate.onCreateView(null, null, null, 0, shouldDelayFirstAndroidViewDraw);
1225
1226 assertNotNull(delegate.activePreDrawListener);
1227 }
1228
1229 @Test
1231 // ---- Test setup ----
1233
1234 // We're testing lifecycle behaviors, which require/expect that certain methods have already
1235 // been executed by the time they run. Therefore, we run those expected methods first.
1236 delegate.onAttach(ctx);
1237
1238 // --- Execute the behavior under test ---
1239 boolean shouldDelayFirstAndroidViewDraw = false;
1240 delegate.onCreateView(null, null, null, 0, shouldDelayFirstAndroidViewDraw);
1241
1242 assertNull(delegate.activePreDrawListener);
1243 }
1244
1245 @Test
1247 // ---- Test setup ----
1248 when(mockHost.getRenderMode()).thenReturn(RenderMode.texture);
1250
1251 // We're testing lifecycle behaviors, which require/expect that certain methods have already
1252 // been executed by the time they run. Therefore, we run those expected methods first.
1253 delegate.onAttach(ctx);
1254
1255 // --- Execute the behavior under test ---
1256 boolean shouldDelayFirstAndroidViewDraw = true;
1257 assertThrows(
1258 IllegalArgumentException.class,
1259 () -> {
1260 delegate.onCreateView(null, null, null, 0, shouldDelayFirstAndroidViewDraw);
1261 });
1262 }
1263
1264 @Test
1266 // ---- Test setup ----
1267 // Create the real object that we're testing.
1269
1270 // --- Execute the behavior under test ---
1271 delegate.onAttach(ctx);
1272 delegate.onCreateView(null, null, null, 0, true);
1273 delegate.onStart();
1274 // Verify that the flutterView is visible.
1275 assertEquals(View.VISIBLE, delegate.flutterView.getVisibility());
1276 delegate.onStop();
1277 // Verify that the flutterView is gone.
1278 assertEquals(View.GONE, delegate.flutterView.getVisibility());
1279 delegate.onStart();
1280 // Verify that the flutterView is visible.
1281 assertEquals(View.VISIBLE, delegate.flutterView.getVisibility());
1282
1283 delegate.flutterView.setVisibility(View.INVISIBLE);
1284 delegate.onStop();
1285 // Verify that the flutterView is gone.
1286 assertEquals(View.GONE, delegate.flutterView.getVisibility());
1287 delegate.onStart();
1288 // Verify that the flutterView is invisible.
1289 assertEquals(View.INVISIBLE, delegate.flutterView.getVisibility());
1290
1291 delegate.flutterView.setVisibility(View.GONE);
1292 delegate.onStop();
1293 // Verify that the flutterView is gone.
1294 assertEquals(View.GONE, delegate.flutterView.getVisibility());
1295 delegate.onStart();
1296 // Verify that the flutterView is gone.
1297 assertEquals(View.GONE, delegate.flutterView.getVisibility());
1298 }
1299
1300 @Test
1302 // ---- Test setup ----
1303 // Create the real object that we're testing.
1305 delegate.onAttach(ctx);
1306 delegate.onCreateView(null, null, null, 0, true);
1307 // --- Execute the behavior under test ---
1308 // For `FlutterSurfaceView`, setting visibility to the current `FlutterView` will not take
1309 // effect since it is not in the view tree. So we need to make sure that when the visibility of
1310 // `FlutterView` changes, the `FlutterSurfaceView` changes at the same time
1311 // See https://github.com/flutter/flutter/issues/105203
1312 assertEquals(FlutterSurfaceView.class, delegate.flutterView.renderSurface.getClass());
1314 // Verify that the `FlutterSurfaceView` is gone.
1315 delegate.flutterView.setVisibility(View.GONE);
1316 assertEquals(View.GONE, surfaceView.getVisibility());
1317 // Verify that the `FlutterSurfaceView` is visible.
1318 delegate.flutterView.setVisibility(View.VISIBLE);
1319 assertEquals(View.VISIBLE, surfaceView.getVisibility());
1320 // Verify that the `FlutterSurfaceView` is invisible.
1321 delegate.flutterView.setVisibility(View.INVISIBLE);
1322 assertEquals(View.INVISIBLE, surfaceView.getVisibility());
1323 }
1324
1325 @Test
1327 FlutterEngineGroup mockEngineGroup = mock(FlutterEngineGroup.class);
1328 when(mockEngineGroup.createAndRunEngine(any(FlutterEngineGroup.Options.class)))
1329 .thenReturn(mockFlutterEngine);
1330 FlutterActivityAndFragmentDelegate.Host host =
1332 when(mockHost.getContext()).thenReturn(ctx);
1333
1335 new FlutterActivityAndFragmentDelegate(mockHost, mockEngineGroup);
1336 delegate.onAttach(ctx);
1337 FlutterEngine engineUnderTest = delegate.getFlutterEngine();
1338 assertEquals(engineUnderTest, mockFlutterEngine);
1339 }
1340
1341 @Test
1343 // ---- Test setup ----
1344 // Create the real object that we're testing.
1346 delegate.onAttach(ctx);
1347 delegate.onCreateView(null, null, null, 0, true);
1348
1349 // --- Execute the behavior under test ---
1350 assertTrue(delegate.flutterView.isAttachedToFlutterEngine());
1351 }
1352
1353 @Test
1355 // ---- Test setup ----
1356 // Create the real object that we're testing.
1357 when(mockHost.attachToEngineAutomatically()).thenReturn(false);
1359 delegate.onAttach(ctx);
1360 delegate.onCreateView(null, null, null, 0, true);
1361
1362 // --- Execute the behavior under test ---
1363 assertFalse(delegate.flutterView.isAttachedToFlutterEngine());
1364 }
1365
1366 @Test
1367 public void itDoesNotDetachTwice() {
1368 FlutterEngine cachedEngine = mockFlutterEngine();
1369 FlutterEngineCache.getInstance().put("my_flutter_engine", cachedEngine);
1370
1371 // Engine is a cached singleton that isn't owned by either hosts.
1372 when(mockHost.shouldDestroyEngineWithHost()).thenReturn(false);
1373 when(mockHost2.shouldDestroyEngineWithHost()).thenReturn(false);
1374
1375 // Adjust fake hosts to request cached engine.
1376 when(mockHost.getCachedEngineId()).thenReturn("my_flutter_engine");
1377 when(mockHost2.getCachedEngineId()).thenReturn("my_flutter_engine");
1378
1379 // Create the real objects that we're testing.
1383
1384 // This test is written to recreate the following scenario:
1385 // 1. We have a FlutterFragment_A attached to a singleton cached engine.
1386 // 2. An intent arrives that spawns FlutterFragment_B.
1387 // 3. FlutterFragment_B starts and steals the engine from FlutterFragment_A while attaching.
1388 // Via a call to FlutterActivityAndFragmentDelegate.detachFromFlutterEngine().
1389 // 4. FlutterFragment_A is forcibly detached from the engine.
1390 // 5. FlutterFragment_B is attached to the engine.
1391 // 6. FlutterFragment_A is detached from the engine.
1392 // Note that the second detach for FlutterFragment_A is done unconditionally when the Fragment
1393 // is being
1394 // torn down.
1395
1396 // At this point the engine's life cycle channel receives a message (triggered by
1397 // FlutterFragment_A's second detach)
1398 // that indicates the app is detached. This breaks FlutterFragment_B.
1399
1400 // Below is a sequence of calls that mimicks the calls that the above scenario would trigger
1401 // without
1402 // relying on an intent to trigger the behaviour.
1403
1404 // FlutterFragment_A is attached to the engine.
1405 delegate.onAttach(ctx);
1406
1407 // NOTE: The following two calls happen in a slightly different order in reality. That is, via,
1408 // a call to host.detachFromFlutterEngine, delegate2.onAttach ends up invoking
1409 // delegate.onDetach.
1410 // To keep this regression test simple, we call them directly.
1411
1412 // Detach FlutterFragment_A.
1413 delegate.onDetach();
1414
1415 verify(cachedEngine.getLifecycleChannel(), times(1)).appIsDetached();
1416
1417 // Attaches to the engine FlutterFragment_B.
1418 delegate2.onAttach(ctx);
1419 delegate2.onResume();
1420
1421 verify(cachedEngine.getLifecycleChannel(), times(1)).appIsResumed();
1422 verify(cachedEngine.getLifecycleChannel(), times(1)).appIsDetached();
1423
1424 // A second Detach of FlutterFragment_A happens when the Fragment is detached.
1425 delegate.onDetach();
1426
1427 // IMPORTANT: The bug we fixed would have resulted in the engine thinking the app
1428 // is detached twice instead of once.
1429 verify(cachedEngine.getLifecycleChannel(), times(1)).appIsDetached();
1430 }
1431
1432 /**
1433 * Creates a mock {@link io.flutter.embedding.engine.FlutterEngine}.
1434 *
1435 * <p>The heuristic for deciding what to mock in the given {@link
1436 * io.flutter.embedding.engine.FlutterEngine} is that we should mock the minimum number of
1437 * necessary methods and associated objects. Maintaining developers should add more mock behavior
1438 * as required for tests, but should avoid mocking things that are not required for the correct
1439 * execution of tests.
1440 */
1441 @NonNull
1442 private FlutterEngine mockFlutterEngine() {
1443 // The use of SettingsChannel by the delegate requires some behavior of its own, so it is
1444 // explicitly mocked with some internal behavior.
1445 SettingsChannel fakeSettingsChannel = mock(SettingsChannel.class);
1446 SettingsChannel.MessageBuilder fakeMessageBuilder = mock(SettingsChannel.MessageBuilder.class);
1447 when(fakeMessageBuilder.setPlatformBrightness(any(SettingsChannel.PlatformBrightness.class)))
1448 .thenReturn(fakeMessageBuilder);
1449 when(fakeMessageBuilder.setTextScaleFactor(any(Float.class))).thenReturn(fakeMessageBuilder);
1450 when(fakeMessageBuilder.setDisplayMetrics(any())).thenReturn(fakeMessageBuilder);
1451 when(fakeMessageBuilder.setNativeSpellCheckServiceDefined(any(Boolean.class)))
1452 .thenReturn(fakeMessageBuilder);
1453 when(fakeMessageBuilder.setBrieflyShowPassword(any(Boolean.class)))
1454 .thenReturn(fakeMessageBuilder);
1455 when(fakeMessageBuilder.setUse24HourFormat(any(Boolean.class))).thenReturn(fakeMessageBuilder);
1456 when(fakeSettingsChannel.startMessage()).thenReturn(fakeMessageBuilder);
1457
1458 // Mock FlutterEngine and all of its required direct calls.
1459 FlutterEngine engine = mock(FlutterEngine.class);
1460 when(engine.getAccessibilityChannel()).thenReturn(mock(AccessibilityChannel.class));
1461 when(engine.getActivityControlSurface()).thenReturn(mock(ActivityControlSurface.class));
1462 when(engine.getDartExecutor()).thenReturn(mock(DartExecutor.class));
1463 when(engine.getLifecycleChannel()).thenReturn(mock(LifecycleChannel.class));
1464 when(engine.getLocalizationChannel()).thenReturn(mock(LocalizationChannel.class));
1465 when(engine.getLocalizationPlugin()).thenReturn(mock(LocalizationPlugin.class));
1466 when(engine.getMouseCursorChannel()).thenReturn(mock(MouseCursorChannel.class));
1467 when(engine.getNavigationChannel()).thenReturn(mock(NavigationChannel.class));
1468 when(engine.getBackGestureChannel()).thenReturn(mock(BackGestureChannel.class));
1469 when(engine.getPlatformViewsController()).thenReturn(mock(PlatformViewsController.class));
1470
1471 FlutterRenderer renderer = mock(FlutterRenderer.class);
1472 when(engine.getRenderer()).thenReturn(renderer);
1473
1474 when(engine.getSettingsChannel()).thenReturn(fakeSettingsChannel);
1475 when(engine.getSystemChannel()).thenReturn(mock(SystemChannel.class));
1476 when(engine.getTextInputChannel()).thenReturn(mock(TextInputChannel.class));
1477
1478 return engine;
1479 }
1480}
static SkISize times(const SkISize &size, float factor)
static bool eq(const SkM44 &a, const SkM44 &b, float tol)
Definition M44Test.cpp:18
void add(sk_sp< SkIDChangeListener > listener) SK_EXCLUDES(fMutex)
Builder setFlutterLoader(@NonNull FlutterLoader flutterLoader)
static void setInstance(@NonNull FlutterInjector injector)
View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState, int flutterViewId, boolean shouldDelayFirstAndroidViewDraw)
void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults)
static Intent createDefaultIntent(@NonNull Context launchContext)
FlutterEngine engine
Definition main.cc:68