summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author Johannes Gallmann <gallmann@google.com> 2024-10-04 18:06:54 +0000
committer Android (Google) Code Review <android-gerrit@google.com> 2024-10-04 18:06:54 +0000
commit8f44e78f6d4617d352f0e2e90aa12cd93d96097c (patch)
tree06bb436c422217f9471a097e15c3061c8c954552
parent0506a46f26c99a6c812760ba66a323f4c3f5406c (diff)
parentebaebbd466efc1cd18666d7319b7881d5391095d (diff)
Merge "Add new PRIORITY_SYSTEM_NAVIGATION_OBSERVER to OnBackInvokedDispatcher" into main
-rw-r--r--core/api/current.txt1
-rw-r--r--core/java/android/view/ViewRootImpl.java2
-rw-r--r--core/java/android/window/OnBackInvokedDispatcher.java18
-rw-r--r--core/java/android/window/ProxyOnBackInvokedDispatcher.java19
-rw-r--r--core/java/android/window/WindowOnBackInvokedDispatcher.java137
-rw-r--r--core/java/android/window/flags/windowing_frontend.aconfig8
-rw-r--r--core/tests/coretests/src/android/window/WindowOnBackInvokedDispatcherTest.java140
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);
}