Merge "Cancel the back animation if the topCallback is removed or the WindowOnBackInvokedDispatcher is detached from the window." into udc-dev am: b3805c735f

Original change: https://googleplex-android-review.googlesource.com/c/platform/frameworks/base/+/23516257

Change-Id: I93084886716de78b06ee9c4ac46b134de1bb27d2
Signed-off-by: Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
This commit is contained in:
Omar Miatello
2023-06-02 17:05:56 +00:00
committed by Automerger Merge Worker
2 changed files with 70 additions and 9 deletions

View File

@@ -163,16 +163,22 @@ public class WindowOnBackInvokedDispatcher implements OnBackInvokedDispatcher {
// Re-populate the top callback to WM if the removed callback was previously the top one. // Re-populate the top callback to WM if the removed callback was previously the top one.
if (previousTopCallback == callback) { if (previousTopCallback == callback) {
// We should call onBackCancelled() when an active callback is removed from dispatcher. // We should call onBackCancelled() when an active callback is removed from dispatcher.
if (mProgressAnimator.isBackAnimationInProgress() sendCancelledIfInProgress(callback);
&& callback instanceof OnBackAnimationCallback) { setTopOnBackInvokedCallback(getTopCallback());
// The ProgressAnimator will handle the new topCallback, so we don't want to call }
// onBackCancelled() on it. We call immediately the callback instead. }
private void sendCancelledIfInProgress(@NonNull OnBackInvokedCallback callback) {
boolean isInProgress = mProgressAnimator.isBackAnimationInProgress();
if (isInProgress && callback instanceof OnBackAnimationCallback) {
OnBackAnimationCallback animatedCallback = (OnBackAnimationCallback) callback; OnBackAnimationCallback animatedCallback = (OnBackAnimationCallback) callback;
animatedCallback.onBackCancelled(); animatedCallback.onBackCancelled();
Log.d(TAG, "The callback was removed while a back animation was in progress, " if (DEBUG) {
+ "an onBackCancelled() was dispatched."); Log.d(TAG, "sendCancelIfRunning: callback canceled");
} }
setTopOnBackInvokedCallback(getTopCallback()); } else {
Log.w(TAG, "sendCancelIfRunning: isInProgress=" + isInProgress
+ "callback=" + callback);
} }
} }
@@ -188,9 +194,20 @@ public class WindowOnBackInvokedDispatcher implements OnBackInvokedDispatcher {
mImeDispatcher = null; mImeDispatcher = null;
} }
if (!mAllCallbacks.isEmpty()) { if (!mAllCallbacks.isEmpty()) {
OnBackInvokedCallback topCallback = getTopCallback();
if (topCallback != null) {
sendCancelledIfInProgress(topCallback);
} else {
// Should not be possible
Log.e(TAG, "There is no topCallback, even if mAllCallbacks is not empty");
}
// Clear binder references in WM. // Clear binder references in WM.
setTopOnBackInvokedCallback(null); setTopOnBackInvokedCallback(null);
} }
// We should also stop running animations since all callbacks have been removed.
// note: mSpring.skipToEnd(), in ProgressAnimator.reset(), requires the main handler.
Handler.getMain().post(mProgressAnimator::reset);
mAllCallbacks.clear(); mAllCallbacks.clear();
mOnBackInvokedCallbacks.clear(); mOnBackInvokedCallbacks.clear();
} }
@@ -342,12 +359,17 @@ public class WindowOnBackInvokedDispatcher implements OnBackInvokedDispatcher {
@Override @Override
public void onBackInvoked() throws RemoteException { public void onBackInvoked() throws RemoteException {
Handler.getMain().post(() -> { Handler.getMain().post(() -> {
boolean isInProgress = mProgressAnimator.isBackAnimationInProgress();
mProgressAnimator.reset(); mProgressAnimator.reset();
final OnBackInvokedCallback callback = mCallbackRef.get(); final OnBackInvokedCallback callback = mCallbackRef.get();
if (callback == null) { if (callback == null) {
Log.d(TAG, "Trying to call onBackInvoked() on a null callback reference."); Log.d(TAG, "Trying to call onBackInvoked() on a null callback reference.");
return; return;
} }
if (callback instanceof OnBackAnimationCallback && !isInProgress) {
Log.w(TAG, "ProgressAnimator was not in progress, skip onBackInvoked().");
return;
}
callback.onBackInvoked(); callback.onBackInvoked();
}); });
} }

View File

@@ -25,6 +25,7 @@ import static org.mockito.ArgumentMatchers.isNull;
import static org.mockito.Mockito.atLeast; import static org.mockito.Mockito.atLeast;
import static org.mockito.Mockito.atMost; import static org.mockito.Mockito.atMost;
import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoMoreInteractions; import static org.mockito.Mockito.verifyNoMoreInteractions;
import static org.mockito.Mockito.verifyZeroInteractions; import static org.mockito.Mockito.verifyZeroInteractions;
@@ -340,4 +341,42 @@ public class WindowOnBackInvokedDispatcherTest {
verify(mCallback1).onBackCancelled(); verify(mCallback1).onBackCancelled();
verify(mWindowSession).setOnBackInvokedCallbackInfo(Mockito.eq(mWindow), isNull()); verify(mWindowSession).setOnBackInvokedCallbackInfo(Mockito.eq(mWindow), isNull());
} }
@Test
public void onBackInvoked_calledAfterOnBackStarted() throws RemoteException {
mDispatcher.registerOnBackInvokedCallback(PRIORITY_DEFAULT, mCallback1);
OnBackInvokedCallbackInfo callbackInfo = assertSetCallbackInfo();
callbackInfo.getCallback().onBackStarted(mBackEvent);
waitForIdle();
verify(mCallback1).onBackStarted(any(BackEvent.class));
callbackInfo.getCallback().onBackInvoked();
waitForIdle();
verify(mCallback1).onBackInvoked();
verify(mCallback1, never()).onBackCancelled();
}
@Test
public void onDetachFromWindow_cancelCallbackAndIgnoreOnBackInvoked() throws RemoteException {
mDispatcher.registerOnBackInvokedCallback(PRIORITY_DEFAULT, mCallback1);
OnBackInvokedCallbackInfo callbackInfo = assertSetCallbackInfo();
callbackInfo.getCallback().onBackStarted(mBackEvent);
waitForIdle();
verify(mCallback1).onBackStarted(any(BackEvent.class));
// This should trigger mCallback1.onBackCancelled()
mDispatcher.detachFromWindow();
// This should be ignored by mCallback1
callbackInfo.getCallback().onBackInvoked();
waitForIdle();
verify(mCallback1, never()).onBackInvoked();
verify(mCallback1).onBackCancelled();
}
} }