Flutter Engine
The Flutter Engine
Loading...
Searching...
No Matches
FlutterActivityTest.java
Go to the documentation of this file.
1package io.flutter.embedding.android;
2
3import static io.flutter.Build.API_LEVELS;
4import static io.flutter.embedding.android.FlutterActivityLaunchConfigs.HANDLE_DEEPLINKING_META_DATA_KEY;
5import static org.junit.Assert.assertArrayEquals;
6import static org.junit.Assert.assertEquals;
7import static org.junit.Assert.assertFalse;
8import static org.junit.Assert.assertNotNull;
9import static org.junit.Assert.assertNull;
10import static org.junit.Assert.assertTrue;
11import static org.mockito.Mockito.doAnswer;
12import static org.mockito.Mockito.mock;
13import static org.mockito.Mockito.never;
14import static org.mockito.Mockito.spy;
15import static org.mockito.Mockito.times;
16import static org.mockito.Mockito.verify;
17import static org.mockito.Mockito.when;
18
19import android.annotation.SuppressLint;
20import android.annotation.TargetApi;
21import android.content.Context;
22import android.content.Intent;
23import android.content.pm.PackageManager;
24import android.os.Bundle;
25import android.window.BackEvent;
26import android.window.OnBackAnimationCallback;
27import android.window.OnBackInvokedCallback;
28import androidx.annotation.NonNull;
29import androidx.annotation.Nullable;
30import androidx.annotation.RequiresApi;
31import androidx.lifecycle.DefaultLifecycleObserver;
32import androidx.lifecycle.LifecycleOwner;
33import androidx.test.core.app.ApplicationProvider;
34import androidx.test.ext.junit.runners.AndroidJUnit4;
35import io.flutter.FlutterInjector;
36import io.flutter.embedding.android.FlutterActivityLaunchConfigs.BackgroundMode;
37import io.flutter.embedding.engine.FlutterEngine;
38import io.flutter.embedding.engine.FlutterEngineCache;
39import io.flutter.embedding.engine.FlutterJNI;
40import io.flutter.embedding.engine.loader.FlutterLoader;
41import io.flutter.embedding.engine.plugins.FlutterPlugin;
42import io.flutter.embedding.engine.plugins.activity.ActivityAware;
43import io.flutter.embedding.engine.plugins.activity.ActivityPluginBinding;
44import io.flutter.embedding.engine.plugins.activity.ActivityPluginBinding.OnSaveInstanceStateListener;
45import io.flutter.plugins.GeneratedPluginRegistrant;
46import java.util.ArrayList;
47import java.util.Arrays;
48import java.util.List;
49import org.junit.After;
50import org.junit.Before;
51import org.junit.Test;
52import org.junit.runner.RunWith;
53import org.robolectric.Robolectric;
54import org.robolectric.android.controller.ActivityController;
55import org.robolectric.annotation.Config;
56
57@Config(manifest = Config.NONE)
58@RunWith(AndroidJUnit4.class)
59public class FlutterActivityTest {
60 private final Context ctx = ApplicationProvider.getApplicationContext();
62
63 @Before
64 public void setUp() {
66 GeneratedPluginRegistrant.clearRegisteredEngines();
67 FlutterJNI mockFlutterJNI = mock(FlutterJNI.class);
68 when(mockFlutterJNI.isAttached()).thenReturn(true);
69 FlutterJNI.Factory mockFlutterJNIFactory = mock(FlutterJNI.Factory.class);
70 when(mockFlutterJNIFactory.provideFlutterJNI()).thenReturn(mockFlutterJNI);
72 new FlutterInjector.Builder().setFlutterJNIFactory(mockFlutterJNIFactory).build());
73 }
74
75 @After
76 public void tearDown() {
77 GeneratedPluginRegistrant.clearRegisteredEngines();
79 }
80
81 @Test
82 public void flutterViewHasId() {
83 Intent intent = FlutterActivity.createDefaultIntent(ctx);
84 ActivityController<FlutterActivity> activityController =
85 Robolectric.buildActivity(FlutterActivity.class, intent);
86 FlutterActivity activity = activityController.get();
87
88 activity.onCreate(null);
89 assertNotNull(activity.findViewById(FlutterActivity.FLUTTER_VIEW_ID));
90 assertTrue(activity.findViewById(FlutterActivity.FLUTTER_VIEW_ID) instanceof FlutterView);
91 }
92
93 // TODO(garyq): Robolectric does not yet support android api 33 yet. Switch to a robolectric
94 // test that directly exercises the OnBackInvoked APIs when API 33 is supported.
95 @Test
96 @TargetApi(API_LEVELS.API_33)
97 public void itRegistersOnBackInvokedCallbackOnChangingFrameworkHandlesBack() {
98 Intent intent = FlutterActivityWithReportFullyDrawn.createDefaultIntent(ctx);
99 ActivityController<FlutterActivityWithReportFullyDrawn> activityController =
100 Robolectric.buildActivity(FlutterActivityWithReportFullyDrawn.class, intent);
101 FlutterActivityWithReportFullyDrawn activity = spy(activityController.get());
102
103 activity.onCreate(null);
104
105 verify(activity, times(0)).registerOnBackInvokedCallback();
106
107 activity.setFrameworkHandlesBack(false);
108 verify(activity, times(0)).registerOnBackInvokedCallback();
109
110 activity.setFrameworkHandlesBack(true);
111 verify(activity, times(1)).registerOnBackInvokedCallback();
112 }
113
114 // TODO(garyq): Robolectric does not yet support android api 33 yet. Switch to a robolectric
115 // test that directly exercises the OnBackInvoked APIs when API 33 is supported.
116 @Test
117 @TargetApi(API_LEVELS.API_33)
118 public void itUnregistersOnBackInvokedCallbackOnRelease() {
119 Intent intent = FlutterActivityWithReportFullyDrawn.createDefaultIntent(ctx);
120 ActivityController<FlutterActivityWithReportFullyDrawn> activityController =
121 Robolectric.buildActivity(FlutterActivityWithReportFullyDrawn.class, intent);
122 FlutterActivityWithReportFullyDrawn activity = spy(activityController.get());
123
124 activity.release();
125
126 verify(activity, times(1)).unregisterOnBackInvokedCallback();
127 }
128
129 @Test
130 @Config(sdk = API_LEVELS.API_32)
131 public void onBackInvokedCallbackIsNullForSdk32OrLower() {
132 Intent intent = FlutterActivity.createDefaultIntent(ctx);
133 ActivityController<FlutterActivity> activityController =
134 Robolectric.buildActivity(FlutterActivity.class, intent);
135 FlutterActivity flutterActivity = activityController.get();
136
137 assertNull(
138 "onBackInvokedCallback should be null for SDK 32 or lower",
139 flutterActivity.getOnBackInvokedCallback());
140 }
141
142 @Test
143 @Config(sdk = API_LEVELS.API_33)
144 @TargetApi(API_LEVELS.API_33)
145 public void onBackInvokedCallbackCallsOnBackPressedForSdk33() {
146 Intent intent = FlutterActivityWithMockBackInvokedHandling.createDefaultIntent(ctx);
147 ActivityController<FlutterActivityWithMockBackInvokedHandling> activityController =
148 Robolectric.buildActivity(FlutterActivityWithMockBackInvokedHandling.class, intent);
149 FlutterActivityWithMockBackInvokedHandling activity = activityController.get();
150
151 OnBackInvokedCallback callback = activity.getOnBackInvokedCallback();
152 assertNotNull("onBackInvokedCallback should not be null for SDK 33", callback);
153
154 callback.onBackInvoked();
155 assertEquals("Expected onBackPressed to be called 1 times", 1, activity.onBackPressedCounter);
156 }
157
158 @Test
159 @Config(sdk = API_LEVELS.API_34)
160 @TargetApi(API_LEVELS.API_34)
161 public void itHandlesOnBackAnimationCallbackAsExpectedForSdk34OrHigher() {
162 Intent intent = FlutterActivityWithMockBackInvokedHandling.createDefaultIntent(ctx);
163 ActivityController<FlutterActivityWithMockBackInvokedHandling> activityController =
164 Robolectric.buildActivity(FlutterActivityWithMockBackInvokedHandling.class, intent);
165 FlutterActivityWithMockBackInvokedHandling activity = activityController.get();
166
167 assertTrue(
168 "onBackInvokedCallback should be an instance of OnBackAnimationCallback for SDK 34 or higher",
169 activity.getOnBackInvokedCallback() instanceof OnBackAnimationCallback);
170
171 OnBackAnimationCallback callback =
172 (OnBackAnimationCallback) activity.getOnBackInvokedCallback();
173
174 BackEvent mockBackEvent = mock(BackEvent.class);
175 callback.onBackStarted(mockBackEvent);
176 assertEquals(
177 "Expected startBackGesture to be called 1 times", 1, activity.startBackGestureCounter);
178
179 callback.onBackProgressed(mockBackEvent);
180 assertEquals(
181 "Expected updateBackGestureProgress to be called 1 times",
182 1,
183 activity.updateBackGestureProgressCounter);
184
185 callback.onBackInvoked();
186 assertEquals(
187 "Expected commitBackGesture to be called 1 times", 1, activity.commitBackGestureCounter);
188
189 callback.onBackCancelled();
190 assertEquals(
191 "Expected cancelBackGesture to be called 1 times", 1, activity.cancelBackGestureCounter);
192 }
193
194 @Test
196 Intent intent = FlutterActivity.createDefaultIntent(ctx);
197 ActivityController<FlutterActivity> activityController =
198 Robolectric.buildActivity(FlutterActivity.class, intent);
199 FlutterActivity flutterActivity = activityController.get();
200 flutterActivity.setDelegate(new FlutterActivityAndFragmentDelegate(flutterActivity));
201
202 assertEquals("main", flutterActivity.getDartEntrypointFunctionName());
203 assertNull(flutterActivity.getDartEntrypointLibraryUri());
204 assertNull(flutterActivity.getDartEntrypointArgs());
205 assertEquals("/", flutterActivity.getInitialRoute());
206 assertArrayEquals(new String[] {}, flutterActivity.getFlutterShellArgs().toArray());
207 assertTrue(flutterActivity.shouldAttachEngineToActivity());
208 assertNull(flutterActivity.getCachedEngineId());
209 assertTrue(flutterActivity.shouldDestroyEngineWithHost());
210 assertEquals(BackgroundMode.opaque, flutterActivity.getBackgroundMode());
211 assertEquals(RenderMode.surface, flutterActivity.getRenderMode());
212 assertEquals(TransparencyMode.opaque, flutterActivity.getTransparencyMode());
213 }
214
215 @Test
217 // All clients should use the static members of FlutterActivity to construct an
218 // Intent. Missing extras is an error. However, Flutter has number of tests that
219 // don't seem to use the static members of FlutterActivity to construct the
220 // launching Intent, so this test explicitly verifies that even illegal Intents
221 // result in the automatic destruction of a non-cached FlutterEngine, which prevents
222 // the breakage of memory usage benchmark tests.
223 Intent intent = new Intent(ctx, FlutterActivity.class);
224 ActivityController<FlutterActivity> activityController =
225 Robolectric.buildActivity(FlutterActivity.class, intent);
226 FlutterActivity flutterActivity = activityController.get();
227 flutterActivity.setDelegate(new FlutterActivityAndFragmentDelegate(flutterActivity));
228
229 assertTrue(flutterActivity.shouldDestroyEngineWithHost());
230 }
231
232 @Test
234 Intent intent = new Intent(ctx, FlutterActivityWithProvidedEngine.class);
235 ActivityController<FlutterActivityWithProvidedEngine> activityController =
236 Robolectric.buildActivity(FlutterActivityWithProvidedEngine.class, intent);
237 activityController.create();
238 FlutterActivityWithProvidedEngine flutterActivity = activityController.get();
239
240 assertFalse(flutterActivity.shouldDestroyEngineWithHost());
241 }
242
243 @Test
245 Intent intent =
247 .initialRoute("/custom/route")
248 .dartEntrypointArgs(new ArrayList<String>(Arrays.asList("foo", "bar")))
249 .backgroundMode(BackgroundMode.transparent)
250 .build(ctx);
251 ActivityController<FlutterActivity> activityController =
252 Robolectric.buildActivity(FlutterActivity.class, intent);
253 FlutterActivity flutterActivity = activityController.get();
254 flutterActivity.setDelegate(new FlutterActivityAndFragmentDelegate(flutterActivity));
255
256 assertEquals("/custom/route", flutterActivity.getInitialRoute());
257 assertArrayEquals(
258 new String[] {"foo", "bar"}, flutterActivity.getDartEntrypointArgs().toArray());
259 assertArrayEquals(new String[] {}, flutterActivity.getFlutterShellArgs().toArray());
260 assertTrue(flutterActivity.shouldAttachEngineToActivity());
261 assertNull(flutterActivity.getCachedEngineId());
262 assertTrue(flutterActivity.shouldDestroyEngineWithHost());
263 assertEquals(BackgroundMode.transparent, flutterActivity.getBackgroundMode());
264 assertEquals(RenderMode.texture, flutterActivity.getRenderMode());
265 assertEquals(TransparencyMode.transparent, flutterActivity.getTransparencyMode());
266 }
267
268 @Test
270 Intent intent =
271 FlutterActivity.withNewEngineInGroup("my_cached_engine_group")
272 .dartEntrypoint("custom_entrypoint")
273 .initialRoute("/custom/route")
274 .backgroundMode(BackgroundMode.transparent)
275 .build(ctx);
276 ActivityController<FlutterActivity> activityController =
277 Robolectric.buildActivity(FlutterActivity.class, intent);
278 FlutterActivity flutterActivity = activityController.get();
279 flutterActivity.setDelegate(new FlutterActivityAndFragmentDelegate(flutterActivity));
280
281 assertEquals("my_cached_engine_group", flutterActivity.getCachedEngineGroupId());
282 assertEquals("custom_entrypoint", flutterActivity.getDartEntrypointFunctionName());
283 assertEquals("/custom/route", flutterActivity.getInitialRoute());
284 assertArrayEquals(new String[] {}, flutterActivity.getFlutterShellArgs().toArray());
285 assertTrue(flutterActivity.shouldAttachEngineToActivity());
286 assertTrue(flutterActivity.shouldDestroyEngineWithHost());
287 assertNull(flutterActivity.getCachedEngineId());
288 assertEquals(BackgroundMode.transparent, flutterActivity.getBackgroundMode());
289 assertEquals(RenderMode.texture, flutterActivity.getRenderMode());
290 assertEquals(TransparencyMode.transparent, flutterActivity.getTransparencyMode());
291 }
292
293 @Test
295 throws PackageManager.NameNotFoundException {
296 Intent intent =
297 FlutterActivity.withNewEngine().backgroundMode(BackgroundMode.transparent).build(ctx);
298 ActivityController<FlutterActivity> activityController =
299 Robolectric.buildActivity(FlutterActivity.class, intent);
300 FlutterActivity flutterActivity = activityController.get();
301 Bundle bundle = new Bundle();
302 bundle.putBoolean(HANDLE_DEEPLINKING_META_DATA_KEY, true);
303 FlutterActivity spyFlutterActivity = spy(flutterActivity);
304 when(spyFlutterActivity.getMetaData()).thenReturn(bundle);
305 assertTrue(spyFlutterActivity.shouldHandleDeeplinking());
306 }
307
308 @Test
310 throws PackageManager.NameNotFoundException {
311 Intent intent =
312 FlutterActivity.withNewEngine().backgroundMode(BackgroundMode.transparent).build(ctx);
313 ActivityController<FlutterActivity> activityController =
314 Robolectric.buildActivity(FlutterActivity.class, intent);
315 FlutterActivity flutterActivity = activityController.get();
316 Bundle bundle = new Bundle();
317 bundle.putBoolean(HANDLE_DEEPLINKING_META_DATA_KEY, false);
318 FlutterActivity spyFlutterActivity = spy(flutterActivity);
319 when(spyFlutterActivity.getMetaData()).thenReturn(bundle);
320 assertFalse(spyFlutterActivity.shouldHandleDeeplinking());
321 }
322
323 @Test
325 throws PackageManager.NameNotFoundException {
326 Intent intent =
327 FlutterActivity.withNewEngine().backgroundMode(BackgroundMode.transparent).build(ctx);
328 ActivityController<FlutterActivity> activityController =
329 Robolectric.buildActivity(FlutterActivity.class, intent);
330 FlutterActivity flutterActivity = activityController.get();
331 // Creates an empty bundle.
332 Bundle bundle = new Bundle();
333 FlutterActivity spyFlutterActivity = spy(flutterActivity);
334 when(spyFlutterActivity.getMetaData()).thenReturn(bundle);
335 // Empty bundle should return false.
336 assertFalse(spyFlutterActivity.shouldHandleDeeplinking());
337 }
338
339 @Test
341 Intent intent =
342 FlutterActivity.withCachedEngine("my_cached_engine")
343 .destroyEngineWithActivity(false)
344 .build(ctx);
345 ActivityController<FlutterActivity> activityController =
346 Robolectric.buildActivity(FlutterActivity.class, intent);
347 FlutterActivity flutterActivity = activityController.get();
348
349 assertArrayEquals(new String[] {}, flutterActivity.getFlutterShellArgs().toArray());
350 assertTrue(flutterActivity.shouldAttachEngineToActivity());
351 assertEquals("my_cached_engine", flutterActivity.getCachedEngineId());
352 assertFalse(flutterActivity.shouldDestroyEngineWithHost());
353 }
354
355 @Test
357 Intent intent =
358 FlutterActivity.withCachedEngine("my_cached_engine")
359 .destroyEngineWithActivity(true)
360 .build(ctx);
361 ActivityController<FlutterActivity> activityController =
362 Robolectric.buildActivity(FlutterActivity.class, intent);
363 FlutterActivity flutterActivity = activityController.get();
364
365 assertArrayEquals(new String[] {}, flutterActivity.getFlutterShellArgs().toArray());
366 assertTrue(flutterActivity.shouldAttachEngineToActivity());
367 assertEquals("my_cached_engine", flutterActivity.getCachedEngineId());
368 assertTrue(flutterActivity.shouldDestroyEngineWithHost());
369 }
370
371 @Test
373 Intent intent = FlutterActivity.createDefaultIntent(ctx);
374 ActivityController<FlutterActivity> activityController =
375 Robolectric.buildActivity(FlutterActivity.class, intent);
376 FlutterActivity activity = activityController.get();
377
378 // This calls onAttach on FlutterActivityAndFragmentDelegate and subsequently
379 // configureFlutterEngine which registers the plugins.
380 activity.onCreate(null);
381
382 List<FlutterEngine> registeredEngines = GeneratedPluginRegistrant.getRegisteredEngines();
383 assertEquals(1, registeredEngines.size());
384 assertEquals(activity.getFlutterEngine(), registeredEngines.get(0));
385 }
386
387 @Test
391 FlutterEngine mockEngine = mock(FlutterEngine.class);
392 FlutterEngineCache.getInstance().put("my_cached_engine", mockEngine);
393
394 Intent intent = FlutterActivity.withCachedEngine("my_cached_engine").build(ctx);
395 ActivityController<FlutterActivity> activityController =
396 Robolectric.buildActivity(FlutterActivity.class, intent);
397 FlutterActivity flutterActivity = activityController.get();
398
399 isDelegateAttached = true;
400 when(mockDelegate.isAttached()).thenAnswer(invocation -> isDelegateAttached);
401 doAnswer(invocation -> isDelegateAttached = false).when(mockDelegate).onDetach();
402
403 flutterActivity.setDelegate(mockDelegate);
404 flutterActivity.onStart();
405 flutterActivity.onResume();
406
407 verify(mockDelegate, times(1)).onStart();
408 verify(mockDelegate, times(1)).onResume();
409
410 flutterActivity.onPause();
411 flutterActivity.detachFromFlutterEngine();
412 verify(mockDelegate, times(1)).onPause();
413 verify(mockDelegate, times(1)).onDestroyView();
414 verify(mockDelegate, times(1)).onDetach();
415
416 flutterActivity.onStop();
417 verify(mockDelegate, never()).onStop();
418
419 // Simulate the disconnected activity resuming again.
420 flutterActivity.onStart();
421 flutterActivity.onResume();
422 // Shouldn't send more events to the delegates as before and shouldn't crash.
423 verify(mockDelegate, times(1)).onStart();
424 verify(mockDelegate, times(1)).onResume();
425
426 flutterActivity.onDestroy();
427 // 1 time same as before.
428 verify(mockDelegate, times(1)).onDestroyView();
429 verify(mockDelegate, times(1)).onDetach();
430 verify(mockDelegate, times(1)).release();
431 }
432
433 @Test
435 Intent intent = FlutterActivity.createDefaultIntent(ctx);
436 ActivityController<FlutterActivity> activityController =
437 Robolectric.buildActivity(FlutterActivity.class, intent);
438 FlutterActivity flutterActivity = activityController.get();
440 new FlutterActivityAndFragmentDelegate(flutterActivity);
441 flutterActivity.setDelegate(delegate);
442
443 assertEquals(flutterActivity.getExclusiveAppComponent(), delegate);
444 }
445
446 @Test
447 public void itDelaysDrawing() {
448 Intent intent = FlutterActivity.createDefaultIntent(ctx);
449 ActivityController<FlutterActivity> activityController =
450 Robolectric.buildActivity(FlutterActivity.class, intent);
451 FlutterActivity flutterActivity = activityController.get();
452
453 flutterActivity.onCreate(null);
454
455 assertNotNull(flutterActivity.delegate.activePreDrawListener);
456 }
457
458 @Test
460 Intent intent = FlutterActivityWithTextureRendering.createDefaultIntent(ctx);
461 ActivityController<FlutterActivityWithTextureRendering> activityController =
462 Robolectric.buildActivity(FlutterActivityWithTextureRendering.class, intent);
463 FlutterActivityWithTextureRendering flutterActivity = activityController.get();
464
465 flutterActivity.onCreate(null);
466
467 assertNull(flutterActivity.delegate.activePreDrawListener);
468 }
469
470 @Test
472 FlutterLoader mockFlutterLoader = mock(FlutterLoader.class);
473 FlutterJNI mockFlutterJni = mock(FlutterJNI.class);
474 when(mockFlutterJni.isAttached()).thenReturn(true);
475 FlutterEngine cachedEngine = new FlutterEngine(ctx, mockFlutterLoader, mockFlutterJni);
476 FakeFlutterPlugin fakeFlutterPlugin = new FakeFlutterPlugin();
477 cachedEngine.getPlugins().add(fakeFlutterPlugin);
478 FlutterEngineCache.getInstance().put("my_cached_engine", cachedEngine);
479
480 Intent intent = FlutterActivity.withCachedEngine("my_cached_engine").build(ctx);
481 Robolectric.buildActivity(FlutterActivity.class, intent).setup();
482 assertTrue(
483 "Expected FakeFlutterPlugin onCreateCalled to be true", fakeFlutterPlugin.onCreateCalled);
484 }
485
486 @Test
488 Intent intent = new Intent(ctx, FlutterActivityWithProvidedEngine.class);
489 ActivityController<FlutterActivityWithProvidedEngine> activityController =
490 Robolectric.buildActivity(FlutterActivityWithProvidedEngine.class, intent);
491 activityController.create();
492 FlutterActivityWithProvidedEngine flutterActivity = activityController.get();
493 flutterActivity.configureFlutterEngine(flutterActivity.getFlutterEngine());
494
495 List<FlutterEngine> registeredEngines = GeneratedPluginRegistrant.getRegisteredEngines();
496 // This might cause the plugins to be registered twice, once by the FlutterEngine constructor,
497 // and once by the default FlutterActivity.configureFlutterEngine implementation.
498 // Test that it doesn't happen.
499 assertEquals(1, registeredEngines.size());
500 }
501
502 @Test
506 isDelegateAttached = true;
507 when(mockDelegate.isAttached()).thenAnswer(invocation -> isDelegateAttached);
508 doAnswer(invocation -> isDelegateAttached = false).when(mockDelegate).onDetach();
509 FlutterEngine mockEngine = mock(FlutterEngine.class);
510 FlutterEngineCache.getInstance().put("my_cached_engine", mockEngine);
511
512 Intent intent = FlutterActivity.withCachedEngine("my_cached_engine").build(ctx);
513 ActivityController<FlutterActivity> activityController =
514 Robolectric.buildActivity(FlutterActivity.class, intent);
515 FlutterActivity flutterActivity = activityController.get();
516
517 flutterActivity.setDelegate(mockDelegate);
518 flutterActivity.onStart();
519 flutterActivity.onResume();
520 flutterActivity.onPause();
521
522 assertTrue(mockDelegate.isAttached());
523 flutterActivity.detachFromFlutterEngine();
524 verify(mockDelegate, times(1)).onDetach();
525 verify(mockDelegate, never()).release();
526 assertFalse(mockDelegate.isAttached());
527 }
528
529 @Test
533 isDelegateAttached = true;
534 when(mockDelegate.isAttached()).thenAnswer(invocation -> isDelegateAttached);
535 doAnswer(invocation -> isDelegateAttached = false).when(mockDelegate).onDetach();
536 FlutterEngine mockEngine = mock(FlutterEngine.class);
537 FlutterEngineCache.getInstance().put("my_cached_engine", mockEngine);
538
539 Intent intent = FlutterActivity.withCachedEngine("my_cached_engine").build(ctx);
540 ActivityController<FlutterActivity> activityController =
541 Robolectric.buildActivity(FlutterActivity.class, intent);
542 FlutterActivity flutterActivity = activityController.get();
543
544 flutterActivity.setDelegate(mockDelegate);
545 flutterActivity.onStart();
546 flutterActivity.onResume();
547 flutterActivity.onPause();
548
549 assertTrue(mockDelegate.isAttached());
550 flutterActivity.onDestroy();
551 verify(mockDelegate, times(1)).onDetach();
552 verify(mockDelegate, times(1)).release();
553 assertFalse(mockDelegate.isAttached());
554 }
555
556 @Test
557 @Config(minSdk = API_LEVELS.API_21, maxSdk = API_LEVELS.API_28)
558 public void fullyDrawn_beforeAndroidQ() {
559 Intent intent = FlutterActivityWithReportFullyDrawn.createDefaultIntent(ctx);
560 ActivityController<FlutterActivityWithReportFullyDrawn> activityController =
561 Robolectric.buildActivity(FlutterActivityWithReportFullyDrawn.class, intent);
562 FlutterActivityWithReportFullyDrawn flutterActivity = activityController.get();
563
564 // See https://github.com/flutter/flutter/issues/46172, and
565 // https://github.com/flutter/flutter/issues/88767.
566 flutterActivity.onFlutterUiDisplayed();
567 assertFalse("reportFullyDrawn isn't used", flutterActivity.isFullyDrawn());
568 }
569
570 @Test
571 @Config(minSdk = API_LEVELS.API_29)
572 public void fullyDrawn_fromAndroidQ() {
573 Intent intent = FlutterActivityWithReportFullyDrawn.createDefaultIntent(ctx);
574 ActivityController<FlutterActivityWithReportFullyDrawn> activityController =
575 Robolectric.buildActivity(FlutterActivityWithReportFullyDrawn.class, intent);
576 FlutterActivityWithReportFullyDrawn flutterActivity = activityController.get();
577
578 flutterActivity.onFlutterUiDisplayed();
579 assertTrue("reportFullyDrawn is used", flutterActivity.isFullyDrawn());
580 flutterActivity.resetFullyDrawn();
581 }
582
584 @Override
585 @SuppressLint("MissingSuperCall")
586 protected void onCreate(@Nullable Bundle savedInstanceState) {
587 super.delegate = new FlutterActivityAndFragmentDelegate(this);
588 super.delegate.setUpFlutterEngine();
589 }
590
591 @Nullable
592 @Override
593 public FlutterEngine provideFlutterEngine(@NonNull Context context) {
594 FlutterJNI flutterJNI = mock(FlutterJNI.class);
595 FlutterLoader flutterLoader = mock(FlutterLoader.class);
596 when(flutterJNI.isAttached()).thenReturn(true);
597 when(flutterLoader.automaticallyRegisterPlugins()).thenReturn(true);
598
599 return new FlutterEngine(context, flutterLoader, flutterJNI, new String[] {}, true);
600 }
601 }
602
603 // This is just a compile time check to ensure that it's possible for FlutterActivity subclasses
604 // to provide their own intent builders which builds their own runtime types.
606
610
611 public static CachedEngineIntentBuilder withCachedEngine(@NonNull String cachedEngineId) {
612 return new CachedEngineIntentBuilder(FlutterActivityWithIntentBuilders.class, cachedEngineId);
613 }
614 }
615
616 private static class FlutterActivityWithTextureRendering extends FlutterActivity {
617 @Override
618 public RenderMode getRenderMode() {
619 return RenderMode.texture;
620 }
621 }
622
623 private static class FlutterActivityWithReportFullyDrawn extends FlutterActivity {
624 private boolean fullyDrawn = false;
625
626 @Override
627 public void reportFullyDrawn() {
628 fullyDrawn = true;
629 }
630
631 public boolean isFullyDrawn() {
632 return fullyDrawn;
633 }
634
635 public void resetFullyDrawn() {
636 fullyDrawn = false;
637 }
638 }
639
640 private static class FlutterActivityWithMockBackInvokedHandling extends FlutterActivity {
641
642 int onBackPressedCounter = 0;
643 int startBackGestureCounter = 0;
644 int updateBackGestureProgressCounter = 0;
645 int commitBackGestureCounter = 0;
646 int cancelBackGestureCounter = 0;
647
648 @Override
649 public void onBackPressed() {
650 onBackPressedCounter++;
651 }
652
653 @TargetApi(API_LEVELS.API_34)
654 @RequiresApi(API_LEVELS.API_34)
655 @Override
656 public void startBackGesture(@NonNull BackEvent backEvent) {
657 startBackGestureCounter++;
658 }
659
660 @TargetApi(API_LEVELS.API_34)
661 @RequiresApi(API_LEVELS.API_34)
662 @Override
663 public void updateBackGestureProgress(@NonNull BackEvent backEvent) {
664 updateBackGestureProgressCounter++;
665 }
666
667 @TargetApi(API_LEVELS.API_34)
668 @RequiresApi(API_LEVELS.API_34)
669 @Override
670 public void commitBackGesture() {
671 commitBackGestureCounter++;
672 }
673
674 @TargetApi(API_LEVELS.API_34)
675 @RequiresApi(API_LEVELS.API_34)
676 @Override
677 public void cancelBackGesture() {
678 cancelBackGestureCounter++;
679 }
680 }
681
682 private static final class FakeFlutterPlugin
683 implements FlutterPlugin,
684 ActivityAware,
685 OnSaveInstanceStateListener,
686 DefaultLifecycleObserver {
687
688 private ActivityPluginBinding activityPluginBinding;
689 private boolean stateRestored = false;
690 private boolean onCreateCalled = false;
691
692 @Override
693 public void onAttachedToEngine(@NonNull FlutterPluginBinding binding) {}
694
695 @Override
696 public void onDetachedFromEngine(@NonNull FlutterPluginBinding binding) {}
697
698 @Override
699 public void onAttachedToActivity(@NonNull ActivityPluginBinding binding) {
700 activityPluginBinding = binding;
701 binding.addOnSaveStateListener(this);
702 ((FlutterActivity) binding.getActivity()).getLifecycle().addObserver(this);
703 }
704
705 @Override
706 public void onDetachedFromActivityForConfigChanges() {
707 onDetachedFromActivity();
708 }
709
710 @Override
711 public void onReattachedToActivityForConfigChanges(@NonNull ActivityPluginBinding binding) {
712 onAttachedToActivity(binding);
713 }
714
715 @Override
716 public void onDetachedFromActivity() {
717 ((FlutterActivity) activityPluginBinding.getActivity()).getLifecycle().removeObserver(this);
718 activityPluginBinding.removeOnSaveStateListener(this);
719 activityPluginBinding = null;
720 }
721
722 @Override
723 public void onSaveInstanceState(@NonNull Bundle bundle) {}
724
725 @Override
726 public void onRestoreInstanceState(@Nullable Bundle bundle) {
727 stateRestored = true;
728 }
729
730 @Override
731 public void onCreate(@NonNull LifecycleOwner lifecycleOwner) {
732 assertTrue("State was restored before onCreate", stateRestored);
733 onCreateCalled = true;
734 }
735 }
736}
static SkISize times(const SkISize &size, float factor)
Builder setFlutterJNIFactory(@NonNull FlutterJNI.Factory factory)
static void setInstance(@NonNull FlutterInjector injector)
static CachedEngineIntentBuilder withCachedEngine(@NonNull String cachedEngineId)
static CachedEngineIntentBuilder withCachedEngine(@NonNull String cachedEngineId)
void setDelegate(@NonNull FlutterActivityAndFragmentDelegate delegate)
ExclusiveAppComponent< Activity > getExclusiveAppComponent()
static Intent createDefaultIntent(@NonNull Context launchContext)
static NewEngineInGroupIntentBuilder withNewEngineInGroup(@NonNull String engineGroupId)
static NewEngineIntentBuilder withNewEngine()
void onCreate(@Nullable Bundle savedInstanceState)
FlutterActivityAndFragmentDelegate delegate
void configureFlutterEngine(@NonNull FlutterEngine flutterEngine)
FlKeyEvent uint64_t FlKeyResponderAsyncCallback callback