From ec896de5c443408a441d85af84879c74fbf87a34 Mon Sep 17 00:00:00 2001 From: Gray Mackall <34871572+gmackall@users.noreply.github.com> Date: Tue, 3 Dec 2024 17:07:27 -0800 Subject: [PATCH] [Android] Save back handling state in Activity/Fragment bundle (flutter/engine#56715) Fixes https://github.com/flutter/flutter/issues/159158, and fixes b/355556397 [C++, Objective-C, Java style guides]: https://github.com/flutter/engine/blob/main/CONTRIBUTING.md#style --- .../embedding/android/FlutterActivity.java | 14 ++++- .../FlutterActivityAndFragmentDelegate.java | 9 +++ .../embedding/android/FlutterFragment.java | 14 ++++- .../android/FlutterActivityTest.java | 44 +++++++++++++ .../android/FlutterAndroidComponentTest.java | 5 ++ .../android/FlutterFragmentActivityTest.java | 62 +++++++++++++++++++ 6 files changed, 146 insertions(+), 2 deletions(-) diff --git a/engine/src/flutter/shell/platform/android/io/flutter/embedding/android/FlutterActivity.java b/engine/src/flutter/shell/platform/android/io/flutter/embedding/android/FlutterActivity.java index f5794335b74..b46a8e70295 100644 --- a/engine/src/flutter/shell/platform/android/io/flutter/embedding/android/FlutterActivity.java +++ b/engine/src/flutter/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,13 @@ public class FlutterActivity extends Activity super.onCreate(savedInstanceState); + if (savedInstanceState != null) { + boolean frameworkHandlesBack = + savedInstanceState.getBoolean( + FlutterActivityAndFragmentDelegate.ON_BACK_CALLBACK_ENABLED_KEY); + setFrameworkHandlesBack(frameworkHandlesBack); + } + delegate = new FlutterActivityAndFragmentDelegate(this); delegate.onAttach(this); delegate.onRestoreInstanceState(savedInstanceState); @@ -1477,6 +1484,11 @@ public class FlutterActivity extends Activity return true; } + @Override + public boolean getBackCallbackState() { + return hasRegisteredBackCallback; + } + @Override public boolean popSystemNavigator() { // Hook for subclass. No-op if returns false. diff --git a/engine/src/flutter/shell/platform/android/io/flutter/embedding/android/FlutterActivityAndFragmentDelegate.java b/engine/src/flutter/shell/platform/android/io/flutter/embedding/android/FlutterActivityAndFragmentDelegate.java index c576eacda8c..630d3dff4f4 100644 --- a/engine/src/flutter/shell/platform/android/io/flutter/embedding/android/FlutterActivityAndFragmentDelegate.java +++ b/engine/src/flutter/shell/platform/android/io/flutter/embedding/android/FlutterActivityAndFragmentDelegate.java @@ -76,6 +76,7 @@ import java.util.List; 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 @@ import java.util.List; 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 @@ import java.util.List; *
Defaults to {@code true}.
*/
boolean attachToEngineAutomatically();
+
+ boolean getBackCallbackState();
}
}
diff --git a/engine/src/flutter/shell/platform/android/io/flutter/embedding/android/FlutterFragment.java b/engine/src/flutter/shell/platform/android/io/flutter/embedding/android/FlutterFragment.java
index e26d13e80f7..fd2021b590e 100644
--- a/engine/src/flutter/shell/platform/android/io/flutter/embedding/android/FlutterFragment.java
+++ b/engine/src/flutter/shell/platform/android/io/flutter/embedding/android/FlutterFragment.java
@@ -1009,7 +1009,8 @@ public class FlutterFragment extends Fragment
return new FlutterActivityAndFragmentDelegate(host);
}
- private final OnBackPressedCallback onBackPressedCallback =
+ @VisibleForTesting
+ final OnBackPressedCallback onBackPressedCallback =
new OnBackPressedCallback(true) {
@Override
public void handleOnBackPressed() {
@@ -1071,6 +1072,12 @@ public class FlutterFragment extends Fragment
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
+ if (savedInstanceState != null) {
+ boolean frameworkHandlesBack =
+ savedInstanceState.getBoolean(
+ FlutterActivityAndFragmentDelegate.ON_BACK_CALLBACK_ENABLED_KEY);
+ onBackPressedCallback.setEnabled(frameworkHandlesBack);
+ }
delegate.onRestoreInstanceState(savedInstanceState);
}
@@ -1655,6 +1662,11 @@ public class FlutterFragment extends Fragment
return true;
}
+ @Override
+ public boolean getBackCallbackState() {
+ return onBackPressedCallback.isEnabled();
+ }
+
/**
* {@inheritDoc}
*
diff --git a/engine/src/flutter/shell/platform/android/test/io/flutter/embedding/android/FlutterActivityTest.java b/engine/src/flutter/shell/platform/android/test/io/flutter/embedding/android/FlutterActivityTest.java
index 8833b7a96db..d3686a6dbcf 100644
--- a/engine/src/flutter/shell/platform/android/test/io/flutter/embedding/android/FlutterActivityTest.java
+++ b/engine/src/flutter/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.Nullable;
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 class FlutterActivityTest {
assertTrue(activity.findViewById(FlutterActivity.FLUTTER_VIEW_ID) instanceof FlutterView);
}
+ @Test
+ @Config(minSdk = 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