Flutter Engine
The Flutter Engine
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 ----
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
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
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)
void put(@NonNull String engineId, @Nullable FlutterEngine engine)
FlutterEngine get(@NonNull String engineId)
void put(@NonNull String engineGroupId, @Nullable FlutterEngineGroup engineGroup)
FlutterEngine createAndRunEngine( @NonNull Context context, @Nullable DartEntrypoint dartEntrypoint)
PlatformViewsController getPlatformViewsController()
ActivityControlSurface getActivityControlSurface()
AccessibilityChannel getAccessibilityChannel()
void addIsDisplayingFlutterUiListener(@NonNull FlutterUiDisplayListener listener)
FlutterEngine engine
Definition: main.cc:68
FlutterEngine provideFlutterEngine(@NonNull Context context)
DEF_SWITCHES_START aot vmservice shared library Name of the *so containing AOT compiled Dart assets for launching the service isolate vm snapshot The VM snapshot data that will be memory mapped as read only SnapshotAssetPath must be present isolate snapshot The isolate snapshot data that will be memory mapped as read only SnapshotAssetPath must be present cache dir Path to the cache directory This is different from the persistent_cache_path in embedder which is used for Skia shader cache icu native lib Path to the library file that exports the ICU data vm service host
Definition: switches.h:74
SIT bool any(const Vec< 1, T > &x)
Definition: SkVx.h:530