Skip to content
This repository was archived by the owner on Feb 25, 2025. It is now read-only.

Commit 00b0905

Browse files
authored
Platform channel for predictive back in route transitions on android (#49093)
Support for Android's predictive back feature on internal Flutter routes. Reports predictive back gestures to the framework (where supported by the system).
1 parent 5d13a29 commit 00b0905

File tree

9 files changed

+519
-15
lines changed

9 files changed

+519
-15
lines changed

ci/licenses_golden/licenses_flutter

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40968,6 +40968,7 @@ ORIGIN: ../../../flutter/shell/platform/android/io/flutter/embedding/engine/rend
4096840968
ORIGIN: ../../../flutter/shell/platform/android/io/flutter/embedding/engine/renderer/FlutterUiDisplayListener.java + ../../../flutter/LICENSE
4096940969
ORIGIN: ../../../flutter/shell/platform/android/io/flutter/embedding/engine/renderer/RenderSurface.java + ../../../flutter/LICENSE
4097040970
ORIGIN: ../../../flutter/shell/platform/android/io/flutter/embedding/engine/renderer/SurfaceTextureWrapper.java + ../../../flutter/LICENSE
40971+
ORIGIN: ../../../flutter/shell/platform/android/io/flutter/embedding/engine/systemchannels/BackGestureChannel.java + ../../../flutter/LICENSE
4097140972
ORIGIN: ../../../flutter/shell/platform/android/io/flutter/embedding/engine/systemchannels/DeferredComponentChannel.java + ../../../flutter/LICENSE
4097240973
ORIGIN: ../../../flutter/shell/platform/android/io/flutter/embedding/engine/systemchannels/KeyEventChannel.java + ../../../flutter/LICENSE
4097340974
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
4385443855
FILE: ../../../flutter/shell/platform/android/io/flutter/embedding/engine/renderer/SurfaceTextureSurfaceProducer.java
4385543856
FILE: ../../../flutter/shell/platform/android/io/flutter/embedding/engine/renderer/SurfaceTextureWrapper.java
4385643857
FILE: ../../../flutter/shell/platform/android/io/flutter/embedding/engine/systemchannels/AccessibilityChannel.java
43858+
FILE: ../../../flutter/shell/platform/android/io/flutter/embedding/engine/systemchannels/BackGestureChannel.java
4385743859
FILE: ../../../flutter/shell/platform/android/io/flutter/embedding/engine/systemchannels/DeferredComponentChannel.java
4385843860
FILE: ../../../flutter/shell/platform/android/io/flutter/embedding/engine/systemchannels/KeyEventChannel.java
4385943861
FILE: ../../../flutter/shell/platform/android/io/flutter/embedding/engine/systemchannels/KeyboardChannel.java

shell/platform/android/BUILD.gn

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -271,6 +271,7 @@ android_java_sources = [
271271
"io/flutter/embedding/engine/renderer/SurfaceTextureSurfaceProducer.java",
272272
"io/flutter/embedding/engine/renderer/SurfaceTextureWrapper.java",
273273
"io/flutter/embedding/engine/systemchannels/AccessibilityChannel.java",
274+
"io/flutter/embedding/engine/systemchannels/BackGestureChannel.java",
274275
"io/flutter/embedding/engine/systemchannels/DeferredComponentChannel.java",
275276
"io/flutter/embedding/engine/systemchannels/KeyEventChannel.java",
276277
"io/flutter/embedding/engine/systemchannels/KeyboardChannel.java",

shell/platform/android/io/flutter/embedding/android/FlutterActivity.java

Lines changed: 73 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
import static io.flutter.embedding.android.FlutterActivityLaunchConfigs.INITIAL_ROUTE_META_DATA_KEY;
2323
import static io.flutter.embedding.android.FlutterActivityLaunchConfigs.NORMAL_THEME_META_DATA_KEY;
2424

25+
import android.annotation.TargetApi;
2526
import android.app.Activity;
2627
import android.content.Context;
2728
import android.content.Intent;
@@ -35,10 +36,13 @@
3536
import android.view.View;
3637
import android.view.Window;
3738
import android.view.WindowManager;
39+
import android.window.BackEvent;
40+
import android.window.OnBackAnimationCallback;
3841
import android.window.OnBackInvokedCallback;
3942
import android.window.OnBackInvokedDispatcher;
4043
import androidx.annotation.NonNull;
4144
import androidx.annotation.Nullable;
45+
import androidx.annotation.RequiresApi;
4246
import androidx.annotation.VisibleForTesting;
4347
import androidx.lifecycle.Lifecycle;
4448
import androidx.lifecycle.LifecycleOwner;
@@ -678,18 +682,43 @@ public void unregisterOnBackInvokedCallback() {
678682
}
679683

680684
private final OnBackInvokedCallback onBackInvokedCallback =
681-
Build.VERSION.SDK_INT >= API_LEVELS.API_33
682-
? new OnBackInvokedCallback() {
683-
// TODO(garyq): Remove SuppressWarnings annotation. This was added to workaround
684-
// a google3 bug where the linter is not properly running against API 33, causing
685-
// a failure here. See b/243609613 and https://github.com/flutter/flutter/issues/111295
686-
@SuppressWarnings("Override")
687-
@Override
688-
public void onBackInvoked() {
689-
onBackPressed();
690-
}
691-
}
692-
: null;
685+
Build.VERSION.SDK_INT < API_LEVELS.API_33 ? null : createOnBackInvokedCallback();
686+
687+
@VisibleForTesting
688+
protected OnBackInvokedCallback getOnBackInvokedCallback() {
689+
return onBackInvokedCallback;
690+
}
691+
692+
@NonNull
693+
@TargetApi(API_LEVELS.API_33)
694+
@RequiresApi(API_LEVELS.API_33)
695+
private OnBackInvokedCallback createOnBackInvokedCallback() {
696+
if (Build.VERSION.SDK_INT >= API_LEVELS.API_34) {
697+
return new OnBackAnimationCallback() {
698+
@Override
699+
public void onBackInvoked() {
700+
commitBackGesture();
701+
}
702+
703+
@Override
704+
public void onBackCancelled() {
705+
cancelBackGesture();
706+
}
707+
708+
@Override
709+
public void onBackProgressed(@NonNull BackEvent backEvent) {
710+
updateBackGestureProgress(backEvent);
711+
}
712+
713+
@Override
714+
public void onBackStarted(@NonNull BackEvent backEvent) {
715+
startBackGesture(backEvent);
716+
}
717+
};
718+
}
719+
720+
return this::onBackPressed;
721+
}
693722

694723
@Override
695724
public void setFrameworkHandlesBack(boolean frameworkHandlesBack) {
@@ -899,6 +928,38 @@ public void onBackPressed() {
899928
}
900929
}
901930

931+
@TargetApi(API_LEVELS.API_34)
932+
@RequiresApi(API_LEVELS.API_34)
933+
public void startBackGesture(@NonNull BackEvent backEvent) {
934+
if (stillAttachedForEvent("startBackGesture")) {
935+
delegate.startBackGesture(backEvent);
936+
}
937+
}
938+
939+
@TargetApi(API_LEVELS.API_34)
940+
@RequiresApi(API_LEVELS.API_34)
941+
public void updateBackGestureProgress(@NonNull BackEvent backEvent) {
942+
if (stillAttachedForEvent("updateBackGestureProgress")) {
943+
delegate.updateBackGestureProgress(backEvent);
944+
}
945+
}
946+
947+
@TargetApi(API_LEVELS.API_34)
948+
@RequiresApi(API_LEVELS.API_34)
949+
public void commitBackGesture() {
950+
if (stillAttachedForEvent("commitBackGesture")) {
951+
delegate.commitBackGesture();
952+
}
953+
}
954+
955+
@TargetApi(API_LEVELS.API_34)
956+
@RequiresApi(API_LEVELS.API_34)
957+
public void cancelBackGesture() {
958+
if (stillAttachedForEvent("cancelBackGesture")) {
959+
delegate.cancelBackGesture();
960+
}
961+
}
962+
902963
@Override
903964
public void onRequestPermissionsResult(
904965
int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {

shell/platform/android/io/flutter/embedding/android/FlutterActivityAndFragmentDelegate.java

Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
import static android.content.ComponentCallbacks2.TRIM_MEMORY_RUNNING_LOW;
88
import static io.flutter.embedding.android.FlutterActivityLaunchConfigs.DEFAULT_INITIAL_ROUTE;
99

10+
import android.annotation.TargetApi;
1011
import android.app.Activity;
1112
import android.content.Context;
1213
import android.content.Intent;
@@ -16,10 +17,14 @@
1617
import android.view.View;
1718
import android.view.ViewGroup;
1819
import android.view.ViewTreeObserver.OnPreDrawListener;
20+
import android.window.BackEvent;
21+
import android.window.OnBackAnimationCallback;
1922
import androidx.annotation.NonNull;
2023
import androidx.annotation.Nullable;
24+
import androidx.annotation.RequiresApi;
2125
import androidx.annotation.VisibleForTesting;
2226
import androidx.lifecycle.Lifecycle;
27+
import io.flutter.Build.API_LEVELS;
2328
import io.flutter.FlutterInjector;
2429
import io.flutter.Log;
2530
import io.flutter.embedding.engine.FlutterEngine;
@@ -779,6 +784,100 @@ void onBackPressed() {
779784
}
780785
}
781786

787+
/**
788+
* Invoke this from {@link OnBackAnimationCallback#onBackStarted(BackEvent)}.
789+
*
790+
* <p>This method should be called when the back gesture is initiated. It should be invoked as
791+
* part of the implementation of {@link OnBackAnimationCallback}.
792+
*
793+
* <p>This method delegates the handling of the start of a back gesture to the Flutter framework,
794+
* which is responsible for the appropriate response, such as initiating animations or preparing
795+
* the UI for the back navigation process.
796+
*
797+
* @param backEvent The BackEvent object containing information about the touch.
798+
*/
799+
@TargetApi(API_LEVELS.API_34)
800+
@RequiresApi(API_LEVELS.API_34)
801+
void startBackGesture(@NonNull BackEvent backEvent) {
802+
ensureAlive();
803+
if (flutterEngine != null) {
804+
Log.v(TAG, "Forwarding startBackGesture() to FlutterEngine.");
805+
flutterEngine.getBackGestureChannel().startBackGesture(backEvent);
806+
} else {
807+
Log.w(TAG, "Invoked startBackGesture() before FlutterFragment was attached to an Activity.");
808+
}
809+
}
810+
811+
/**
812+
* Invoke this from {@link OnBackAnimationCallback#onBackProgressed(BackEvent)}.
813+
*
814+
* <p>This method should be called in response to progress in a back gesture, as part of the
815+
* implementation of {@link OnBackAnimationCallback}.
816+
*
817+
* <p>This method delegates to the Flutter framework to update UI elements or animations based on
818+
* the progression of the back gesture.
819+
*
820+
* @param backEvent An BackEvent object describing the progress event.
821+
*/
822+
@TargetApi(API_LEVELS.API_34)
823+
@RequiresApi(API_LEVELS.API_34)
824+
void updateBackGestureProgress(@NonNull BackEvent backEvent) {
825+
ensureAlive();
826+
if (flutterEngine != null) {
827+
Log.v(TAG, "Forwarding updateBackGestureProgress() to FlutterEngine.");
828+
flutterEngine.getBackGestureChannel().updateBackGestureProgress(backEvent);
829+
} else {
830+
Log.w(
831+
TAG,
832+
"Invoked updateBackGestureProgress() before FlutterFragment was attached to an Activity.");
833+
}
834+
}
835+
836+
/**
837+
* Invoke this from {@link OnBackAnimationCallback#onBackInvoked()}.
838+
*
839+
* <p>This method is called to signify the completion of a back gesture and commits the navigation
840+
* action initiated by the gesture. It should be invoked as the final step in handling a back
841+
* gesture.
842+
*
843+
* <p>This method indicates to the Flutter framework that it should proceed with the back
844+
* navigation, including finalizing animations and updating the UI to reflect the navigation
845+
* outcome.
846+
*/
847+
@TargetApi(API_LEVELS.API_34)
848+
@RequiresApi(API_LEVELS.API_34)
849+
void commitBackGesture() {
850+
ensureAlive();
851+
if (flutterEngine != null) {
852+
Log.v(TAG, "Forwarding commitBackGesture() to FlutterEngine.");
853+
flutterEngine.getBackGestureChannel().commitBackGesture();
854+
} else {
855+
Log.w(TAG, "Invoked commitBackGesture() before FlutterFragment was attached to an Activity.");
856+
}
857+
}
858+
859+
/**
860+
* Invoke this from {@link OnBackAnimationCallback#onBackCancelled()}.
861+
*
862+
* <p>This method should be called when a back gesture is cancelled or the back button is pressed.
863+
* It informs the Flutter framework about the cancellation.
864+
*
865+
* <p>This method enables the Flutter framework to rollback any UI changes or animations initiated
866+
* in response to the back gesture. This includes resetting UI elements to their state prior to
867+
* the gesture's start.
868+
*/
869+
@TargetApi(API_LEVELS.API_34)
870+
@RequiresApi(API_LEVELS.API_34)
871+
void cancelBackGesture() {
872+
ensureAlive();
873+
if (flutterEngine != null) {
874+
Log.v(TAG, "Forwarding cancelBackGesture() to FlutterEngine.");
875+
flutterEngine.getBackGestureChannel().cancelBackGesture();
876+
} else {
877+
Log.w(TAG, "Invoked cancelBackGesture() before FlutterFragment was attached to an Activity.");
878+
}
879+
}
880+
782881
/**
783882
* Invoke this from {@link android.app.Activity#onRequestPermissionsResult(int, String[], int[])}
784883
* or {@code Fragment#onRequestPermissionsResult(int, String[], int[])}.

shell/platform/android/io/flutter/embedding/engine/FlutterEngine.java

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
import io.flutter.embedding.engine.renderer.FlutterRenderer;
2626
import io.flutter.embedding.engine.renderer.RenderSurface;
2727
import io.flutter.embedding.engine.systemchannels.AccessibilityChannel;
28+
import io.flutter.embedding.engine.systemchannels.BackGestureChannel;
2829
import io.flutter.embedding.engine.systemchannels.DeferredComponentChannel;
2930
import io.flutter.embedding.engine.systemchannels.LifecycleChannel;
3031
import io.flutter.embedding.engine.systemchannels.LocalizationChannel;
@@ -95,6 +96,7 @@ public class FlutterEngine implements ViewUtils.DisplayUpdater {
9596
@NonNull private final LocalizationChannel localizationChannel;
9697
@NonNull private final MouseCursorChannel mouseCursorChannel;
9798
@NonNull private final NavigationChannel navigationChannel;
99+
@NonNull private final BackGestureChannel backGestureChannel;
98100
@NonNull private final RestorationChannel restorationChannel;
99101
@NonNull private final PlatformChannel platformChannel;
100102
@NonNull private final ProcessTextChannel processTextChannel;
@@ -331,6 +333,7 @@ public FlutterEngine(
331333
localizationChannel = new LocalizationChannel(dartExecutor);
332334
mouseCursorChannel = new MouseCursorChannel(dartExecutor);
333335
navigationChannel = new NavigationChannel(dartExecutor);
336+
backGestureChannel = new BackGestureChannel(dartExecutor);
334337
platformChannel = new PlatformChannel(dartExecutor);
335338
processTextChannel = new ProcessTextChannel(dartExecutor, context.getPackageManager());
336339
restorationChannel = new RestorationChannel(dartExecutor, waitForRestorationData);
@@ -541,6 +544,12 @@ public NavigationChannel getNavigationChannel() {
541544
return navigationChannel;
542545
}
543546

547+
/** System channel that sends back gesture commands from Android to Flutter. */
548+
@NonNull
549+
public BackGestureChannel getBackGestureChannel() {
550+
return backGestureChannel;
551+
}
552+
544553
/**
545554
* System channel that sends platform-oriented requests and information to Flutter, e.g., requests
546555
* to play sounds, requests for haptics, system chrome settings, etc.

0 commit comments

Comments
 (0)