diff --git a/ci/licenses_golden/licenses_flutter b/ci/licenses_golden/licenses_flutter index b6d82a2305b12..feb7ecc75b01c 100644 --- a/ci/licenses_golden/licenses_flutter +++ b/ci/licenses_golden/licenses_flutter @@ -40968,6 +40968,7 @@ ORIGIN: ../../../flutter/shell/platform/android/io/flutter/embedding/engine/rend ORIGIN: ../../../flutter/shell/platform/android/io/flutter/embedding/engine/renderer/FlutterUiDisplayListener.java + ../../../flutter/LICENSE ORIGIN: ../../../flutter/shell/platform/android/io/flutter/embedding/engine/renderer/RenderSurface.java + ../../../flutter/LICENSE ORIGIN: ../../../flutter/shell/platform/android/io/flutter/embedding/engine/renderer/SurfaceTextureWrapper.java + ../../../flutter/LICENSE +ORIGIN: ../../../flutter/shell/platform/android/io/flutter/embedding/engine/systemchannels/BackGestureChannel.java + ../../../flutter/LICENSE ORIGIN: ../../../flutter/shell/platform/android/io/flutter/embedding/engine/systemchannels/DeferredComponentChannel.java + ../../../flutter/LICENSE ORIGIN: ../../../flutter/shell/platform/android/io/flutter/embedding/engine/systemchannels/KeyEventChannel.java + ../../../flutter/LICENSE ORIGIN: ../../../flutter/shell/platform/android/io/flutter/embedding/engine/systemchannels/KeyboardChannel.java + ../../../flutter/LICENSE @@ -43854,6 +43855,7 @@ FILE: ../../../flutter/shell/platform/android/io/flutter/embedding/engine/render FILE: ../../../flutter/shell/platform/android/io/flutter/embedding/engine/renderer/SurfaceTextureSurfaceProducer.java FILE: ../../../flutter/shell/platform/android/io/flutter/embedding/engine/renderer/SurfaceTextureWrapper.java FILE: ../../../flutter/shell/platform/android/io/flutter/embedding/engine/systemchannels/AccessibilityChannel.java +FILE: ../../../flutter/shell/platform/android/io/flutter/embedding/engine/systemchannels/BackGestureChannel.java FILE: ../../../flutter/shell/platform/android/io/flutter/embedding/engine/systemchannels/DeferredComponentChannel.java FILE: ../../../flutter/shell/platform/android/io/flutter/embedding/engine/systemchannels/KeyEventChannel.java FILE: ../../../flutter/shell/platform/android/io/flutter/embedding/engine/systemchannels/KeyboardChannel.java diff --git a/shell/platform/android/BUILD.gn b/shell/platform/android/BUILD.gn index 3025fabbbbbec..b1a0f428e860b 100644 --- a/shell/platform/android/BUILD.gn +++ b/shell/platform/android/BUILD.gn @@ -271,6 +271,7 @@ android_java_sources = [ "io/flutter/embedding/engine/renderer/SurfaceTextureSurfaceProducer.java", "io/flutter/embedding/engine/renderer/SurfaceTextureWrapper.java", "io/flutter/embedding/engine/systemchannels/AccessibilityChannel.java", + "io/flutter/embedding/engine/systemchannels/BackGestureChannel.java", "io/flutter/embedding/engine/systemchannels/DeferredComponentChannel.java", "io/flutter/embedding/engine/systemchannels/KeyEventChannel.java", "io/flutter/embedding/engine/systemchannels/KeyboardChannel.java", diff --git a/shell/platform/android/io/flutter/embedding/android/FlutterActivity.java b/shell/platform/android/io/flutter/embedding/android/FlutterActivity.java index dcecd3fbc1bd2..d536fed70dd06 100644 --- a/shell/platform/android/io/flutter/embedding/android/FlutterActivity.java +++ b/shell/platform/android/io/flutter/embedding/android/FlutterActivity.java @@ -22,6 +22,7 @@ import static io.flutter.embedding.android.FlutterActivityLaunchConfigs.INITIAL_ROUTE_META_DATA_KEY; import static io.flutter.embedding.android.FlutterActivityLaunchConfigs.NORMAL_THEME_META_DATA_KEY; +import android.annotation.TargetApi; import android.app.Activity; import android.content.Context; import android.content.Intent; @@ -35,10 +36,13 @@ import android.view.View; import android.view.Window; import android.view.WindowManager; +import android.window.BackEvent; +import android.window.OnBackAnimationCallback; import android.window.OnBackInvokedCallback; import android.window.OnBackInvokedDispatcher; import androidx.annotation.NonNull; import androidx.annotation.Nullable; +import androidx.annotation.RequiresApi; import androidx.annotation.VisibleForTesting; import androidx.lifecycle.Lifecycle; import androidx.lifecycle.LifecycleOwner; @@ -678,18 +682,43 @@ public void unregisterOnBackInvokedCallback() { } private final OnBackInvokedCallback onBackInvokedCallback = - Build.VERSION.SDK_INT >= API_LEVELS.API_33 - ? new OnBackInvokedCallback() { - // TODO(garyq): Remove SuppressWarnings annotation. This was added to workaround - // a google3 bug where the linter is not properly running against API 33, causing - // a failure here. See b/243609613 and https://github.com/flutter/flutter/issues/111295 - @SuppressWarnings("Override") - @Override - public void onBackInvoked() { - onBackPressed(); - } - } - : null; + Build.VERSION.SDK_INT < API_LEVELS.API_33 ? null : createOnBackInvokedCallback(); + + @VisibleForTesting + protected OnBackInvokedCallback getOnBackInvokedCallback() { + return onBackInvokedCallback; + } + + @NonNull + @TargetApi(API_LEVELS.API_33) + @RequiresApi(API_LEVELS.API_33) + private OnBackInvokedCallback createOnBackInvokedCallback() { + if (Build.VERSION.SDK_INT >= API_LEVELS.API_34) { + return new OnBackAnimationCallback() { + @Override + public void onBackInvoked() { + commitBackGesture(); + } + + @Override + public void onBackCancelled() { + cancelBackGesture(); + } + + @Override + public void onBackProgressed(@NonNull BackEvent backEvent) { + updateBackGestureProgress(backEvent); + } + + @Override + public void onBackStarted(@NonNull BackEvent backEvent) { + startBackGesture(backEvent); + } + }; + } + + return this::onBackPressed; + } @Override public void setFrameworkHandlesBack(boolean frameworkHandlesBack) { @@ -899,6 +928,38 @@ public void onBackPressed() { } } + @TargetApi(API_LEVELS.API_34) + @RequiresApi(API_LEVELS.API_34) + public void startBackGesture(@NonNull BackEvent backEvent) { + if (stillAttachedForEvent("startBackGesture")) { + delegate.startBackGesture(backEvent); + } + } + + @TargetApi(API_LEVELS.API_34) + @RequiresApi(API_LEVELS.API_34) + public void updateBackGestureProgress(@NonNull BackEvent backEvent) { + if (stillAttachedForEvent("updateBackGestureProgress")) { + delegate.updateBackGestureProgress(backEvent); + } + } + + @TargetApi(API_LEVELS.API_34) + @RequiresApi(API_LEVELS.API_34) + public void commitBackGesture() { + if (stillAttachedForEvent("commitBackGesture")) { + delegate.commitBackGesture(); + } + } + + @TargetApi(API_LEVELS.API_34) + @RequiresApi(API_LEVELS.API_34) + public void cancelBackGesture() { + if (stillAttachedForEvent("cancelBackGesture")) { + delegate.cancelBackGesture(); + } + } + @Override public void onRequestPermissionsResult( int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { diff --git a/shell/platform/android/io/flutter/embedding/android/FlutterActivityAndFragmentDelegate.java b/shell/platform/android/io/flutter/embedding/android/FlutterActivityAndFragmentDelegate.java index 6b8b0c44aafde..2f1156704b982 100644 --- a/shell/platform/android/io/flutter/embedding/android/FlutterActivityAndFragmentDelegate.java +++ b/shell/platform/android/io/flutter/embedding/android/FlutterActivityAndFragmentDelegate.java @@ -7,6 +7,7 @@ import static android.content.ComponentCallbacks2.TRIM_MEMORY_RUNNING_LOW; import static io.flutter.embedding.android.FlutterActivityLaunchConfigs.DEFAULT_INITIAL_ROUTE; +import android.annotation.TargetApi; import android.app.Activity; import android.content.Context; import android.content.Intent; @@ -16,10 +17,14 @@ import android.view.View; import android.view.ViewGroup; import android.view.ViewTreeObserver.OnPreDrawListener; +import android.window.BackEvent; +import android.window.OnBackAnimationCallback; import androidx.annotation.NonNull; import androidx.annotation.Nullable; +import androidx.annotation.RequiresApi; import androidx.annotation.VisibleForTesting; import androidx.lifecycle.Lifecycle; +import io.flutter.Build.API_LEVELS; import io.flutter.FlutterInjector; import io.flutter.Log; import io.flutter.embedding.engine.FlutterEngine; @@ -779,6 +784,100 @@ void onBackPressed() { } } + /** + * Invoke this from {@link OnBackAnimationCallback#onBackStarted(BackEvent)}. + * + *
This method should be called when the back gesture is initiated. It should be invoked as + * part of the implementation of {@link OnBackAnimationCallback}. + * + *
This method delegates the handling of the start of a back gesture to the Flutter framework, + * which is responsible for the appropriate response, such as initiating animations or preparing + * the UI for the back navigation process. + * + * @param backEvent The BackEvent object containing information about the touch. + */ + @TargetApi(API_LEVELS.API_34) + @RequiresApi(API_LEVELS.API_34) + void startBackGesture(@NonNull BackEvent backEvent) { + ensureAlive(); + if (flutterEngine != null) { + Log.v(TAG, "Forwarding startBackGesture() to FlutterEngine."); + flutterEngine.getBackGestureChannel().startBackGesture(backEvent); + } else { + Log.w(TAG, "Invoked startBackGesture() before FlutterFragment was attached to an Activity."); + } + } + + /** + * Invoke this from {@link OnBackAnimationCallback#onBackProgressed(BackEvent)}. + * + *
This method should be called in response to progress in a back gesture, as part of the + * implementation of {@link OnBackAnimationCallback}. + * + *
This method delegates to the Flutter framework to update UI elements or animations based on + * the progression of the back gesture. + * + * @param backEvent An BackEvent object describing the progress event. + */ + @TargetApi(API_LEVELS.API_34) + @RequiresApi(API_LEVELS.API_34) + void updateBackGestureProgress(@NonNull BackEvent backEvent) { + ensureAlive(); + if (flutterEngine != null) { + Log.v(TAG, "Forwarding updateBackGestureProgress() to FlutterEngine."); + flutterEngine.getBackGestureChannel().updateBackGestureProgress(backEvent); + } else { + Log.w( + TAG, + "Invoked updateBackGestureProgress() before FlutterFragment was attached to an Activity."); + } + } + + /** + * Invoke this from {@link OnBackAnimationCallback#onBackInvoked()}. + * + *
This method is called to signify the completion of a back gesture and commits the navigation + * action initiated by the gesture. It should be invoked as the final step in handling a back + * gesture. + * + *
This method indicates to the Flutter framework that it should proceed with the back + * navigation, including finalizing animations and updating the UI to reflect the navigation + * outcome. + */ + @TargetApi(API_LEVELS.API_34) + @RequiresApi(API_LEVELS.API_34) + void commitBackGesture() { + ensureAlive(); + if (flutterEngine != null) { + Log.v(TAG, "Forwarding commitBackGesture() to FlutterEngine."); + flutterEngine.getBackGestureChannel().commitBackGesture(); + } else { + Log.w(TAG, "Invoked commitBackGesture() before FlutterFragment was attached to an Activity."); + } + } + + /** + * Invoke this from {@link OnBackAnimationCallback#onBackCancelled()}. + * + *
This method should be called when a back gesture is cancelled or the back button is pressed. + * It informs the Flutter framework about the cancellation. + * + *
This method enables the Flutter framework to rollback any UI changes or animations initiated + * in response to the back gesture. This includes resetting UI elements to their state prior to + * the gesture's start. + */ + @TargetApi(API_LEVELS.API_34) + @RequiresApi(API_LEVELS.API_34) + void cancelBackGesture() { + ensureAlive(); + if (flutterEngine != null) { + Log.v(TAG, "Forwarding cancelBackGesture() to FlutterEngine."); + flutterEngine.getBackGestureChannel().cancelBackGesture(); + } else { + Log.w(TAG, "Invoked cancelBackGesture() before FlutterFragment was attached to an Activity."); + } + } + /** * Invoke this from {@link android.app.Activity#onRequestPermissionsResult(int, String[], int[])} * or {@code Fragment#onRequestPermissionsResult(int, String[], int[])}. diff --git a/shell/platform/android/io/flutter/embedding/engine/FlutterEngine.java b/shell/platform/android/io/flutter/embedding/engine/FlutterEngine.java index 7ff6830c7bba5..4c80b90a603f7 100644 --- a/shell/platform/android/io/flutter/embedding/engine/FlutterEngine.java +++ b/shell/platform/android/io/flutter/embedding/engine/FlutterEngine.java @@ -25,6 +25,7 @@ import io.flutter.embedding.engine.renderer.FlutterRenderer; import io.flutter.embedding.engine.renderer.RenderSurface; import io.flutter.embedding.engine.systemchannels.AccessibilityChannel; +import io.flutter.embedding.engine.systemchannels.BackGestureChannel; import io.flutter.embedding.engine.systemchannels.DeferredComponentChannel; import io.flutter.embedding.engine.systemchannels.LifecycleChannel; import io.flutter.embedding.engine.systemchannels.LocalizationChannel; @@ -95,6 +96,7 @@ public class FlutterEngine implements ViewUtils.DisplayUpdater { @NonNull private final LocalizationChannel localizationChannel; @NonNull private final MouseCursorChannel mouseCursorChannel; @NonNull private final NavigationChannel navigationChannel; + @NonNull private final BackGestureChannel backGestureChannel; @NonNull private final RestorationChannel restorationChannel; @NonNull private final PlatformChannel platformChannel; @NonNull private final ProcessTextChannel processTextChannel; @@ -331,6 +333,7 @@ public FlutterEngine( localizationChannel = new LocalizationChannel(dartExecutor); mouseCursorChannel = new MouseCursorChannel(dartExecutor); navigationChannel = new NavigationChannel(dartExecutor); + backGestureChannel = new BackGestureChannel(dartExecutor); platformChannel = new PlatformChannel(dartExecutor); processTextChannel = new ProcessTextChannel(dartExecutor, context.getPackageManager()); restorationChannel = new RestorationChannel(dartExecutor, waitForRestorationData); @@ -541,6 +544,12 @@ public NavigationChannel getNavigationChannel() { return navigationChannel; } + /** System channel that sends back gesture commands from Android to Flutter. */ + @NonNull + public BackGestureChannel getBackGestureChannel() { + return backGestureChannel; + } + /** * System channel that sends platform-oriented requests and information to Flutter, e.g., requests * to play sounds, requests for haptics, system chrome settings, etc. diff --git a/shell/platform/android/io/flutter/embedding/engine/systemchannels/BackGestureChannel.java b/shell/platform/android/io/flutter/embedding/engine/systemchannels/BackGestureChannel.java new file mode 100644 index 0000000000000..6fb8997d46b54 --- /dev/null +++ b/shell/platform/android/io/flutter/embedding/engine/systemchannels/BackGestureChannel.java @@ -0,0 +1,131 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +package io.flutter.embedding.engine.systemchannels; + +import android.annotation.TargetApi; +import android.window.BackEvent; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.annotation.RequiresApi; +import io.flutter.Build.API_LEVELS; +import io.flutter.Log; +import io.flutter.embedding.engine.dart.DartExecutor; +import io.flutter.plugin.common.MethodCall; +import io.flutter.plugin.common.MethodChannel; +import io.flutter.plugin.common.StandardMethodCodec; +import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; + +/** + * A {@link MethodChannel} for communicating back gesture events to the Flutter framework. + * + *
The BackGestureChannel facilitates communication between the platform-specific Android back + * gesture handling code and the Flutter framework. It enables the dispatch of back gesture events + * such as start, progress, commit, and cancellation from the platform to the Flutter application. + */ +public class BackGestureChannel { + private static final String TAG = "BackGestureChannel"; + + @NonNull public final MethodChannel channel; + + /** + * Constructs a BackGestureChannel. + * + * @param dartExecutor The DartExecutor used to establish communication with the Flutter + * framework. + */ + public BackGestureChannel(@NonNull DartExecutor dartExecutor) { + this.channel = + new MethodChannel(dartExecutor, "flutter/backgesture", StandardMethodCodec.INSTANCE); + channel.setMethodCallHandler(defaultHandler); + } + + // Provide a default handler that returns an empty response to any messages + // on this channel. + private final MethodChannel.MethodCallHandler defaultHandler = + new MethodChannel.MethodCallHandler() { + @Override + public void onMethodCall(@NonNull MethodCall call, @NonNull MethodChannel.Result result) { + result.success(null); + } + }; + + /** + * Initiates a back gesture event. + * + *
This method should be called when the back gesture is initiated by the user. + * + * @param backEvent The BackEvent object containing information about the touch. + */ + @TargetApi(API_LEVELS.API_34) + @RequiresApi(API_LEVELS.API_34) + public void startBackGesture(@NonNull BackEvent backEvent) { + Log.v(TAG, "Sending message to start back gesture"); + channel.invokeMethod("startBackGesture", backEventToJsonMap(backEvent)); + } + + /** + * Updates the progress of a back gesture event. + * + *
This method should be called to update the progress of an ongoing back gesture event. + * + * @param backEvent An BackEvent object describing the progress event. + */ + @TargetApi(API_LEVELS.API_34) + @RequiresApi(API_LEVELS.API_34) + public void updateBackGestureProgress(@NonNull BackEvent backEvent) { + Log.v(TAG, "Sending message to update back gesture progress"); + channel.invokeMethod("updateBackGestureProgress", backEventToJsonMap(backEvent)); + } + + /** + * Commits the back gesture event. + * + *
This method should be called to signify the completion of a back gesture event and commit + * the navigation action initiated by the gesture. + */ + @TargetApi(API_LEVELS.API_34) + @RequiresApi(API_LEVELS.API_34) + public void commitBackGesture() { + Log.v(TAG, "Sending message to commit back gesture"); + channel.invokeMethod("commitBackGesture", null); + } + + /** + * Cancels the back gesture event. + * + *
This method should be called when a back gesture is cancelled or the back button is pressed.
+ */
+ @TargetApi(API_LEVELS.API_34)
+ @RequiresApi(API_LEVELS.API_34)
+ public void cancelBackGesture() {
+ Log.v(TAG, "Sending message to cancel back gesture");
+ channel.invokeMethod("cancelBackGesture", null);
+ }
+
+ /**
+ * Sets a method call handler for the channel.
+ *
+ * @param handler The handler to set for the channel.
+ */
+ public void setMethodCallHandler(@Nullable MethodChannel.MethodCallHandler handler) {
+ channel.setMethodCallHandler(handler);
+ }
+
+ @TargetApi(API_LEVELS.API_34)
+ @RequiresApi(API_LEVELS.API_34)
+ private Map