[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
This commit is contained in:
parent
dcc250c8a5
commit
ec896de5c4
@ -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.
|
||||
|
@ -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;
|
||||
* <p>Defaults to {@code true}.
|
||||
*/
|
||||
boolean attachToEngineAutomatically();
|
||||
|
||||
boolean getBackCallbackState();
|
||||
}
|
||||
}
|
||||
|
@ -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}
|
||||
*
|
||||
|
@ -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<FlutterActivity> 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(minSdk = API_LEVELS.API_34)
|
||||
@TargetApi(API_LEVELS.API_34)
|
||||
public void whenNotUsingCachedEngine_predictiveBackStateIsNotSaved() {
|
||||
ActivityScenario<FlutterActivity> 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
|
||||
|
@ -401,6 +401,11 @@ public class FlutterAndroidComponentTest {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean getBackCallbackState() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFlutterSurfaceViewCreated(@NonNull FlutterSurfaceView flutterSurfaceView) {}
|
||||
|
||||
|
@ -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.mock;
|
||||
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.annotation.Nullable;
|
||||
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 class FlutterFragmentActivityTest {
|
||||
assertEquals(0, activity.numberOfEnginesCreated);
|
||||
}
|
||||
|
||||
@Test
|
||||
@Config(minSdk = 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<FlutterFragmentActivity> 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(minSdk = Build.API_LEVELS.API_34)
|
||||
@TargetApi(Build.API_LEVELS.API_34)
|
||||
public void whenNotUsingCachedEngine_predictiveBackStateIsNotSaved() {
|
||||
ActivityScenario<FlutterActivity> 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;
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user