Loading core/java/android/app/Activity.java +30 −0 Original line number Original line Diff line number Diff line Loading @@ -26,6 +26,8 @@ import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED; import static android.app.WindowConfiguration.inMultiWindowMode; import static android.app.WindowConfiguration.inMultiWindowMode; import static android.os.Process.myUid; import static android.os.Process.myUid; import static com.android.window.flags.Flags.predictiveBackStopKeycodeBackForwarding; import static java.lang.Character.MIN_VALUE; import static java.lang.Character.MIN_VALUE; import android.Manifest; import android.Manifest; Loading Loading @@ -166,6 +168,8 @@ import android.view.translation.UiTranslationSpec; import android.widget.AdapterView; import android.widget.AdapterView; import android.widget.Toast; import android.widget.Toast; import android.widget.Toolbar; import android.widget.Toolbar; import android.window.BackEvent; import android.window.ObserverOnBackAnimationCallback; import android.window.OnBackInvokedCallback; import android.window.OnBackInvokedCallback; import android.window.OnBackInvokedDispatcher; import android.window.OnBackInvokedDispatcher; import android.window.SplashScreen; import android.window.SplashScreen; Loading Loading @@ -822,6 +826,7 @@ public class Activity extends ContextThemeWrapper private static final int LOG_AM_ON_TOP_RESUMED_GAINED_CALLED = 30064; private static final int LOG_AM_ON_TOP_RESUMED_GAINED_CALLED = 30064; private static final int LOG_AM_ON_TOP_RESUMED_LOST_CALLED = 30065; private static final int LOG_AM_ON_TOP_RESUMED_LOST_CALLED = 30065; private OnBackInvokedCallback mDefaultBackCallback; private OnBackInvokedCallback mDefaultBackCallback; private ObserverOnBackAnimationCallback mObserverBackCallback; /** /** * After {@link Build.VERSION_CODES#TIRAMISU}, * After {@link Build.VERSION_CODES#TIRAMISU}, Loading Loading @@ -1923,6 +1928,27 @@ public class Activity extends ContextThemeWrapper mDefaultBackCallback = this::onBackInvoked; mDefaultBackCallback = this::onBackInvoked; getOnBackInvokedDispatcher().registerSystemOnBackInvokedCallback(mDefaultBackCallback); getOnBackInvokedDispatcher().registerSystemOnBackInvokedCallback(mDefaultBackCallback); } } if (predictiveBackStopKeycodeBackForwarding()) { mObserverBackCallback = new ObserverOnBackAnimationCallback() { @Override public void onBackStarted(@NonNull BackEvent backEvent) { onUserInteraction(); } @Override public void onBackInvoked() { onUserInteraction(); } @Override public void onBackCancelled() {} }; // Register a ObserverOnBackAnimationCallback with PRIORITY_SYSTEM_NAVIGATION_OBSERVER // to get notified on every back navigation so that onUserInteraction can be called. getOnBackInvokedDispatcher().registerOnBackInvokedCallback( OnBackInvokedDispatcher.PRIORITY_SYSTEM_NAVIGATION_OBSERVER, mObserverBackCallback); } } } /** /** Loading Loading @@ -3010,6 +3036,10 @@ public class Activity extends ContextThemeWrapper getOnBackInvokedDispatcher().unregisterOnBackInvokedCallback(mDefaultBackCallback); getOnBackInvokedDispatcher().unregisterOnBackInvokedCallback(mDefaultBackCallback); mDefaultBackCallback = null; mDefaultBackCallback = null; } } if (mObserverBackCallback != null) { getOnBackInvokedDispatcher().unregisterOnBackInvokedCallback(mObserverBackCallback); mObserverBackCallback = null; } if (mCallbacksController != null) { if (mCallbacksController != null) { mCallbacksController.clearCallbacks(); mCallbacksController.clearCallbacks(); Loading core/java/android/view/ViewRootImpl.java +22 −23 Original line number Original line Diff line number Diff line Loading @@ -135,6 +135,7 @@ import static com.android.text.flags.Flags.disableHandwritingInitiatorForIme; import static com.android.window.flags.Flags.alwaysSeqIdLayout; import static com.android.window.flags.Flags.alwaysSeqIdLayout; import static com.android.window.flags.Flags.alwaysSeqIdLayoutWear; import static com.android.window.flags.Flags.alwaysSeqIdLayoutWear; import static com.android.window.flags.Flags.enableWindowContextResourcesUpdateOnConfigChange; import static com.android.window.flags.Flags.enableWindowContextResourcesUpdateOnConfigChange; import static com.android.window.flags.Flags.predictiveBackStopKeycodeBackForwarding; import static com.android.window.flags.Flags.reduceChangedExclusionRectsMsgs; import static com.android.window.flags.Flags.reduceChangedExclusionRectsMsgs; import static com.android.window.flags.Flags.setScPropertiesInClient; import static com.android.window.flags.Flags.setScPropertiesInClient; Loading Loading @@ -275,7 +276,6 @@ import android.window.ClientWindowFrames; import android.window.CompatOnBackInvokedCallback; import android.window.CompatOnBackInvokedCallback; import android.window.ImeBackCallbackProxy; import android.window.ImeBackCallbackProxy; import android.window.InputTransferToken; import android.window.InputTransferToken; import android.window.OnBackAnimationCallback; import android.window.OnBackInvokedCallback; import android.window.OnBackInvokedCallback; import android.window.OnBackInvokedDispatcher; import android.window.OnBackInvokedDispatcher; import android.window.ScreenCaptureInternal; import android.window.ScreenCaptureInternal; Loading Loading @@ -7816,10 +7816,7 @@ public final class ViewRootImpl implements ViewParent, if (dispatcher.isBackGestureInProgress()) { if (dispatcher.isBackGestureInProgress()) { return FINISH_NOT_HANDLED; return FINISH_NOT_HANDLED; } } if (topCallback instanceof OnBackAnimationCallback if (topCallback != null) { && !(topCallback instanceof ImeBackAnimationController)) { final OnBackAnimationCallback animationCallback = (OnBackAnimationCallback) topCallback; switch (keyEvent.getAction()) { switch (keyEvent.getAction()) { case KeyEvent.ACTION_DOWN: case KeyEvent.ACTION_DOWN: // ACTION_DOWN is emitted twice: once when the user presses the button, // ACTION_DOWN is emitted twice: once when the user presses the button, Loading @@ -7828,29 +7825,30 @@ public final class ViewRootImpl implements ViewParent, // - 0 means the button was pressed. // - 0 means the button was pressed. // - 1 means the button continues to be pressed (long press). // - 1 means the button continues to be pressed (long press). if (keyEvent.getRepeatCount() == 0) { if (keyEvent.getRepeatCount() == 0) { animationCallback.onBackStarted( dispatcher.onBackStarted(topCallback, new BackEvent(0, 0, 0f, BackEvent.EDGE_NONE)); new BackEvent(0, 0, 0f, BackEvent.EDGE_NONE), /* observerOnly */ topCallback instanceof ImeBackAnimationController); } } break; break; case KeyEvent.ACTION_UP: case KeyEvent.ACTION_UP: if (keyEvent.isCanceled()) { if (keyEvent.isCanceled()) { animationCallback.onBackCancelled(); dispatcher.onBackCancelled(topCallback); } else { } else { dispatcher.tryInvokeSystemNavigationObserverCallbacks(); dispatcher.onBackInvoked(topCallback); topCallback.onBackInvoked(); if (predictiveBackStopKeycodeBackForwarding()) { return FINISH_HANDLED; } } } break; break; } } } else if (topCallback != null) { if (keyEvent.getAction() == KeyEvent.ACTION_UP) { if (!keyEvent.isCanceled()) { dispatcher.tryInvokeSystemNavigationObserverCallbacks(); topCallback.onBackInvoked(); } else { } else { Log.d(mTag, "Skip onBackInvoked(), reason: keyEvent.isCanceled=true"); if (predictiveBackStopKeycodeBackForwarding()) { } return FORWARD; } } } } if (predictiveBackStopKeycodeBackForwarding()) { return FINISH_NOT_HANDLED; } else { // Do not cancel the keyEvent if no callback can handle the back event. // Do not cancel the keyEvent if no callback can handle the back event. if (topCallback != null && keyEvent.getAction() == KeyEvent.ACTION_UP) { if (topCallback != null && keyEvent.getAction() == KeyEvent.ACTION_UP) { // forward a cancelled event so that following stages cancel their back logic // forward a cancelled event so that following stages cancel their back logic Loading @@ -7858,6 +7856,7 @@ public final class ViewRootImpl implements ViewParent, } } return FORWARD; return FORWARD; } } } @Override @Override public void onFinishedInputEvent(Object token, boolean handled) { public void onFinishedInputEvent(Object token, boolean handled) { Loading core/java/android/window/ObserverOnBackAnimationCallback.java 0 → 100644 +41 −0 Original line number Original line Diff line number Diff line /* * Copyright (C) 2025 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package android.window; import android.annotation.NonNull; /** * Privileged marker interface for {@link OnBackAnimationCallback} that's only available to system * components. When registered with * {@link OnBackInvokedDispatcher#PRIORITY_SYSTEM_NAVIGATION_OBSERVER}, this callback gets * {@link OnBackAnimationCallback#onBackStarted}, {@link OnBackAnimationCallback#onBackInvoked()} * and {@link OnBackAnimationCallback#onBackCancelled()} callbacks whenever ANY back navigation * happens, including any non-system back navigation. * @hide */ public interface ObserverOnBackAnimationCallback extends OnBackAnimationCallback { @Override void onBackStarted(@NonNull BackEvent backEvent); @Override void onBackInvoked(); @Override void onBackCancelled(); } core/java/android/window/WindowOnBackInvokedDispatcher.java +90 −38 Original line number Original line Diff line number Diff line Loading @@ -20,6 +20,7 @@ import static android.window.SystemOverrideOnBackInvokedCallback.OVERRIDE_UNDEFI import static com.android.window.flags.Flags.multipleSystemNavigationObserverCallbacks; import static com.android.window.flags.Flags.multipleSystemNavigationObserverCallbacks; import static com.android.window.flags.Flags.predictiveBackCallbackCancellationFix; import static com.android.window.flags.Flags.predictiveBackCallbackCancellationFix; import static com.android.window.flags.Flags.predictiveBackStopKeycodeBackForwarding; import android.annotation.NonNull; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.Nullable; Loading Loading @@ -58,6 +59,7 @@ import java.util.Objects; import java.util.Set; import java.util.Set; import java.util.TreeMap; import java.util.TreeMap; import java.util.function.BooleanSupplier; import java.util.function.BooleanSupplier; import java.util.function.Consumer; import java.util.function.Supplier; import java.util.function.Supplier; /** /** Loading Loading @@ -425,31 +427,88 @@ public class WindowOnBackInvokedDispatcher implements OnBackInvokedDispatcher { } } /** /** * Tries to call {@link OnBackInvokedCallback#onBackInvoked} on all system navigation observer * Calls the {@link OnBackAnimationCallback#onBackStarted} function of the provided callback * callback (if any are registered and if the top-most regular callback has * if it is an instance of {@link OnBackAnimationCallback}. * {@link OnBackInvokedDispatcher#PRIORITY_SYSTEM}) * Additionally invokes any registered observer callbacks if necessary. * * @param callback The callback to call {@link OnBackAnimationCallback#onBackStarted} on. * @param backEvent The back event to pass to the callback. * @param observerOnly Whether to only invoke observer callbacks. */ */ public void tryInvokeSystemNavigationObserverCallbacks() { public void onBackStarted(OnBackInvokedCallback callback, BackEvent backEvent, OnBackInvokedCallback topCallback = getTopCallback(); boolean observerOnly) { Integer callbackPriority = mAllCallbacks.getOrDefault(topCallback, null); if (predictiveBackStopKeycodeBackForwarding()) { final boolean isSystemOverride = topCallback instanceof SystemOverrideOnBackInvokedCallback; forEachObserverCallback((observerCallback) -> { if ((callbackPriority != null && callbackPriority == PRIORITY_SYSTEM) || isSystemOverride) { if (observerCallback instanceof ObserverOnBackAnimationCallback) { invokeSystemNavigationObserverCallback(); ((ObserverOnBackAnimationCallback) observerCallback).onBackStarted(backEvent); } }); } if (callback instanceof OnBackAnimationCallback && !observerOnly) { ((OnBackAnimationCallback) callback).onBackStarted(backEvent); } } /** * Calls the {@link OnBackAnimationCallback#onBackCancelled} function of the provided callback * if it is an instance of {@link OnBackAnimationCallback}. * Additionally invokes any registered observer callbacks if necessary. * * @param callback The callback to call {@link OnBackAnimationCallback#onBackCancelled} on. */ public void onBackCancelled(OnBackInvokedCallback callback) { if (predictiveBackStopKeycodeBackForwarding()) { forEachObserverCallback((observerCallback) -> { if (observerCallback instanceof ObserverOnBackAnimationCallback) { ((ObserverOnBackAnimationCallback) observerCallback).onBackCancelled(); } }); } if (callback instanceof OnBackAnimationCallback) { ((OnBackAnimationCallback) callback).onBackCancelled(); } } } } private void invokeSystemNavigationObserverCallback() { /** * Calls the {@link OnBackInvokedCallback#onBackInvoked} function of the provided callback. * Additionally invokes any registered observer callbacks if necessary. * @param callback The callback to call {@link OnBackInvokedCallback#onBackInvoked} on. */ public void onBackInvoked(OnBackInvokedCallback callback) { Integer callbackPriority = mAllCallbacks.getOrDefault(callback, null); final boolean isSystemOverride = callback instanceof SystemOverrideOnBackInvokedCallback; final boolean isSystemCallback = isSystemOverride || (callbackPriority != null && callbackPriority == PRIORITY_SYSTEM); forEachObserverCallback((observerCallback) -> { if (isSystemCallback || observerCallback instanceof ObserverOnBackAnimationCallback) { // Call observer callback whenever there is a system back navigation happening. // When there is a ObserverOnBackAnimationCallback registered (which is only // available for system components), call it for non-system back navigation too observerCallback.onBackInvoked(); } }); callback.onBackInvoked(); } /** * Executes a given action on all registered observer callback. * * @param action The action to execute for each callback. */ private void forEachObserverCallback(Consumer<OnBackInvokedCallback> action) { if (multipleSystemNavigationObserverCallbacks()) { if (multipleSystemNavigationObserverCallbacks()) { if (mSystemNavigationObserverCallbacks.isEmpty()) return; Set<OnBackInvokedCallback> observerCallbacks; Set<OnBackInvokedCallback> observerCallbacks; synchronized (mLock) { synchronized (mLock) { observerCallbacks = new HashSet<>(mSystemNavigationObserverCallbacks); observerCallbacks = new HashSet<>(mSystemNavigationObserverCallbacks); } } for (OnBackInvokedCallback callback : observerCallbacks) { for (OnBackInvokedCallback observerCallback : observerCallbacks) { callback.onBackInvoked(); action.accept(observerCallback); } } } else { } else { if (mSystemNavigationObserverCallback != null) { if (mSystemNavigationObserverCallback != null) { mSystemNavigationObserverCallback.onBackInvoked(); action.accept(mSystemNavigationObserverCallback); } } } } } } Loading @@ -467,12 +526,12 @@ public class WindowOnBackInvokedDispatcher implements OnBackInvokedDispatcher { overrideAnimation = ((SystemOverrideOnBackInvokedCallback) callback) overrideAnimation = ((SystemOverrideOnBackInvokedCallback) callback) .overrideBehavior(); .overrideBehavior(); } } final boolean isSystemCallback = priority == PRIORITY_SYSTEM || overrideAnimation != OVERRIDE_UNDEFINED; final WeakReference<OnBackInvokedCallback> weakRefCallback = new WeakReference<>( final IOnBackInvokedCallback iCallback = new OnBackInvokedCallbackWrapper(callback, callback); mTouchTracker, mProgressAnimator, mHandler, this::callOnKeyPreIme, final IOnBackInvokedCallback iCallback = new OnBackInvokedCallbackWrapper( this::invokeSystemNavigationObserverCallback, weakRefCallback, mTouchTracker, mProgressAnimator, mHandler, isSystemCallback /*isSystemCallback*/); this::callOnKeyPreIme); callbackInfo = new OnBackInvokedCallbackInfo( callbackInfo = new OnBackInvokedCallbackInfo( iCallback, iCallback, priority, priority, Loading Loading @@ -564,7 +623,7 @@ public class WindowOnBackInvokedDispatcher implements OnBackInvokedDispatcher { } } } } private static class OnBackInvokedCallbackWrapper extends IOnBackInvokedCallback.Stub { private class OnBackInvokedCallbackWrapper extends IOnBackInvokedCallback.Stub { @NonNull @NonNull private final WeakReference<OnBackInvokedCallback> mCallback; private final WeakReference<OnBackInvokedCallback> mCallback; @NonNull @NonNull Loading @@ -575,26 +634,19 @@ public class WindowOnBackInvokedDispatcher implements OnBackInvokedDispatcher { private final Handler mHandler; private final Handler mHandler; @NonNull @NonNull private final BooleanSupplier mOnKeyPreIme; private final BooleanSupplier mOnKeyPreIme; @NonNull private final Runnable mSystemNavigationObserverCallbackRunnable; private final boolean mIsSystemCallback; OnBackInvokedCallbackWrapper( OnBackInvokedCallbackWrapper( @NonNull OnBackInvokedCallback callback, @NonNull WeakReference<OnBackInvokedCallback> callback, @NonNull BackTouchTracker touchTracker, @NonNull BackTouchTracker touchTracker, @NonNull BackProgressAnimator progressAnimator, @NonNull BackProgressAnimator progressAnimator, @NonNull Handler handler, @NonNull Handler handler, @NonNull BooleanSupplier onKeyPreIme, @NonNull BooleanSupplier onKeyPreIme @NonNull Runnable systemNavigationObserverCallbackRunnable, boolean isSystemCallback ) { ) { mCallback = new WeakReference<>(callback); mCallback = callback; mTouchTracker = touchTracker; mTouchTracker = touchTracker; mProgressAnimator = progressAnimator; mProgressAnimator = progressAnimator; mHandler = handler; mHandler = handler; mOnKeyPreIme = onKeyPreIme; mOnKeyPreIme = onKeyPreIme; mSystemNavigationObserverCallbackRunnable = systemNavigationObserverCallbackRunnable; mIsSystemCallback = isSystemCallback; } } @Override @Override Loading @@ -613,14 +665,14 @@ public class WindowOnBackInvokedDispatcher implements OnBackInvokedDispatcher { mTouchTracker.setGestureStartLocation( mTouchTracker.setGestureStartLocation( backEvent.getTouchX(), backEvent.getTouchY(), backEvent.getSwipeEdge()); backEvent.getTouchX(), backEvent.getTouchY(), backEvent.getSwipeEdge()); WindowOnBackInvokedDispatcher.this.onBackStarted(mCallback.get(), BackEvent.fromBackMotionEvent(backEvent), /* observerOnly */ false); if (callback != null) { if (callback != null) { callback.onBackStarted(BackEvent.fromBackMotionEvent(backEvent)); if (predictiveBackCallbackCancellationFix()) { if (predictiveBackCallbackCancellationFix()) { mProgressAnimator.onBackStarted(backEvent, callback::onBackProgressed, mProgressAnimator.onBackStarted(backEvent, callback::onBackProgressed, callback); callback); } else { } else { mProgressAnimator.onBackStarted(backEvent, callback::onBackProgressed); mProgressAnimator.onBackStarted(backEvent, callback::onBackProgressed); } } } } }); }); Loading Loading @@ -649,7 +701,9 @@ public class WindowOnBackInvokedDispatcher implements OnBackInvokedDispatcher { final OnBackAnimationCallback callback = getBackAnimationCallback(); final OnBackAnimationCallback callback = getBackAnimationCallback(); mTouchTracker.reset(); mTouchTracker.reset(); if (callback == null) return; if (callback == null) return; mProgressAnimator.onBackCancelled(callback::onBackCancelled); mProgressAnimator.onBackCancelled( () -> WindowOnBackInvokedDispatcher.this.onBackCancelled(callback) ); }); }); } } Loading @@ -670,10 +724,7 @@ public class WindowOnBackInvokedDispatcher implements OnBackInvokedDispatcher { return; return; } } mProgressAnimator.reset(); mProgressAnimator.reset(); if (mIsSystemCallback) { WindowOnBackInvokedDispatcher.this.onBackInvoked(callback); mSystemNavigationObserverCallbackRunnable.run(); } callback.onBackInvoked(); }); }); } } Loading Loading @@ -774,7 +825,8 @@ public class WindowOnBackInvokedDispatcher implements OnBackInvokedDispatcher { return false; return false; } } if (!WindowOnBackInvokedDispatcher.isOnBackInvokedCallbackEnabled(hostContext) if (!WindowOnBackInvokedDispatcher.isOnBackInvokedCallbackEnabled(hostContext) && !(callback instanceof CompatOnBackInvokedCallback)) { && !(callback instanceof CompatOnBackInvokedCallback || callback instanceof ObserverOnBackAnimationCallback)) { Log.w(TAG, Log.w(TAG, "OnBackInvokedCallback is not enabled for the application." "OnBackInvokedCallback is not enabled for the application." + "\nSet 'android:enableOnBackInvokedCallback=\"true\"' in the" + "\nSet 'android:enableOnBackInvokedCallback=\"true\"' in the" Loading core/java/android/window/flags/windowing_frontend.aconfig +10 −0 Original line number Original line Diff line number Diff line Loading @@ -482,3 +482,13 @@ flag { purpose: PURPOSE_BUGFIX purpose: PURPOSE_BUGFIX } } } } flag { name: "predictive_back_stop_keycode_back_forwarding" namespace: "windowing_frontend" description: "No longer forward KEYCODE_BACK events when app has enableOnBackInvokedCallback=true" bug: "436871339" metadata { purpose: PURPOSE_BUGFIX } } No newline at end of file Loading
core/java/android/app/Activity.java +30 −0 Original line number Original line Diff line number Diff line Loading @@ -26,6 +26,8 @@ import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED; import static android.app.WindowConfiguration.inMultiWindowMode; import static android.app.WindowConfiguration.inMultiWindowMode; import static android.os.Process.myUid; import static android.os.Process.myUid; import static com.android.window.flags.Flags.predictiveBackStopKeycodeBackForwarding; import static java.lang.Character.MIN_VALUE; import static java.lang.Character.MIN_VALUE; import android.Manifest; import android.Manifest; Loading Loading @@ -166,6 +168,8 @@ import android.view.translation.UiTranslationSpec; import android.widget.AdapterView; import android.widget.AdapterView; import android.widget.Toast; import android.widget.Toast; import android.widget.Toolbar; import android.widget.Toolbar; import android.window.BackEvent; import android.window.ObserverOnBackAnimationCallback; import android.window.OnBackInvokedCallback; import android.window.OnBackInvokedCallback; import android.window.OnBackInvokedDispatcher; import android.window.OnBackInvokedDispatcher; import android.window.SplashScreen; import android.window.SplashScreen; Loading Loading @@ -822,6 +826,7 @@ public class Activity extends ContextThemeWrapper private static final int LOG_AM_ON_TOP_RESUMED_GAINED_CALLED = 30064; private static final int LOG_AM_ON_TOP_RESUMED_GAINED_CALLED = 30064; private static final int LOG_AM_ON_TOP_RESUMED_LOST_CALLED = 30065; private static final int LOG_AM_ON_TOP_RESUMED_LOST_CALLED = 30065; private OnBackInvokedCallback mDefaultBackCallback; private OnBackInvokedCallback mDefaultBackCallback; private ObserverOnBackAnimationCallback mObserverBackCallback; /** /** * After {@link Build.VERSION_CODES#TIRAMISU}, * After {@link Build.VERSION_CODES#TIRAMISU}, Loading Loading @@ -1923,6 +1928,27 @@ public class Activity extends ContextThemeWrapper mDefaultBackCallback = this::onBackInvoked; mDefaultBackCallback = this::onBackInvoked; getOnBackInvokedDispatcher().registerSystemOnBackInvokedCallback(mDefaultBackCallback); getOnBackInvokedDispatcher().registerSystemOnBackInvokedCallback(mDefaultBackCallback); } } if (predictiveBackStopKeycodeBackForwarding()) { mObserverBackCallback = new ObserverOnBackAnimationCallback() { @Override public void onBackStarted(@NonNull BackEvent backEvent) { onUserInteraction(); } @Override public void onBackInvoked() { onUserInteraction(); } @Override public void onBackCancelled() {} }; // Register a ObserverOnBackAnimationCallback with PRIORITY_SYSTEM_NAVIGATION_OBSERVER // to get notified on every back navigation so that onUserInteraction can be called. getOnBackInvokedDispatcher().registerOnBackInvokedCallback( OnBackInvokedDispatcher.PRIORITY_SYSTEM_NAVIGATION_OBSERVER, mObserverBackCallback); } } } /** /** Loading Loading @@ -3010,6 +3036,10 @@ public class Activity extends ContextThemeWrapper getOnBackInvokedDispatcher().unregisterOnBackInvokedCallback(mDefaultBackCallback); getOnBackInvokedDispatcher().unregisterOnBackInvokedCallback(mDefaultBackCallback); mDefaultBackCallback = null; mDefaultBackCallback = null; } } if (mObserverBackCallback != null) { getOnBackInvokedDispatcher().unregisterOnBackInvokedCallback(mObserverBackCallback); mObserverBackCallback = null; } if (mCallbacksController != null) { if (mCallbacksController != null) { mCallbacksController.clearCallbacks(); mCallbacksController.clearCallbacks(); Loading
core/java/android/view/ViewRootImpl.java +22 −23 Original line number Original line Diff line number Diff line Loading @@ -135,6 +135,7 @@ import static com.android.text.flags.Flags.disableHandwritingInitiatorForIme; import static com.android.window.flags.Flags.alwaysSeqIdLayout; import static com.android.window.flags.Flags.alwaysSeqIdLayout; import static com.android.window.flags.Flags.alwaysSeqIdLayoutWear; import static com.android.window.flags.Flags.alwaysSeqIdLayoutWear; import static com.android.window.flags.Flags.enableWindowContextResourcesUpdateOnConfigChange; import static com.android.window.flags.Flags.enableWindowContextResourcesUpdateOnConfigChange; import static com.android.window.flags.Flags.predictiveBackStopKeycodeBackForwarding; import static com.android.window.flags.Flags.reduceChangedExclusionRectsMsgs; import static com.android.window.flags.Flags.reduceChangedExclusionRectsMsgs; import static com.android.window.flags.Flags.setScPropertiesInClient; import static com.android.window.flags.Flags.setScPropertiesInClient; Loading Loading @@ -275,7 +276,6 @@ import android.window.ClientWindowFrames; import android.window.CompatOnBackInvokedCallback; import android.window.CompatOnBackInvokedCallback; import android.window.ImeBackCallbackProxy; import android.window.ImeBackCallbackProxy; import android.window.InputTransferToken; import android.window.InputTransferToken; import android.window.OnBackAnimationCallback; import android.window.OnBackInvokedCallback; import android.window.OnBackInvokedCallback; import android.window.OnBackInvokedDispatcher; import android.window.OnBackInvokedDispatcher; import android.window.ScreenCaptureInternal; import android.window.ScreenCaptureInternal; Loading Loading @@ -7816,10 +7816,7 @@ public final class ViewRootImpl implements ViewParent, if (dispatcher.isBackGestureInProgress()) { if (dispatcher.isBackGestureInProgress()) { return FINISH_NOT_HANDLED; return FINISH_NOT_HANDLED; } } if (topCallback instanceof OnBackAnimationCallback if (topCallback != null) { && !(topCallback instanceof ImeBackAnimationController)) { final OnBackAnimationCallback animationCallback = (OnBackAnimationCallback) topCallback; switch (keyEvent.getAction()) { switch (keyEvent.getAction()) { case KeyEvent.ACTION_DOWN: case KeyEvent.ACTION_DOWN: // ACTION_DOWN is emitted twice: once when the user presses the button, // ACTION_DOWN is emitted twice: once when the user presses the button, Loading @@ -7828,29 +7825,30 @@ public final class ViewRootImpl implements ViewParent, // - 0 means the button was pressed. // - 0 means the button was pressed. // - 1 means the button continues to be pressed (long press). // - 1 means the button continues to be pressed (long press). if (keyEvent.getRepeatCount() == 0) { if (keyEvent.getRepeatCount() == 0) { animationCallback.onBackStarted( dispatcher.onBackStarted(topCallback, new BackEvent(0, 0, 0f, BackEvent.EDGE_NONE)); new BackEvent(0, 0, 0f, BackEvent.EDGE_NONE), /* observerOnly */ topCallback instanceof ImeBackAnimationController); } } break; break; case KeyEvent.ACTION_UP: case KeyEvent.ACTION_UP: if (keyEvent.isCanceled()) { if (keyEvent.isCanceled()) { animationCallback.onBackCancelled(); dispatcher.onBackCancelled(topCallback); } else { } else { dispatcher.tryInvokeSystemNavigationObserverCallbacks(); dispatcher.onBackInvoked(topCallback); topCallback.onBackInvoked(); if (predictiveBackStopKeycodeBackForwarding()) { return FINISH_HANDLED; } } } break; break; } } } else if (topCallback != null) { if (keyEvent.getAction() == KeyEvent.ACTION_UP) { if (!keyEvent.isCanceled()) { dispatcher.tryInvokeSystemNavigationObserverCallbacks(); topCallback.onBackInvoked(); } else { } else { Log.d(mTag, "Skip onBackInvoked(), reason: keyEvent.isCanceled=true"); if (predictiveBackStopKeycodeBackForwarding()) { } return FORWARD; } } } } if (predictiveBackStopKeycodeBackForwarding()) { return FINISH_NOT_HANDLED; } else { // Do not cancel the keyEvent if no callback can handle the back event. // Do not cancel the keyEvent if no callback can handle the back event. if (topCallback != null && keyEvent.getAction() == KeyEvent.ACTION_UP) { if (topCallback != null && keyEvent.getAction() == KeyEvent.ACTION_UP) { // forward a cancelled event so that following stages cancel their back logic // forward a cancelled event so that following stages cancel their back logic Loading @@ -7858,6 +7856,7 @@ public final class ViewRootImpl implements ViewParent, } } return FORWARD; return FORWARD; } } } @Override @Override public void onFinishedInputEvent(Object token, boolean handled) { public void onFinishedInputEvent(Object token, boolean handled) { Loading
core/java/android/window/ObserverOnBackAnimationCallback.java 0 → 100644 +41 −0 Original line number Original line Diff line number Diff line /* * Copyright (C) 2025 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package android.window; import android.annotation.NonNull; /** * Privileged marker interface for {@link OnBackAnimationCallback} that's only available to system * components. When registered with * {@link OnBackInvokedDispatcher#PRIORITY_SYSTEM_NAVIGATION_OBSERVER}, this callback gets * {@link OnBackAnimationCallback#onBackStarted}, {@link OnBackAnimationCallback#onBackInvoked()} * and {@link OnBackAnimationCallback#onBackCancelled()} callbacks whenever ANY back navigation * happens, including any non-system back navigation. * @hide */ public interface ObserverOnBackAnimationCallback extends OnBackAnimationCallback { @Override void onBackStarted(@NonNull BackEvent backEvent); @Override void onBackInvoked(); @Override void onBackCancelled(); }
core/java/android/window/WindowOnBackInvokedDispatcher.java +90 −38 Original line number Original line Diff line number Diff line Loading @@ -20,6 +20,7 @@ import static android.window.SystemOverrideOnBackInvokedCallback.OVERRIDE_UNDEFI import static com.android.window.flags.Flags.multipleSystemNavigationObserverCallbacks; import static com.android.window.flags.Flags.multipleSystemNavigationObserverCallbacks; import static com.android.window.flags.Flags.predictiveBackCallbackCancellationFix; import static com.android.window.flags.Flags.predictiveBackCallbackCancellationFix; import static com.android.window.flags.Flags.predictiveBackStopKeycodeBackForwarding; import android.annotation.NonNull; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.Nullable; Loading Loading @@ -58,6 +59,7 @@ import java.util.Objects; import java.util.Set; import java.util.Set; import java.util.TreeMap; import java.util.TreeMap; import java.util.function.BooleanSupplier; import java.util.function.BooleanSupplier; import java.util.function.Consumer; import java.util.function.Supplier; import java.util.function.Supplier; /** /** Loading Loading @@ -425,31 +427,88 @@ public class WindowOnBackInvokedDispatcher implements OnBackInvokedDispatcher { } } /** /** * Tries to call {@link OnBackInvokedCallback#onBackInvoked} on all system navigation observer * Calls the {@link OnBackAnimationCallback#onBackStarted} function of the provided callback * callback (if any are registered and if the top-most regular callback has * if it is an instance of {@link OnBackAnimationCallback}. * {@link OnBackInvokedDispatcher#PRIORITY_SYSTEM}) * Additionally invokes any registered observer callbacks if necessary. * * @param callback The callback to call {@link OnBackAnimationCallback#onBackStarted} on. * @param backEvent The back event to pass to the callback. * @param observerOnly Whether to only invoke observer callbacks. */ */ public void tryInvokeSystemNavigationObserverCallbacks() { public void onBackStarted(OnBackInvokedCallback callback, BackEvent backEvent, OnBackInvokedCallback topCallback = getTopCallback(); boolean observerOnly) { Integer callbackPriority = mAllCallbacks.getOrDefault(topCallback, null); if (predictiveBackStopKeycodeBackForwarding()) { final boolean isSystemOverride = topCallback instanceof SystemOverrideOnBackInvokedCallback; forEachObserverCallback((observerCallback) -> { if ((callbackPriority != null && callbackPriority == PRIORITY_SYSTEM) || isSystemOverride) { if (observerCallback instanceof ObserverOnBackAnimationCallback) { invokeSystemNavigationObserverCallback(); ((ObserverOnBackAnimationCallback) observerCallback).onBackStarted(backEvent); } }); } if (callback instanceof OnBackAnimationCallback && !observerOnly) { ((OnBackAnimationCallback) callback).onBackStarted(backEvent); } } /** * Calls the {@link OnBackAnimationCallback#onBackCancelled} function of the provided callback * if it is an instance of {@link OnBackAnimationCallback}. * Additionally invokes any registered observer callbacks if necessary. * * @param callback The callback to call {@link OnBackAnimationCallback#onBackCancelled} on. */ public void onBackCancelled(OnBackInvokedCallback callback) { if (predictiveBackStopKeycodeBackForwarding()) { forEachObserverCallback((observerCallback) -> { if (observerCallback instanceof ObserverOnBackAnimationCallback) { ((ObserverOnBackAnimationCallback) observerCallback).onBackCancelled(); } }); } if (callback instanceof OnBackAnimationCallback) { ((OnBackAnimationCallback) callback).onBackCancelled(); } } } } private void invokeSystemNavigationObserverCallback() { /** * Calls the {@link OnBackInvokedCallback#onBackInvoked} function of the provided callback. * Additionally invokes any registered observer callbacks if necessary. * @param callback The callback to call {@link OnBackInvokedCallback#onBackInvoked} on. */ public void onBackInvoked(OnBackInvokedCallback callback) { Integer callbackPriority = mAllCallbacks.getOrDefault(callback, null); final boolean isSystemOverride = callback instanceof SystemOverrideOnBackInvokedCallback; final boolean isSystemCallback = isSystemOverride || (callbackPriority != null && callbackPriority == PRIORITY_SYSTEM); forEachObserverCallback((observerCallback) -> { if (isSystemCallback || observerCallback instanceof ObserverOnBackAnimationCallback) { // Call observer callback whenever there is a system back navigation happening. // When there is a ObserverOnBackAnimationCallback registered (which is only // available for system components), call it for non-system back navigation too observerCallback.onBackInvoked(); } }); callback.onBackInvoked(); } /** * Executes a given action on all registered observer callback. * * @param action The action to execute for each callback. */ private void forEachObserverCallback(Consumer<OnBackInvokedCallback> action) { if (multipleSystemNavigationObserverCallbacks()) { if (multipleSystemNavigationObserverCallbacks()) { if (mSystemNavigationObserverCallbacks.isEmpty()) return; Set<OnBackInvokedCallback> observerCallbacks; Set<OnBackInvokedCallback> observerCallbacks; synchronized (mLock) { synchronized (mLock) { observerCallbacks = new HashSet<>(mSystemNavigationObserverCallbacks); observerCallbacks = new HashSet<>(mSystemNavigationObserverCallbacks); } } for (OnBackInvokedCallback callback : observerCallbacks) { for (OnBackInvokedCallback observerCallback : observerCallbacks) { callback.onBackInvoked(); action.accept(observerCallback); } } } else { } else { if (mSystemNavigationObserverCallback != null) { if (mSystemNavigationObserverCallback != null) { mSystemNavigationObserverCallback.onBackInvoked(); action.accept(mSystemNavigationObserverCallback); } } } } } } Loading @@ -467,12 +526,12 @@ public class WindowOnBackInvokedDispatcher implements OnBackInvokedDispatcher { overrideAnimation = ((SystemOverrideOnBackInvokedCallback) callback) overrideAnimation = ((SystemOverrideOnBackInvokedCallback) callback) .overrideBehavior(); .overrideBehavior(); } } final boolean isSystemCallback = priority == PRIORITY_SYSTEM || overrideAnimation != OVERRIDE_UNDEFINED; final WeakReference<OnBackInvokedCallback> weakRefCallback = new WeakReference<>( final IOnBackInvokedCallback iCallback = new OnBackInvokedCallbackWrapper(callback, callback); mTouchTracker, mProgressAnimator, mHandler, this::callOnKeyPreIme, final IOnBackInvokedCallback iCallback = new OnBackInvokedCallbackWrapper( this::invokeSystemNavigationObserverCallback, weakRefCallback, mTouchTracker, mProgressAnimator, mHandler, isSystemCallback /*isSystemCallback*/); this::callOnKeyPreIme); callbackInfo = new OnBackInvokedCallbackInfo( callbackInfo = new OnBackInvokedCallbackInfo( iCallback, iCallback, priority, priority, Loading Loading @@ -564,7 +623,7 @@ public class WindowOnBackInvokedDispatcher implements OnBackInvokedDispatcher { } } } } private static class OnBackInvokedCallbackWrapper extends IOnBackInvokedCallback.Stub { private class OnBackInvokedCallbackWrapper extends IOnBackInvokedCallback.Stub { @NonNull @NonNull private final WeakReference<OnBackInvokedCallback> mCallback; private final WeakReference<OnBackInvokedCallback> mCallback; @NonNull @NonNull Loading @@ -575,26 +634,19 @@ public class WindowOnBackInvokedDispatcher implements OnBackInvokedDispatcher { private final Handler mHandler; private final Handler mHandler; @NonNull @NonNull private final BooleanSupplier mOnKeyPreIme; private final BooleanSupplier mOnKeyPreIme; @NonNull private final Runnable mSystemNavigationObserverCallbackRunnable; private final boolean mIsSystemCallback; OnBackInvokedCallbackWrapper( OnBackInvokedCallbackWrapper( @NonNull OnBackInvokedCallback callback, @NonNull WeakReference<OnBackInvokedCallback> callback, @NonNull BackTouchTracker touchTracker, @NonNull BackTouchTracker touchTracker, @NonNull BackProgressAnimator progressAnimator, @NonNull BackProgressAnimator progressAnimator, @NonNull Handler handler, @NonNull Handler handler, @NonNull BooleanSupplier onKeyPreIme, @NonNull BooleanSupplier onKeyPreIme @NonNull Runnable systemNavigationObserverCallbackRunnable, boolean isSystemCallback ) { ) { mCallback = new WeakReference<>(callback); mCallback = callback; mTouchTracker = touchTracker; mTouchTracker = touchTracker; mProgressAnimator = progressAnimator; mProgressAnimator = progressAnimator; mHandler = handler; mHandler = handler; mOnKeyPreIme = onKeyPreIme; mOnKeyPreIme = onKeyPreIme; mSystemNavigationObserverCallbackRunnable = systemNavigationObserverCallbackRunnable; mIsSystemCallback = isSystemCallback; } } @Override @Override Loading @@ -613,14 +665,14 @@ public class WindowOnBackInvokedDispatcher implements OnBackInvokedDispatcher { mTouchTracker.setGestureStartLocation( mTouchTracker.setGestureStartLocation( backEvent.getTouchX(), backEvent.getTouchY(), backEvent.getSwipeEdge()); backEvent.getTouchX(), backEvent.getTouchY(), backEvent.getSwipeEdge()); WindowOnBackInvokedDispatcher.this.onBackStarted(mCallback.get(), BackEvent.fromBackMotionEvent(backEvent), /* observerOnly */ false); if (callback != null) { if (callback != null) { callback.onBackStarted(BackEvent.fromBackMotionEvent(backEvent)); if (predictiveBackCallbackCancellationFix()) { if (predictiveBackCallbackCancellationFix()) { mProgressAnimator.onBackStarted(backEvent, callback::onBackProgressed, mProgressAnimator.onBackStarted(backEvent, callback::onBackProgressed, callback); callback); } else { } else { mProgressAnimator.onBackStarted(backEvent, callback::onBackProgressed); mProgressAnimator.onBackStarted(backEvent, callback::onBackProgressed); } } } } }); }); Loading Loading @@ -649,7 +701,9 @@ public class WindowOnBackInvokedDispatcher implements OnBackInvokedDispatcher { final OnBackAnimationCallback callback = getBackAnimationCallback(); final OnBackAnimationCallback callback = getBackAnimationCallback(); mTouchTracker.reset(); mTouchTracker.reset(); if (callback == null) return; if (callback == null) return; mProgressAnimator.onBackCancelled(callback::onBackCancelled); mProgressAnimator.onBackCancelled( () -> WindowOnBackInvokedDispatcher.this.onBackCancelled(callback) ); }); }); } } Loading @@ -670,10 +724,7 @@ public class WindowOnBackInvokedDispatcher implements OnBackInvokedDispatcher { return; return; } } mProgressAnimator.reset(); mProgressAnimator.reset(); if (mIsSystemCallback) { WindowOnBackInvokedDispatcher.this.onBackInvoked(callback); mSystemNavigationObserverCallbackRunnable.run(); } callback.onBackInvoked(); }); }); } } Loading Loading @@ -774,7 +825,8 @@ public class WindowOnBackInvokedDispatcher implements OnBackInvokedDispatcher { return false; return false; } } if (!WindowOnBackInvokedDispatcher.isOnBackInvokedCallbackEnabled(hostContext) if (!WindowOnBackInvokedDispatcher.isOnBackInvokedCallbackEnabled(hostContext) && !(callback instanceof CompatOnBackInvokedCallback)) { && !(callback instanceof CompatOnBackInvokedCallback || callback instanceof ObserverOnBackAnimationCallback)) { Log.w(TAG, Log.w(TAG, "OnBackInvokedCallback is not enabled for the application." "OnBackInvokedCallback is not enabled for the application." + "\nSet 'android:enableOnBackInvokedCallback=\"true\"' in the" + "\nSet 'android:enableOnBackInvokedCallback=\"true\"' in the" Loading
core/java/android/window/flags/windowing_frontend.aconfig +10 −0 Original line number Original line Diff line number Diff line Loading @@ -482,3 +482,13 @@ flag { purpose: PURPOSE_BUGFIX purpose: PURPOSE_BUGFIX } } } } flag { name: "predictive_back_stop_keycode_back_forwarding" namespace: "windowing_frontend" description: "No longer forward KEYCODE_BACK events when app has enableOnBackInvokedCallback=true" bug: "436871339" metadata { purpose: PURPOSE_BUGFIX } } No newline at end of file