diff options
| author | 2024-10-04 18:06:54 +0000 | |
|---|---|---|
| committer | 2024-10-04 18:06:54 +0000 | |
| commit | 8f44e78f6d4617d352f0e2e90aa12cd93d96097c (patch) | |
| tree | 06bb436c422217f9471a097e15c3061c8c954552 | |
| parent | 0506a46f26c99a6c812760ba66a323f4c3f5406c (diff) | |
| parent | ebaebbd466efc1cd18666d7319b7881d5391095d (diff) | |
Merge "Add new PRIORITY_SYSTEM_NAVIGATION_OBSERVER to OnBackInvokedDispatcher" into main
7 files changed, 281 insertions, 44 deletions
diff --git a/core/api/current.txt b/core/api/current.txt index b740ef36debe..53da33835d31 100644 --- a/core/api/current.txt +++ b/core/api/current.txt @@ -61658,6 +61658,7 @@ package android.window { method public void unregisterOnBackInvokedCallback(@NonNull android.window.OnBackInvokedCallback); field public static final int PRIORITY_DEFAULT = 0; // 0x0 field public static final int PRIORITY_OVERLAY = 1000000; // 0xf4240 + field @FlaggedApi("com.android.window.flags.predictive_back_priority_system_navigation_observer") public static final int PRIORITY_SYSTEM_NAVIGATION_OBSERVER = -2; // 0xfffffffe } public interface SplashScreen { diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java index 8fb17c72c462..0ca442d66e6f 100644 --- a/core/java/android/view/ViewRootImpl.java +++ b/core/java/android/view/ViewRootImpl.java @@ -7531,6 +7531,7 @@ public final class ViewRootImpl implements ViewParent, if (keyEvent.isCanceled()) { animationCallback.onBackCancelled(); } else { + dispatcher.tryInvokeSystemNavigationObserverCallback(); topCallback.onBackInvoked(); } break; @@ -7538,6 +7539,7 @@ public final class ViewRootImpl implements ViewParent, } else if (topCallback != null) { if (keyEvent.getAction() == KeyEvent.ACTION_UP) { if (!keyEvent.isCanceled()) { + dispatcher.tryInvokeSystemNavigationObserverCallback(); topCallback.onBackInvoked(); } else { Log.d(mTag, "Skip onBackInvoked(), reason: keyEvent.isCanceled=true"); diff --git a/core/java/android/window/OnBackInvokedDispatcher.java b/core/java/android/window/OnBackInvokedDispatcher.java index 0632a37a5dfb..02ed57d4be22 100644 --- a/core/java/android/window/OnBackInvokedDispatcher.java +++ b/core/java/android/window/OnBackInvokedDispatcher.java @@ -16,11 +16,14 @@ package android.window; +import android.annotation.FlaggedApi; import android.annotation.IntDef; import android.annotation.IntRange; import android.annotation.NonNull; import android.annotation.SuppressLint; +import com.android.window.flags.Flags; + import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -43,6 +46,7 @@ public interface OnBackInvokedDispatcher { @IntDef({ PRIORITY_DEFAULT, PRIORITY_OVERLAY, + PRIORITY_SYSTEM_NAVIGATION_OBSERVER, }) @Retention(RetentionPolicy.SOURCE) @interface Priority{} @@ -67,6 +71,20 @@ public interface OnBackInvokedDispatcher { int PRIORITY_SYSTEM = -1; /** + * Priority level of {@link OnBackInvokedCallback}s designed to observe system-level back + * handling. + * + * <p>Callbacks registered with this priority do not consume back events. They receive back + * events whenever the system handles a back navigation and have no impact on the normal back + * navigation flow. Useful for logging or analytics. + * + * <p>Only one callback with {@link #PRIORITY_SYSTEM_NAVIGATION_OBSERVER} can be registered at a + * time. + */ + @FlaggedApi(Flags.FLAG_PREDICTIVE_BACK_PRIORITY_SYSTEM_NAVIGATION_OBSERVER) + int PRIORITY_SYSTEM_NAVIGATION_OBSERVER = -2; + + /** * Registers a {@link OnBackInvokedCallback}. * * Within the same priority level, callbacks are invoked in the reverse order in which diff --git a/core/java/android/window/ProxyOnBackInvokedDispatcher.java b/core/java/android/window/ProxyOnBackInvokedDispatcher.java index 56c05b2766ec..dfc4a589f92e 100644 --- a/core/java/android/window/ProxyOnBackInvokedDispatcher.java +++ b/core/java/android/window/ProxyOnBackInvokedDispatcher.java @@ -16,6 +16,8 @@ package android.window; +import static com.android.window.flags.Flags.predictiveBackPrioritySystemNavigationObserver; + import android.annotation.NonNull; import android.annotation.Nullable; import android.content.Context; @@ -95,7 +97,7 @@ public class ProxyOnBackInvokedDispatcher implements OnBackInvokedDispatcher { synchronized (mLock) { mCallbacks.add(Pair.create(callback, priority)); if (mActualDispatcher != null) { - if (priority <= PRIORITY_SYSTEM) { + if (priority == PRIORITY_SYSTEM) { mActualDispatcher.registerSystemOnBackInvokedCallback(callback); } else { mActualDispatcher.registerOnBackInvokedCallback(priority, callback); @@ -123,10 +125,19 @@ public class ProxyOnBackInvokedDispatcher implements OnBackInvokedDispatcher { } for (Pair<OnBackInvokedCallback, Integer> callbackPair : mCallbacks) { int priority = callbackPair.second; - if (priority >= PRIORITY_DEFAULT) { - mActualDispatcher.registerOnBackInvokedCallback(priority, callbackPair.first); + if (predictiveBackPrioritySystemNavigationObserver()) { + if (priority >= PRIORITY_DEFAULT + || priority == PRIORITY_SYSTEM_NAVIGATION_OBSERVER) { + mActualDispatcher.registerOnBackInvokedCallback(priority, callbackPair.first); + } else { + mActualDispatcher.registerSystemOnBackInvokedCallback(callbackPair.first); + } } else { - mActualDispatcher.registerSystemOnBackInvokedCallback(callbackPair.first); + if (priority >= PRIORITY_DEFAULT) { + mActualDispatcher.registerOnBackInvokedCallback(priority, callbackPair.first); + } else { + mActualDispatcher.registerSystemOnBackInvokedCallback(callbackPair.first); + } } } mCallbacks.clear(); diff --git a/core/java/android/window/WindowOnBackInvokedDispatcher.java b/core/java/android/window/WindowOnBackInvokedDispatcher.java index bb89a2499838..51bc7d5c571b 100644 --- a/core/java/android/window/WindowOnBackInvokedDispatcher.java +++ b/core/java/android/window/WindowOnBackInvokedDispatcher.java @@ -16,6 +16,8 @@ package android.window; +import static com.android.window.flags.Flags.predictiveBackPrioritySystemNavigationObserver; + import android.annotation.NonNull; import android.annotation.Nullable; import android.app.Activity; @@ -103,6 +105,9 @@ public class WindowOnBackInvokedDispatcher implements OnBackInvokedDispatcher { public final TreeMap<Integer, ArrayList<OnBackInvokedCallback>> mOnBackInvokedCallbacks = new TreeMap<>(); + @VisibleForTesting + public OnBackInvokedCallback mSystemNavigationObserverCallback = null; + private Checker mChecker; private final Object mLock = new Object(); // The threshold for back swipe full progress. @@ -170,6 +175,20 @@ public class WindowOnBackInvokedDispatcher implements OnBackInvokedDispatcher { } } + private void registerSystemNavigationObserverCallback(@NonNull OnBackInvokedCallback callback) { + synchronized (mLock) { + // If callback has already been added as regular callback, remove it. + if (mAllCallbacks.containsKey(callback)) { + if (DEBUG) { + Log.i(TAG, "Callback already added. Removing and re-adding it as " + + "system-navigation-observer-callback."); + } + removeCallbackInternal(callback); + } + mSystemNavigationObserverCallback = callback; + } + } + /** * Register a callback bypassing platform checks. This is used to register compatibility * callbacks. @@ -181,6 +200,12 @@ public class WindowOnBackInvokedDispatcher implements OnBackInvokedDispatcher { mImeDispatcher.registerOnBackInvokedCallback(priority, callback); return; } + if (predictiveBackPrioritySystemNavigationObserver()) { + if (priority == PRIORITY_SYSTEM_NAVIGATION_OBSERVER) { + registerSystemNavigationObserverCallback(callback); + return; + } + } if (callback instanceof ImeOnBackInvokedDispatcher.ImeOnBackInvokedCallback) { if (callback instanceof ImeOnBackInvokedDispatcher.DefaultImeOnBackAnimationCallback && mImeBackAnimationController != null) { @@ -202,6 +227,13 @@ public class WindowOnBackInvokedDispatcher implements OnBackInvokedDispatcher { Integer prevPriority = mAllCallbacks.get(callback); mOnBackInvokedCallbacks.get(prevPriority).remove(callback); } + if (mSystemNavigationObserverCallback == callback) { + mSystemNavigationObserverCallback = null; + if (DEBUG) { + Log.i(TAG, "Callback already registered (as system-navigation-observer " + + "callback). Removing and re-adding it."); + } + } OnBackInvokedCallback previousTopCallback = getTopCallback(); callbacks.add(callback); @@ -221,6 +253,10 @@ public class WindowOnBackInvokedDispatcher implements OnBackInvokedDispatcher { mImeDispatcher.unregisterOnBackInvokedCallback(callback); return; } + if (mSystemNavigationObserverCallback == callback) { + mSystemNavigationObserverCallback = null; + return; + } if (callback instanceof ImeOnBackInvokedDispatcher.DefaultImeOnBackAnimationCallback) { callback = mImeBackAnimationController; } @@ -230,25 +266,29 @@ public class WindowOnBackInvokedDispatcher implements OnBackInvokedDispatcher { } return; } - OnBackInvokedCallback previousTopCallback = getTopCallback(); - Integer priority = mAllCallbacks.get(callback); - ArrayList<OnBackInvokedCallback> callbacks = mOnBackInvokedCallbacks.get(priority); - callbacks.remove(callback); - if (callbacks.isEmpty()) { - mOnBackInvokedCallbacks.remove(priority); - } - mAllCallbacks.remove(callback); - // Re-populate the top callback to WM if the removed callback was previously the top - // one. - if (previousTopCallback == callback) { - // We should call onBackCancelled() when an active callback is removed from - // dispatcher. - mProgressAnimator.removeOnBackCancelledFinishCallback(); - mProgressAnimator.removeOnBackInvokedFinishCallback(); - sendCancelledIfInProgress(callback); - mHandler.post(mProgressAnimator::reset); - setTopOnBackInvokedCallback(getTopCallback()); - } + removeCallbackInternal(callback); + } + } + + private void removeCallbackInternal(@NonNull OnBackInvokedCallback callback) { + OnBackInvokedCallback previousTopCallback = getTopCallback(); + Integer priority = mAllCallbacks.get(callback); + ArrayList<OnBackInvokedCallback> callbacks = mOnBackInvokedCallbacks.get(priority); + callbacks.remove(callback); + if (callbacks.isEmpty()) { + mOnBackInvokedCallbacks.remove(priority); + } + mAllCallbacks.remove(callback); + // Re-populate the top callback to WM if the removed callback was previously the top + // one. + if (previousTopCallback == callback) { + // We should call onBackCancelled() when an active callback is removed from + // dispatcher. + mProgressAnimator.removeOnBackCancelledFinishCallback(); + mProgressAnimator.removeOnBackInvokedFinishCallback(); + sendCancelledIfInProgress(callback); + mHandler.post(mProgressAnimator::reset); + setTopOnBackInvokedCallback(getTopCallback()); } } @@ -304,6 +344,7 @@ public class WindowOnBackInvokedDispatcher implements OnBackInvokedDispatcher { mHandler.post(mProgressAnimator::reset); mAllCallbacks.clear(); mOnBackInvokedCallbacks.clear(); + mSystemNavigationObserverCallback = null; } } @@ -315,6 +356,25 @@ public class WindowOnBackInvokedDispatcher implements OnBackInvokedDispatcher { } } + /** + * Tries to call {@link OnBackInvokedCallback#onBackInvoked} on the system navigation observer + * callback (if one is set and if the top-most regular callback has + * {@link OnBackInvokedDispatcher#PRIORITY_SYSTEM}) + */ + public void tryInvokeSystemNavigationObserverCallback() { + OnBackInvokedCallback topCallback = getTopCallback(); + Integer callbackPriority = mAllCallbacks.getOrDefault(topCallback, null); + if (callbackPriority != null && callbackPriority == PRIORITY_SYSTEM) { + invokeSystemNavigationObserverCallback(); + } + } + + private void invokeSystemNavigationObserverCallback() { + if (mSystemNavigationObserverCallback != null) { + mSystemNavigationObserverCallback.onBackInvoked(); + } + } + private void setTopOnBackInvokedCallback(@Nullable OnBackInvokedCallback callback) { if (mWindowSession == null || mWindow == null) { return; @@ -324,7 +384,9 @@ public class WindowOnBackInvokedDispatcher implements OnBackInvokedDispatcher { if (callback != null) { int priority = mAllCallbacks.get(callback); final IOnBackInvokedCallback iCallback = new OnBackInvokedCallbackWrapper(callback, - mTouchTracker, mProgressAnimator, mHandler, this::callOnKeyPreIme); + mTouchTracker, mProgressAnimator, mHandler, this::callOnKeyPreIme, + this::invokeSystemNavigationObserverCallback, + /*isSystemCallback*/ priority == PRIORITY_SYSTEM); callbackInfo = new OnBackInvokedCallbackInfo( iCallback, priority, @@ -416,18 +478,26 @@ public class WindowOnBackInvokedDispatcher implements OnBackInvokedDispatcher { private final Handler mHandler; @NonNull private final BooleanSupplier mOnKeyPreIme; + @NonNull + private final Runnable mSystemNavigationObserverCallbackRunnable; + private final boolean mIsSystemCallback; OnBackInvokedCallbackWrapper( @NonNull OnBackInvokedCallback callback, @NonNull BackTouchTracker touchTracker, @NonNull BackProgressAnimator progressAnimator, @NonNull Handler handler, - @NonNull BooleanSupplier onKeyPreIme) { + @NonNull BooleanSupplier onKeyPreIme, + @NonNull Runnable systemNavigationObserverCallbackRunnable, + boolean isSystemCallback + ) { mCallback = new WeakReference<>(callback); mTouchTracker = touchTracker; mProgressAnimator = progressAnimator; mHandler = handler; mOnKeyPreIme = onKeyPreIme; + mSystemNavigationObserverCallbackRunnable = systemNavigationObserverCallbackRunnable; + mIsSystemCallback = isSystemCallback; } @Override @@ -494,9 +564,17 @@ public class WindowOnBackInvokedDispatcher implements OnBackInvokedDispatcher { OnBackAnimationCallback animationCallback = getBackAnimationCallback(); if (animationCallback != null && !(callback instanceof ImeBackAnimationController)) { - mProgressAnimator.onBackInvoked(callback::onBackInvoked); + mProgressAnimator.onBackInvoked(() -> { + if (mIsSystemCallback) { + mSystemNavigationObserverCallbackRunnable.run(); + } + callback.onBackInvoked(); + }); } else { mProgressAnimator.reset(); + if (mIsSystemCallback) { + mSystemNavigationObserverCallbackRunnable.run(); + } callback.onBackInvoked(); } }); @@ -597,9 +675,18 @@ public class WindowOnBackInvokedDispatcher implements OnBackInvokedDispatcher { + " application manifest."); return false; } - if (priority < 0) { - throw new IllegalArgumentException("Application registered OnBackInvokedCallback " - + "cannot have negative priority. Priority: " + priority); + if (predictiveBackPrioritySystemNavigationObserver()) { + if (priority < 0 && priority != PRIORITY_SYSTEM_NAVIGATION_OBSERVER) { + throw new IllegalArgumentException("Application registered " + + "OnBackInvokedCallback cannot have negative priority. Priority: " + + priority); + } + } else { + if (priority < 0) { + throw new IllegalArgumentException("Application registered " + + "OnBackInvokedCallback cannot have negative priority. Priority: " + + priority); + } } Objects.requireNonNull(callback); return true; diff --git a/core/java/android/window/flags/windowing_frontend.aconfig b/core/java/android/window/flags/windowing_frontend.aconfig index 086063f3887c..c9b93c95e0c1 100644 --- a/core/java/android/window/flags/windowing_frontend.aconfig +++ b/core/java/android/window/flags/windowing_frontend.aconfig @@ -307,3 +307,11 @@ flag { bug: "364930619" is_fixed_read_only: true } + +flag { + name: "predictive_back_priority_system_navigation_observer" + namespace: "systemui" + description: "PRIORITY_SYSTEM_NAVIGATION_OBSERVER predictive back API extension" + is_fixed_read_only: true + bug: "362938401" +} diff --git a/core/tests/coretests/src/android/window/WindowOnBackInvokedDispatcherTest.java b/core/tests/coretests/src/android/window/WindowOnBackInvokedDispatcherTest.java index 0bda0fff6819..0a4c5e6eb91a 100644 --- a/core/tests/coretests/src/android/window/WindowOnBackInvokedDispatcherTest.java +++ b/core/tests/coretests/src/android/window/WindowOnBackInvokedDispatcherTest.java @@ -18,6 +18,9 @@ package android.window; import static android.window.OnBackInvokedDispatcher.PRIORITY_DEFAULT; import static android.window.OnBackInvokedDispatcher.PRIORITY_OVERLAY; +import static android.window.OnBackInvokedDispatcher.PRIORITY_SYSTEM_NAVIGATION_OBSERVER; + +import static com.android.window.flags.Flags.FLAG_PREDICTIVE_BACK_PRIORITY_SYSTEM_NAVIGATION_OBSERVER; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; @@ -39,6 +42,10 @@ import android.os.Handler; import android.os.Looper; import android.os.RemoteException; import android.platform.test.annotations.Presubmit; +import android.platform.test.annotations.RequiresFlagsDisabled; +import android.platform.test.annotations.RequiresFlagsEnabled; +import android.platform.test.flag.junit.CheckFlagsRule; +import android.platform.test.flag.junit.DeviceFlagsValueProvider; import android.view.IWindow; import android.view.IWindowSession; import android.view.ImeBackAnimationController; @@ -80,6 +87,8 @@ public class WindowOnBackInvokedDispatcherTest { @Rule public final MockitoRule mockito = MockitoJUnit.rule(); + @Rule + public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule(); @Mock private IWindowSession mWindowSession; @@ -145,7 +154,8 @@ public class WindowOnBackInvokedDispatcherTest { assertEquals("No setOnBackInvokedCallbackInfo", mCallbackInfoCalls, actual); } - private void assertCallbacksSize(int expectedDefault, int expectedOverlay) { + private void assertCallbacksSize(int expectedDefault, int expectedOverlay, + int expectedObserver) { ArrayList<OnBackInvokedCallback> callbacksDefault = mDispatcher .mOnBackInvokedCallbacks.get(PRIORITY_DEFAULT); int actualSizeDefault = callbacksDefault != null ? callbacksDefault.size() : 0; @@ -155,6 +165,10 @@ public class WindowOnBackInvokedDispatcherTest { .mOnBackInvokedCallbacks.get(PRIORITY_OVERLAY); int actualSizeOverlay = callbacksOverlay != null ? callbacksOverlay.size() : 0; assertEquals("mOnBackInvokedCallbacks OVERLAY size", expectedOverlay, actualSizeOverlay); + + int actualSizeObserver = mDispatcher.mSystemNavigationObserverCallback == null ? 0 : 1; + assertEquals("mOnBackInvokedCallbacks SYSTEM_NAVIGATION_OBSERVER size", expectedObserver, + actualSizeObserver); } private void assertTopCallback(OnBackInvokedCallback expectedCallback) { @@ -164,13 +178,13 @@ public class WindowOnBackInvokedDispatcherTest { @Test public void registerCallback_samePriority_sameCallback() throws RemoteException { mDispatcher.registerOnBackInvokedCallback(PRIORITY_DEFAULT, mCallback1); - assertCallbacksSize(/* default */ 1, /* overlay */ 0); + assertCallbacksSize(/* default */ 1, /* overlay */ 0, /* observer */ 0); assertSetCallbackInfo(); assertTopCallback(mCallback1); // The callback is removed and added again mDispatcher.registerOnBackInvokedCallback(PRIORITY_DEFAULT, mCallback1); - assertCallbacksSize(/* default */ 1, /* overlay */ 0); + assertCallbacksSize(/* default */ 1, /* overlay */ 0, /* observer */ 0); assertSetCallbackInfo(); assertTopCallback(mCallback1); @@ -182,13 +196,13 @@ public class WindowOnBackInvokedDispatcherTest { @Test public void registerCallback_samePriority_differentCallback() throws RemoteException { mDispatcher.registerOnBackInvokedCallback(PRIORITY_DEFAULT, mCallback1); - assertCallbacksSize(/* default */ 1, /* overlay */ 0); + assertCallbacksSize(/* default */ 1, /* overlay */ 0, /* observer */ 0); assertSetCallbackInfo(); assertTopCallback(mCallback1); // The new callback becomes the TopCallback mDispatcher.registerOnBackInvokedCallback(PRIORITY_DEFAULT, mCallback2); - assertCallbacksSize(/* default */ 2, /* overlay */ 0); + assertCallbacksSize(/* default */ 2, /* overlay */ 0, /* observer */ 0); assertSetCallbackInfo(); assertTopCallback(mCallback2); @@ -201,13 +215,13 @@ public class WindowOnBackInvokedDispatcherTest { @Test public void registerCallback_differentPriority_sameCallback() throws RemoteException { mDispatcher.registerOnBackInvokedCallback(PRIORITY_OVERLAY, mCallback1); - assertCallbacksSize(/* default */ 0, /* overlay */ 1); + assertCallbacksSize(/* default */ 0, /* overlay */ 1, /* observer */ 0); assertSetCallbackInfo(); assertTopCallback(mCallback1); // The callback is moved to the new priority list mDispatcher.registerOnBackInvokedCallback(PRIORITY_DEFAULT, mCallback1); - assertCallbacksSize(/* default */ 1, /* overlay */ 0); + assertCallbacksSize(/* default */ 1, /* overlay */ 0, /* observer */ 0); assertSetCallbackInfo(); assertTopCallback(mCallback1); @@ -220,13 +234,13 @@ public class WindowOnBackInvokedDispatcherTest { public void registerCallback_differentPriority_differentCallback() throws RemoteException { mDispatcher.registerOnBackInvokedCallback(PRIORITY_OVERLAY, mCallback1); assertSetCallbackInfo(); - assertCallbacksSize(/* default */ 0, /* overlay */ 1); + assertCallbacksSize(/* default */ 0, /* overlay */ 1, /* observer */ 0); assertTopCallback(mCallback1); // The callback with higher priority is still the TopCallback mDispatcher.registerOnBackInvokedCallback(PRIORITY_DEFAULT, mCallback2); assertNoSetCallbackInfo(); - assertCallbacksSize(/* default */ 1, /* overlay */ 1); + assertCallbacksSize(/* default */ 1, /* overlay */ 1, /* observer */ 0); assertTopCallback(mCallback1); waitForIdle(); @@ -238,22 +252,22 @@ public class WindowOnBackInvokedDispatcherTest { @Test public void registerCallback_sameInstanceAddedTwice() throws RemoteException { mDispatcher.registerOnBackInvokedCallback(PRIORITY_OVERLAY, mCallback1); - assertCallbacksSize(/* default */ 0, /* overlay */ 1); + assertCallbacksSize(/* default */ 0, /* overlay */ 1, /* observer */ 0); assertSetCallbackInfo(); assertTopCallback(mCallback1); mDispatcher.registerOnBackInvokedCallback(PRIORITY_DEFAULT, mCallback2); - assertCallbacksSize(/* default */ 1, /* overlay */ 1); + assertCallbacksSize(/* default */ 1, /* overlay */ 1, /* observer */ 0); assertNoSetCallbackInfo(); assertTopCallback(mCallback1); mDispatcher.registerOnBackInvokedCallback(PRIORITY_DEFAULT, mCallback1); - assertCallbacksSize(/* default */ 2, /* overlay */ 0); + assertCallbacksSize(/* default */ 2, /* overlay */ 0, /* observer */ 0); assertSetCallbackInfo(); assertTopCallback(mCallback1); mDispatcher.registerOnBackInvokedCallback(PRIORITY_OVERLAY, mCallback2); - assertCallbacksSize(/* default */ 1, /* overlay */ 1); + assertCallbacksSize(/* default */ 1, /* overlay */ 1, /* observer */ 0); assertSetCallbackInfo(); assertTopCallback(mCallback2); @@ -570,6 +584,102 @@ public class WindowOnBackInvokedDispatcherTest { assertFalse(mDispatcher.mProgressAnimator.isBackAnimationInProgress()); } + @Test(expected = IllegalArgumentException.class) + @RequiresFlagsDisabled(FLAG_PREDICTIVE_BACK_PRIORITY_SYSTEM_NAVIGATION_OBSERVER) + public void testNoUiCallback_registrationFailsWithoutFlaggedApiEnabled() { + mDispatcher.registerOnBackInvokedCallback(PRIORITY_SYSTEM_NAVIGATION_OBSERVER, mCallback2); + } + + @Test + @RequiresFlagsEnabled(FLAG_PREDICTIVE_BACK_PRIORITY_SYSTEM_NAVIGATION_OBSERVER) + public void testNoUiCallback_invokedWithSystemCallback() throws RemoteException { + mDispatcher.registerSystemOnBackInvokedCallback(mCallback1); + mDispatcher.registerOnBackInvokedCallback(PRIORITY_SYSTEM_NAVIGATION_OBSERVER, mCallback2); + + assertCallbacksSize(/* default */ 0, /* overlay */ 0, /* observer */ 1); + OnBackInvokedCallbackInfo callbackInfo = assertSetCallbackInfo(); + assertTopCallback(mCallback1); + + callbackInfo.getCallback().onBackStarted(mBackEvent); + waitForIdle(); + verify(mCallback1).onBackStarted(any()); + verify(mCallback2, never()).onBackStarted(any()); + + callbackInfo.getCallback().onBackProgressed(mBackEvent); + waitForIdle(); + verify(mCallback1).onBackProgressed(any()); + verify(mCallback2, never()).onBackProgressed(any()); + + callbackInfo.getCallback().onBackCancelled(); + waitForIdle(); + verify(mCallback1, timeout(1000)).onBackCancelled(); + verify(mCallback2, never()).onBackCancelled(); + + // start new gesture to test onBackInvoked case + callbackInfo.getCallback().onBackStarted(mBackEvent); + callbackInfo.getCallback().onBackInvoked(); + waitForIdle(); + verify(mCallback1, timeout(1000)).onBackInvoked(); + verify(mCallback2).onBackInvoked(); + } + + @Test + @RequiresFlagsEnabled(FLAG_PREDICTIVE_BACK_PRIORITY_SYSTEM_NAVIGATION_OBSERVER) + public void testNoUiCallback_notInvokedWithNonSystemCallback() throws RemoteException { + mDispatcher.registerOnBackInvokedCallback(PRIORITY_DEFAULT, mCallback1); + mDispatcher.registerOnBackInvokedCallback(PRIORITY_SYSTEM_NAVIGATION_OBSERVER, mCallback2); + + assertCallbacksSize(/* default */ 1, /* overlay */ 0, /* observer */ 1); + OnBackInvokedCallbackInfo callbackInfo = assertSetCallbackInfo(); + assertTopCallback(mCallback1); + + callbackInfo.getCallback().onBackStarted(mBackEvent); + waitForIdle(); + verify(mCallback1).onBackStarted(any()); + verify(mCallback2, never()).onBackStarted(any()); + + callbackInfo.getCallback().onBackProgressed(mBackEvent); + waitForIdle(); + verify(mCallback1).onBackProgressed(any()); + verify(mCallback2, never()).onBackProgressed(any()); + + callbackInfo.getCallback().onBackCancelled(); + waitForIdle(); + verify(mCallback1, timeout(1000)).onBackCancelled(); + verify(mCallback2, never()).onBackCancelled(); + + // start new gesture to test onBackInvoked case + callbackInfo.getCallback().onBackStarted(mBackEvent); + callbackInfo.getCallback().onBackInvoked(); + waitForIdle(); + verify(mCallback1, timeout(1000)).onBackInvoked(); + verify(mCallback2, never()).onBackInvoked(); + } + + @Test + @RequiresFlagsEnabled(FLAG_PREDICTIVE_BACK_PRIORITY_SYSTEM_NAVIGATION_OBSERVER) + public void testNoUiCallback_reregistrations() { + mDispatcher.registerOnBackInvokedCallback(PRIORITY_SYSTEM_NAVIGATION_OBSERVER, mCallback1); + assertCallbacksSize(/* default */ 0, /* overlay */ 0, /* observer */ 1); + assertEquals(mCallback1, mDispatcher.mSystemNavigationObserverCallback); + + // test reregistration of observer-callback as observer-callback + mDispatcher.registerOnBackInvokedCallback(PRIORITY_SYSTEM_NAVIGATION_OBSERVER, mCallback2); + assertCallbacksSize(/* default */ 0, /* overlay */ 0, /* observer */ 1); + assertEquals(mCallback2, mDispatcher.mSystemNavigationObserverCallback); + + // test reregistration of observer-callback as regular callback + mDispatcher.registerOnBackInvokedCallback(PRIORITY_DEFAULT, mCallback2); + assertCallbacksSize(/* default */ 1, /* overlay */ 0, /* observer */ 0); + + // test reregistration of regular callback as observer-callback + mDispatcher.registerOnBackInvokedCallback(PRIORITY_SYSTEM_NAVIGATION_OBSERVER, mCallback2); + assertCallbacksSize(/* default */ 0, /* overlay */ 0, /* observer */ 1); + + mDispatcher.unregisterOnBackInvokedCallback(mCallback2); + assertCallbacksSize(/* default */ 0, /* overlay */ 0, /* observer */ 0); + } + private BackMotionEvent backMotionEventFrom(float progress) { return new BackMotionEvent( /* touchX = */ 0, @@ -585,13 +695,13 @@ public class WindowOnBackInvokedDispatcherTest { private void verifyImeCallackRegistrations() throws RemoteException { // verify default callback is replaced with ImeBackAnimationController mDispatcher.registerOnBackInvokedCallbackUnchecked(mDefaultImeCallback, PRIORITY_DEFAULT); - assertCallbacksSize(/* default */ 1, /* overlay */ 0); + assertCallbacksSize(/* default */ 1, /* overlay */ 0, /* observer */ 0); assertSetCallbackInfo(); assertTopCallback(mImeBackAnimationController); // verify regular ime callback is successfully registered mDispatcher.registerOnBackInvokedCallbackUnchecked(mImeCallback, PRIORITY_DEFAULT); - assertCallbacksSize(/* default */ 2, /* overlay */ 0); + assertCallbacksSize(/* default */ 2, /* overlay */ 0, /* observer */ 0); assertSetCallbackInfo(); assertTopCallback(mImeCallback); } |