diff --git a/shell/platform/android/io/flutter/embedding/android/FlutterActivity.java b/shell/platform/android/io/flutter/embedding/android/FlutterActivity.java index f5794335b74f4..668dda306bcef 100644 --- a/shell/platform/android/io/flutter/embedding/android/FlutterActivity.java +++ b/shell/platform/android/io/flutter/embedding/android/FlutterActivity.java @@ -212,7 +212,7 @@ public class FlutterActivity extends Activity implements FlutterActivityAndFragmentDelegate.Host, LifecycleOwner { private static final String TAG = "FlutterActivity"; - private boolean hasRegisteredBackCallback = false; + @VisibleForTesting boolean hasRegisteredBackCallback = false; /** * The ID of the {@code FlutterView} created by this activity. @@ -634,6 +634,12 @@ protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); + if (savedInstanceState != null + && savedInstanceState.getBoolean( + FlutterActivityAndFragmentDelegate.ON_BACK_CALLBACK_ENABLED_KEY)) { + setFrameworkHandlesBack(true); + } + delegate = new FlutterActivityAndFragmentDelegate(this); delegate.onAttach(this); delegate.onRestoreInstanceState(savedInstanceState); @@ -1477,6 +1483,11 @@ public boolean attachToEngineAutomatically() { return true; } + @Override + public boolean getBackCallbackState() { + return hasRegisteredBackCallback; + } + @Override public boolean popSystemNavigator() { // Hook for subclass. No-op if returns false. diff --git a/shell/platform/android/io/flutter/embedding/android/FlutterActivityAndFragmentDelegate.java b/shell/platform/android/io/flutter/embedding/android/FlutterActivityAndFragmentDelegate.java index c576eacda8c0c..630d3dff4f4c9 100644 --- a/shell/platform/android/io/flutter/embedding/android/FlutterActivityAndFragmentDelegate.java +++ b/shell/platform/android/io/flutter/embedding/android/FlutterActivityAndFragmentDelegate.java @@ -76,6 +76,7 @@ private static final String TAG = "FlutterActivityAndFragmentDelegate"; private static final String FRAMEWORK_RESTORATION_BUNDLE_KEY = "framework"; private static final String PLUGINS_RESTORATION_BUNDLE_KEY = "plugins"; + static final String ON_BACK_CALLBACK_ENABLED_KEY = "enableOnBackInvokedCallbackState"; private static final int FLUTTER_SPLASH_VIEW_FALLBACK_ID = 486947586; /** Factory to obtain a FlutterActivityAndFragmentDelegate instance. */ @@ -691,6 +692,12 @@ void onSaveInstanceState(@Nullable Bundle bundle) { flutterEngine.getActivityControlSurface().onSaveInstanceState(plugins); bundle.putBundle(PLUGINS_RESTORATION_BUNDLE_KEY, plugins); } + + // If using a cached engine, we need to save whether the framework or the system should handle + // backs. + if (host.getCachedEngineId() != null && !host.shouldDestroyEngineWithHost()) { + bundle.putBoolean(ON_BACK_CALLBACK_ENABLED_KEY, host.getBackCallbackState()); + } } @Override @@ -1297,5 +1304,7 @@ PlatformPlugin providePlatformPlugin( *

Defaults to {@code true}. */ boolean attachToEngineAutomatically(); + + boolean getBackCallbackState(); } } diff --git a/shell/platform/android/io/flutter/embedding/android/FlutterFragment.java b/shell/platform/android/io/flutter/embedding/android/FlutterFragment.java index e26d13e80f74a..c9727594b618e 100644 --- a/shell/platform/android/io/flutter/embedding/android/FlutterFragment.java +++ b/shell/platform/android/io/flutter/embedding/android/FlutterFragment.java @@ -1009,7 +1009,8 @@ public FlutterActivityAndFragmentDelegate createDelegate( return new FlutterActivityAndFragmentDelegate(host); } - private final OnBackPressedCallback onBackPressedCallback = + @VisibleForTesting + final OnBackPressedCallback onBackPressedCallback = new OnBackPressedCallback(true) { @Override public void handleOnBackPressed() { @@ -1071,6 +1072,11 @@ public void onAttach(@NonNull Context context) { @Override public void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); + if (savedInstanceState != null + && savedInstanceState.getBoolean( + FlutterActivityAndFragmentDelegate.ON_BACK_CALLBACK_ENABLED_KEY)) { + onBackPressedCallback.setEnabled(true); + } delegate.onRestoreInstanceState(savedInstanceState); } @@ -1655,6 +1661,11 @@ public boolean attachToEngineAutomatically() { return true; } + @Override + public boolean getBackCallbackState() { + return onBackPressedCallback.isEnabled(); + } + /** * {@inheritDoc} * diff --git a/shell/platform/android/test/io/flutter/embedding/android/FlutterActivityTest.java b/shell/platform/android/test/io/flutter/embedding/android/FlutterActivityTest.java index 8833b7a96dbe3..07babd12a79ec 100644 --- a/shell/platform/android/test/io/flutter/embedding/android/FlutterActivityTest.java +++ b/shell/platform/android/test/io/flutter/embedding/android/FlutterActivityTest.java @@ -5,6 +5,7 @@ package io.flutter.embedding.android; import static io.flutter.Build.API_LEVELS; +import static io.flutter.embedding.android.FlutterActivityLaunchConfigs.EXTRA_CACHED_ENGINE_ID; import static io.flutter.embedding.android.FlutterActivityLaunchConfigs.HANDLE_DEEPLINKING_META_DATA_KEY; import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertEquals; @@ -34,6 +35,7 @@ import androidx.annotation.RequiresApi; import androidx.lifecycle.DefaultLifecycleObserver; import androidx.lifecycle.LifecycleOwner; +import androidx.test.core.app.ActivityScenario; import androidx.test.core.app.ApplicationProvider; import androidx.test.ext.junit.runners.AndroidJUnit4; import io.flutter.FlutterInjector; @@ -94,6 +96,48 @@ public void flutterViewHasId() { assertTrue(activity.findViewById(FlutterActivity.FLUTTER_VIEW_ID) instanceof FlutterView); } + @Test + @Config(sdk = API_LEVELS.API_34) + @TargetApi(API_LEVELS.API_34) + public void whenUsingCachedEngine_predictiveBackStateIsSaved() { + FlutterLoader mockFlutterLoader = mock(FlutterLoader.class); + FlutterJNI mockFlutterJni = mock(FlutterJNI.class); + when(mockFlutterJni.isAttached()).thenReturn(true); + FlutterEngine cachedEngine = new FlutterEngine(ctx, mockFlutterLoader, mockFlutterJni); + FlutterEngineCache.getInstance().put("my_cached_engine", cachedEngine); + + ActivityScenario flutterActivityScenario = + ActivityScenario.launch(FlutterActivity.class); + + // Set to framework handling and then recreate the activity and check the state is preserved. + flutterActivityScenario.onActivity(activity -> activity.setFrameworkHandlesBack(true)); + flutterActivityScenario.onActivity( + activity -> activity.getIntent().putExtra(EXTRA_CACHED_ENGINE_ID, "my_cached_engine")); + + flutterActivityScenario.recreate(); + flutterActivityScenario.onActivity(activity -> assertTrue(activity.hasRegisteredBackCallback)); + + // Clean up. + flutterActivityScenario.close(); + } + + @Test + @Config(sdk = API_LEVELS.API_34) + @TargetApi(API_LEVELS.API_34) + public void whenNotUsingCachedEngine_predictiveBackStateIsNotSaved() { + ActivityScenario flutterActivityScenario = + ActivityScenario.launch(FlutterActivity.class); + + // Set to framework handling and then recreate the activity and check the state is preserved. + flutterActivityScenario.onActivity(activity -> activity.setFrameworkHandlesBack(true)); + + flutterActivityScenario.recreate(); + flutterActivityScenario.onActivity(activity -> assertFalse(activity.hasRegisteredBackCallback)); + + // Clean up. + flutterActivityScenario.close(); + } + // TODO(garyq): Robolectric does not yet support android api 33 yet. Switch to a robolectric // test that directly exercises the OnBackInvoked APIs when API 33 is supported. @Test diff --git a/shell/platform/android/test/io/flutter/embedding/android/FlutterAndroidComponentTest.java b/shell/platform/android/test/io/flutter/embedding/android/FlutterAndroidComponentTest.java index 8e8c77619f887..901ac61b2a002 100644 --- a/shell/platform/android/test/io/flutter/embedding/android/FlutterAndroidComponentTest.java +++ b/shell/platform/android/test/io/flutter/embedding/android/FlutterAndroidComponentTest.java @@ -401,6 +401,11 @@ public boolean attachToEngineAutomatically() { return true; } + @Override + public boolean getBackCallbackState() { + return false; + } + @Override public void onFlutterSurfaceViewCreated(@NonNull FlutterSurfaceView flutterSurfaceView) {} diff --git a/shell/platform/android/test/io/flutter/embedding/android/FlutterFragmentActivityTest.java b/shell/platform/android/test/io/flutter/embedding/android/FlutterFragmentActivityTest.java index 97701775adc8e..3fc9084b8422d 100644 --- a/shell/platform/android/test/io/flutter/embedding/android/FlutterFragmentActivityTest.java +++ b/shell/platform/android/test/io/flutter/embedding/android/FlutterFragmentActivityTest.java @@ -5,6 +5,8 @@ package io.flutter.embedding.android; import static io.flutter.embedding.android.FlutterActivityLaunchConfigs.HANDLE_DEEPLINKING_META_DATA_KEY; +import static io.flutter.embedding.android.FlutterFragment.ARG_CACHED_ENGINE_ID; +import static io.flutter.embedding.android.FlutterFragment.ARG_DESTROY_ENGINE_WITH_FRAGMENT; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; @@ -13,6 +15,7 @@ import static org.mockito.Mockito.spy; import static org.mockito.Mockito.when; +import android.annotation.TargetApi; import android.content.Context; import android.content.Intent; import android.content.pm.PackageManager; @@ -25,9 +28,11 @@ import androidx.test.core.app.ActivityScenario; import androidx.test.core.app.ApplicationProvider; import androidx.test.ext.junit.runners.AndroidJUnit4; +import io.flutter.Build; import io.flutter.FlutterInjector; import io.flutter.embedding.android.FlutterActivityLaunchConfigs.BackgroundMode; import io.flutter.embedding.engine.FlutterEngine; +import io.flutter.embedding.engine.FlutterEngineCache; import io.flutter.embedding.engine.FlutterJNI; import io.flutter.embedding.engine.loader.FlutterLoader; import io.flutter.plugins.GeneratedPluginRegistrant; @@ -254,6 +259,63 @@ public void itHandlesNewFragmentRecreationDuringRestoreWhenActivityIsRecreated() assertEquals(0, activity.numberOfEnginesCreated); } + @Test + @Config(sdk = Build.API_LEVELS.API_34) + @TargetApi(Build.API_LEVELS.API_34) + public void whenUsingCachedEngine_predictiveBackStateIsSaved() { + FlutterLoader mockFlutterLoader = mock(FlutterLoader.class); + FlutterJNI mockFlutterJni = mock(FlutterJNI.class); + when(mockFlutterJni.isAttached()).thenReturn(true); + FlutterEngine cachedEngine = new FlutterEngine(ctx, mockFlutterLoader, mockFlutterJni); + FlutterEngineCache.getInstance().put("my_cached_engine", cachedEngine); + + ActivityScenario flutterFragmentActivityActivityScenario = + ActivityScenario.launch(FlutterFragmentActivity.class); + + // Set to framework handling and then recreate the activity and check the state is preserved. + flutterFragmentActivityActivityScenario.onActivity( + activity -> { + FlutterFragment flutterFragment = activity.retrieveExistingFlutterFragmentIfPossible(); + flutterFragment.setFrameworkHandlesBack(true); + Bundle bundle = flutterFragment.getArguments(); + bundle.putString(ARG_CACHED_ENGINE_ID, "my_cached_engine"); + bundle.putBoolean(ARG_DESTROY_ENGINE_WITH_FRAGMENT, false); + FlutterEngineCache.getInstance().put("my_cached_engine", cachedEngine); + flutterFragment.setArguments(bundle); + }); + + flutterFragmentActivityActivityScenario.recreate(); + + flutterFragmentActivityActivityScenario.onActivity( + activity -> { + assertTrue( + activity + .retrieveExistingFlutterFragmentIfPossible() + .onBackPressedCallback + .isEnabled()); + }); + + // Clean up. + flutterFragmentActivityActivityScenario.close(); + } + + @Test + @Config(sdk = Build.API_LEVELS.API_34) + @TargetApi(Build.API_LEVELS.API_34) + public void whenNotUsingCachedEngine_predictiveBackStateIsNotSaved() { + ActivityScenario flutterActivityScenario = + ActivityScenario.launch(FlutterActivity.class); + + // Set to framework handling and then recreate the activity and check the state is preserved. + flutterActivityScenario.onActivity(activity -> activity.setFrameworkHandlesBack(true)); + + flutterActivityScenario.recreate(); + flutterActivityScenario.onActivity(activity -> assertFalse(activity.hasRegisteredBackCallback)); + + // Clean up. + flutterActivityScenario.close(); + } + static class FlutterFragmentActivityWithProvidedEngine extends FlutterFragmentActivity { int numberOfEnginesCreated = 0;