1package io.flutter.embedding.android;
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;
19import android.annotation.SuppressLint;
20import android.annotation.TargetApi;
23import android.content.pm.PackageManager;
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;
49import org.junit.After;
50import org.junit.Before;
52import org.junit.runner.RunWith;
53import org.robolectric.Robolectric;
54import org.robolectric.android.controller.ActivityController;
55import org.robolectric.annotation.Config;
58@RunWith(AndroidJUnit4.class)
60 private final Context ctx = ApplicationProvider.getApplicationContext();
68 when(mockFlutterJNI.
isAttached()).thenReturn(
true);
70 when(mockFlutterJNIFactory.provideFlutterJNI()).thenReturn(mockFlutterJNI);
84 ActivityController<FlutterActivity> activityController =
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());
103 activity.onCreate(
null);
105 verify(activity,
times(0)).registerOnBackInvokedCallback();
107 activity.setFrameworkHandlesBack(
false);
108 verify(activity,
times(0)).registerOnBackInvokedCallback();
110 activity.setFrameworkHandlesBack(
true);
111 verify(activity,
times(1)).registerOnBackInvokedCallback();
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());
126 verify(activity,
times(1)).unregisterOnBackInvokedCallback();
131 public
void onBackInvokedCallbackIsNullForSdk32OrLower() {
133 ActivityController<FlutterActivity> activityController =
138 "onBackInvokedCallback should be null for SDK 32 or lower",
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();
151 OnBackInvokedCallback
callback = activity.getOnBackInvokedCallback();
152 assertNotNull(
"onBackInvokedCallback should not be null for SDK 33",
callback);
155 assertEquals(
"Expected onBackPressed to be called 1 times", 1, activity.onBackPressedCounter);
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();
168 "onBackInvokedCallback should be an instance of OnBackAnimationCallback for SDK 34 or higher",
169 activity.getOnBackInvokedCallback() instanceof OnBackAnimationCallback);
172 (OnBackAnimationCallback) activity.getOnBackInvokedCallback();
174 BackEvent mockBackEvent = mock(BackEvent.class);
175 callback.onBackStarted(mockBackEvent);
177 "Expected startBackGesture to be called 1 times", 1, activity.startBackGestureCounter);
179 callback.onBackProgressed(mockBackEvent);
181 "Expected updateBackGestureProgress to be called 1 times",
183 activity.updateBackGestureProgressCounter);
187 "Expected commitBackGesture to be called 1 times", 1, activity.commitBackGestureCounter);
191 "Expected cancelBackGesture to be called 1 times", 1, activity.cancelBackGestureCounter);
197 ActivityController<FlutterActivity> activityController =
224 ActivityController<FlutterActivity> activityController =
235 ActivityController<FlutterActivityWithProvidedEngine> activityController =
237 activityController.create();
247 .initialRoute(
"/custom/route")
248 .dartEntrypointArgs(
new ArrayList<String>(Arrays.asList(
"foo",
"bar")))
251 ActivityController<FlutterActivity> activityController =
272 .dartEntrypoint(
"custom_entrypoint")
273 .initialRoute(
"/custom/route")
276 ActivityController<FlutterActivity> activityController =
295 throws PackageManager.NameNotFoundException {
298 ActivityController<FlutterActivity> activityController =
301 Bundle bundle =
new Bundle();
302 bundle.putBoolean(HANDLE_DEEPLINKING_META_DATA_KEY,
true);
304 when(spyFlutterActivity.
getMetaData()).thenReturn(bundle);
310 throws PackageManager.NameNotFoundException {
313 ActivityController<FlutterActivity> activityController =
316 Bundle bundle =
new Bundle();
317 bundle.putBoolean(HANDLE_DEEPLINKING_META_DATA_KEY,
false);
319 when(spyFlutterActivity.
getMetaData()).thenReturn(bundle);
325 throws PackageManager.NameNotFoundException {
328 ActivityController<FlutterActivity> activityController =
332 Bundle bundle =
new Bundle();
334 when(spyFlutterActivity.
getMetaData()).thenReturn(bundle);
343 .destroyEngineWithActivity(
false)
345 ActivityController<FlutterActivity> activityController =
359 .destroyEngineWithActivity(
true)
361 ActivityController<FlutterActivity> activityController =
374 ActivityController<FlutterActivity> activityController =
383 assertEquals(1, registeredEngines.size());
395 ActivityController<FlutterActivity> activityController =
399 isDelegateAttached =
true;
400 when(mockDelegate.isAttached()).thenAnswer(invocation -> isDelegateAttached);
401 doAnswer(invocation -> isDelegateAttached =
false).when(mockDelegate).onDetach();
407 verify(mockDelegate,
times(1)).onStart();
408 verify(mockDelegate,
times(1)).onResume();
412 verify(mockDelegate,
times(1)).onPause();
413 verify(mockDelegate,
times(1)).onDestroyView();
414 verify(mockDelegate,
times(1)).onDetach();
417 verify(mockDelegate, never()).onStop();
423 verify(mockDelegate,
times(1)).onStart();
424 verify(mockDelegate,
times(1)).onResume();
428 verify(mockDelegate,
times(1)).onDestroyView();
429 verify(mockDelegate,
times(1)).onDetach();
430 verify(mockDelegate,
times(1)).release();
436 ActivityController<FlutterActivity> activityController =
449 ActivityController<FlutterActivity> activityController =
460 Intent intent = FlutterActivityWithTextureRendering.createDefaultIntent(ctx);
461 ActivityController<FlutterActivityWithTextureRendering> activityController =
462 Robolectric.buildActivity(FlutterActivityWithTextureRendering.class, intent);
463 FlutterActivityWithTextureRendering flutterActivity = activityController.get();
465 flutterActivity.onCreate(
null);
467 assertNull(flutterActivity.delegate.activePreDrawListener);
474 when(mockFlutterJni.
isAttached()).thenReturn(
true);
476 FakeFlutterPlugin fakeFlutterPlugin =
new FakeFlutterPlugin();
483 "Expected FakeFlutterPlugin onCreateCalled to be true", fakeFlutterPlugin.onCreateCalled);
489 ActivityController<FlutterActivityWithProvidedEngine> activityController =
491 activityController.create();
499 assertEquals(1, registeredEngines.size());
506 isDelegateAttached =
true;
507 when(mockDelegate.isAttached()).thenAnswer(invocation -> isDelegateAttached);
508 doAnswer(invocation -> isDelegateAttached =
false).when(mockDelegate).onDetach();
513 ActivityController<FlutterActivity> activityController =
522 assertTrue(mockDelegate.isAttached());
524 verify(mockDelegate,
times(1)).onDetach();
525 verify(mockDelegate, never()).release();
526 assertFalse(mockDelegate.isAttached());
533 isDelegateAttached =
true;
534 when(mockDelegate.isAttached()).thenAnswer(invocation -> isDelegateAttached);
535 doAnswer(invocation -> isDelegateAttached =
false).when(mockDelegate).onDetach();
540 ActivityController<FlutterActivity> activityController =
549 assertTrue(mockDelegate.isAttached());
551 verify(mockDelegate,
times(1)).onDetach();
552 verify(mockDelegate,
times(1)).release();
553 assertFalse(mockDelegate.isAttached());
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();
566 flutterActivity.onFlutterUiDisplayed();
567 assertFalse(
"reportFullyDrawn isn't used", flutterActivity.isFullyDrawn());
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();
578 flutterActivity.onFlutterUiDisplayed();
579 assertTrue(
"reportFullyDrawn is used", flutterActivity.isFullyDrawn());
580 flutterActivity.resetFullyDrawn();
585 @SuppressLint(
"MissingSuperCall")
586 protected
void onCreate(@Nullable Bundle savedInstanceState) {
588 super.delegate.setUpFlutterEngine();
596 when(flutterJNI.
isAttached()).thenReturn(
true);
599 return new FlutterEngine(context, flutterLoader, flutterJNI,
new String[] {},
true);
616 private static class FlutterActivityWithTextureRendering
extends FlutterActivity {
623 private static class FlutterActivityWithReportFullyDrawn
extends FlutterActivity {
624 private boolean fullyDrawn =
false;
627 public void reportFullyDrawn() {
631 public boolean isFullyDrawn() {
635 public void resetFullyDrawn() {
640 private static class FlutterActivityWithMockBackInvokedHandling
extends FlutterActivity {
642 int onBackPressedCounter = 0;
643 int startBackGestureCounter = 0;
644 int updateBackGestureProgressCounter = 0;
645 int commitBackGestureCounter = 0;
646 int cancelBackGestureCounter = 0;
649 public void onBackPressed() {
650 onBackPressedCounter++;
653 @TargetApi(API_LEVELS.API_34)
654 @RequiresApi(API_LEVELS.API_34)
656 public void startBackGesture(@NonNull BackEvent backEvent) {
657 startBackGestureCounter++;
660 @TargetApi(API_LEVELS.API_34)
661 @RequiresApi(API_LEVELS.API_34)
663 public void updateBackGestureProgress(@NonNull BackEvent backEvent) {
664 updateBackGestureProgressCounter++;
667 @TargetApi(API_LEVELS.API_34)
668 @RequiresApi(API_LEVELS.API_34)
670 public void commitBackGesture() {
671 commitBackGestureCounter++;
674 @TargetApi(API_LEVELS.API_34)
675 @RequiresApi(API_LEVELS.API_34)
677 public void cancelBackGesture() {
678 cancelBackGestureCounter++;
682 private static final class FakeFlutterPlugin
683 implements FlutterPlugin,
685 OnSaveInstanceStateListener,
686 DefaultLifecycleObserver {
688 private ActivityPluginBinding activityPluginBinding;
689 private boolean stateRestored =
false;
690 private boolean onCreateCalled =
false;
693 public void onAttachedToEngine(@NonNull FlutterPluginBinding binding) {}
696 public void onDetachedFromEngine(@NonNull FlutterPluginBinding binding) {}
699 public void onAttachedToActivity(@NonNull ActivityPluginBinding binding) {
700 activityPluginBinding = binding;
701 binding.addOnSaveStateListener(
this);
702 ((FlutterActivity) binding.getActivity()).getLifecycle().addObserver(
this);
706 public void onDetachedFromActivityForConfigChanges() {
707 onDetachedFromActivity();
711 public void onReattachedToActivityForConfigChanges(@NonNull ActivityPluginBinding binding) {
712 onAttachedToActivity(binding);
716 public void onDetachedFromActivity() {
717 ((FlutterActivity) activityPluginBinding.getActivity()).getLifecycle().removeObserver(
this);
718 activityPluginBinding.removeOnSaveStateListener(
this);
719 activityPluginBinding =
null;
723 public void onSaveInstanceState(@NonNull Bundle bundle) {}
726 public void onRestoreInstanceState(@Nullable Bundle bundle) {
727 stateRestored =
true;
731 public void onCreate(@NonNull LifecycleOwner lifecycleOwner) {
732 assertTrue(
"State was restored before onCreate", stateRestored);
733 onCreateCalled =
true;
static SkISize times(const SkISize &size, float factor)
Builder setFlutterJNIFactory(@NonNull FlutterJNI.Factory factory)
static void setInstance(@NonNull FlutterInjector injector)
OnPreDrawListener activePreDrawListener
static CachedEngineIntentBuilder withCachedEngine(@NonNull String cachedEngineId)
static NewEngineIntentBuilder withNewEngine()
FlutterEngine provideFlutterEngine(@NonNull Context context)
void itCreatesCachedEngineIntentThatDoesNotDestroyTheEngine()
void itDoesNotReleaseEnginewhenDetachFromFlutterEngine()
void itCreatesNewEngineInGroupIntentWithRequestedSettings()
void itCreatesNewEngineIntentWithRequestedSettings()
void itCreatesCachedEngineIntentThatDestroysTheEngine()
void itReturnsValueFromMetaDataWhenCallsShouldHandleDeepLinkingCase3()
void itCanBeDetachedFromTheEngineAndStopSendingFurtherEvents()
void itDoesNotDestroyFlutterEngineWhenProvidedByHost()
void itDoesNotRegisterPluginsTwiceWhenUsingACachedEngine()
void itReturnsExclusiveAppComponent()
void itReturnsValueFromMetaDataWhenCallsShouldHandleDeepLinkingCase2()
void itReturnsValueFromMetaDataWhenCallsShouldHandleDeepLinkingCase1()
boolean isDelegateAttached
void itDoesNotDelayDrawingwhenUsingTextureRendering()
void itDestroysNewEngineWhenIntentIsMissingParameter()
void itReleaseEngineWhenOnDestroy()
void itRegistersPluginsAtConfigurationTime()
void itCreatesDefaultIntentWithExpectedDefaults()
void itRestoresPluginStateBeforePluginOnCreate()
Intent build(@NonNull Context context)
static CachedEngineIntentBuilder withCachedEngine(@NonNull String cachedEngineId)
void setDelegate(@NonNull FlutterActivityAndFragmentDelegate delegate)
FlutterShellArgs getFlutterShellArgs()
BackgroundMode getBackgroundMode()
TransparencyMode getTransparencyMode()
void detachFromFlutterEngine()
ExclusiveAppComponent< Activity > getExclusiveAppComponent()
static Intent createDefaultIntent(@NonNull Context launchContext)
static NewEngineInGroupIntentBuilder withNewEngineInGroup(@NonNull String engineGroupId)
String getCachedEngineId()
static NewEngineIntentBuilder withNewEngine()
List< String > getDartEntrypointArgs()
void onCreate(@Nullable Bundle savedInstanceState)
boolean shouldAttachEngineToActivity()
FlutterActivityAndFragmentDelegate delegate
OnBackInvokedCallback getOnBackInvokedCallback()
FlutterEngine getFlutterEngine()
boolean shouldHandleDeeplinking()
RenderMode getRenderMode()
String getDartEntrypointFunctionName()
static final int FLUTTER_VIEW_ID
boolean shouldDestroyEngineWithHost()
String getDartEntrypointLibraryUri()
void configureFlutterEngine(@NonNull FlutterEngine flutterEngine)
String getCachedEngineGroupId()
static FlutterEngineCache getInstance()
void put(@NonNull String engineId, @Nullable FlutterEngine engine)
PluginRegistry getPlugins()
boolean automaticallyRegisterPlugins()
static void clearRegisteredEngines()
static List< FlutterEngine > getRegisteredEngines()
FlKeyEvent uint64_t FlKeyResponderAsyncCallback callback
void add(@NonNull FlutterPlugin plugin)