diff --git a/shell/platform/android/io/flutter/embedding/android/FlutterActivity.java b/shell/platform/android/io/flutter/embedding/android/FlutterActivity.java index 13137ebf7dd7b..dd502a880c800 100644 --- a/shell/platform/android/io/flutter/embedding/android/FlutterActivity.java +++ b/shell/platform/android/io/flutter/embedding/android/FlutterActivity.java @@ -35,6 +35,8 @@ import android.view.View; import android.view.Window; import android.view.WindowManager; +import android.window.OnBackInvokedCallback; +import android.window.OnBackInvokedDispatcher; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.annotation.VisibleForTesting; @@ -495,6 +497,8 @@ protected void onCreate(@Nullable Bundle savedInstanceState) { lifecycle.handleLifecycleEvent(Lifecycle.Event.ON_CREATE); + registerOnBackInvokedCallback(); + configureWindowForTransparency(); setContentView(createFlutterView()); @@ -502,6 +506,48 @@ protected void onCreate(@Nullable Bundle savedInstanceState) { configureStatusBarForFullscreenFlutterExperience(); } + /** + * Registers the callback with OnBackInvokedDispatcher to capture back navigation gestures and + * pass them to the framework. + * + *
This replaces the deprecated onBackPressed method override in order to support API 33's + * predictive back navigation feature. + * + *
The callback must be unregistered in order to prevent unpredictable behavior once outside + * the Flutter app. + */ + @VisibleForTesting + public void registerOnBackInvokedCallback() { + if (Build.VERSION.SDK_INT >= 33) { + getOnBackInvokedDispatcher() + .registerOnBackInvokedCallback( + OnBackInvokedDispatcher.PRIORITY_DEFAULT, onBackInvokedCallback); + } + } + + /** + * Unregisters the callback from OnBackInvokedDispatcher. + * + *
This should be called when the activity is no longer in use to prevent unpredictable + * behavior such as being stuck and unable to press back. + */ + @VisibleForTesting + public void unregisterOnBackInvokedCallback() { + if (Build.VERSION.SDK_INT >= 33) { + getOnBackInvokedDispatcher().unregisterOnBackInvokedCallback(onBackInvokedCallback); + } + } + + private final OnBackInvokedCallback onBackInvokedCallback = + Build.VERSION.SDK_INT >= 33 + ? new OnBackInvokedCallback() { + @Override + public void onBackInvoked() { + onBackPressed(); + } + } + : null; + /** * Switches themes for this {@code Activity} from the theme used to launch this {@code Activity} * to a "normal theme" that is intended for regular {@code Activity} operation. @@ -680,7 +726,9 @@ protected void onSaveInstanceState(Bundle outState) { * *
After calling, this activity should be disposed immediately and not be re-used.
*/
- private void release() {
+ @VisibleForTesting
+ public void release() {
+ unregisterOnBackInvokedCallback();
if (delegate != null) {
delegate.release();
delegate = null;
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 d6d1d30d442f0..75d7abfcb4f03 100644
--- a/shell/platform/android/test/io/flutter/embedding/android/FlutterActivityTest.java
+++ b/shell/platform/android/test/io/flutter/embedding/android/FlutterActivityTest.java
@@ -88,6 +88,36 @@ public void flutterViewHasId() {
assertTrue(activity.findViewById(FlutterActivity.FLUTTER_VIEW_ID) instanceof FlutterView);
}
+ // 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
+ @TargetApi(33)
+ public void itRegistersOnBackInvokedCallbackOnCreate() {
+ Intent intent = FlutterActivityWithReportFullyDrawn.createDefaultIntent(ctx);
+ ActivityController