Call onBackCancelled() when an active callback is removed from the WindowOnBackInvokedDispatcher

When an OnBackInvokedCallback is unregistered from a WindowOnBackInvokedDispatcher, it is important to check if the callback is the current top one and if the back animation is in progress. If it is, we should call onBackCancelled() on the callback as a final step.

Test: atest WindowOnBackInvokedDispatcherTest
Bug: 276816667
Change-Id: Icaf98899c474c4e8d89d4a9c77acc309d37e9582
This commit is contained in:
omarmt
2023-05-09 16:01:53 +00:00
committed by Omar Miatello
parent 3e1928ca21
commit 11fccc4afb
3 changed files with 41 additions and 5 deletions

View File

@@ -43,7 +43,7 @@ public class BackProgressAnimator {
private ProgressCallback mCallback;
private float mProgress = 0;
private BackMotionEvent mLastBackEvent;
private boolean mStarted = false;
private boolean mBackAnimationInProgress = false;
private void setProgress(float progress) {
mProgress = progress;
@@ -87,7 +87,7 @@ public class BackProgressAnimator {
* @param event the {@link BackMotionEvent} containing the latest target progress.
*/
public void onBackProgressed(BackMotionEvent event) {
if (!mStarted) {
if (!mBackAnimationInProgress) {
return;
}
mLastBackEvent = event;
@@ -108,7 +108,7 @@ public class BackProgressAnimator {
reset();
mLastBackEvent = event;
mCallback = callback;
mStarted = true;
mBackAnimationInProgress = true;
}
/**
@@ -122,7 +122,7 @@ public class BackProgressAnimator {
// Should never happen.
mSpring.cancel();
}
mStarted = false;
mBackAnimationInProgress = false;
mLastBackEvent = null;
mCallback = null;
mProgress = 0;
@@ -149,8 +149,13 @@ public class BackProgressAnimator {
mSpring.animateToFinalPosition(0);
}
/** Returns true if the back animation is in progress. */
boolean isBackAnimationInProgress() {
return mBackAnimationInProgress;
}
private void updateProgressValue(float progress) {
if (mLastBackEvent == null || mCallback == null || !mStarted) {
if (mLastBackEvent == null || mCallback == null || !mBackAnimationInProgress) {
return;
}
mCallback.onProgressUpdate(

View File

@@ -158,6 +158,16 @@ public class WindowOnBackInvokedDispatcher implements OnBackInvokedDispatcher {
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.
if (mProgressAnimator.isBackAnimationInProgress()
&& callback instanceof OnBackAnimationCallback) {
// The ProgressAnimator will handle the new topCallback, so we don't want to call
// onBackCancelled() on it. We call immediately the callback instead.
OnBackAnimationCallback animatedCallback = (OnBackAnimationCallback) callback;
animatedCallback.onBackCancelled();
Log.d(TAG, "The callback was removed while a back animation was in progress, "
+ "an onBackCancelled() was dispatched.");
}
setTopOnBackInvokedCallback(getTopCallback());
}
}

View File

@@ -173,4 +173,25 @@ public class WindowOnBackInvokedDispatcherTest {
waitForIdle();
verify(mCallback2).onBackStarted(any(BackEvent.class));
}
@Test
public void onUnregisterWhileBackInProgress_callOnBackCancelled() throws RemoteException {
ArgumentCaptor<OnBackInvokedCallbackInfo> captor =
ArgumentCaptor.forClass(OnBackInvokedCallbackInfo.class);
mDispatcher.registerOnBackInvokedCallback(
OnBackInvokedDispatcher.PRIORITY_DEFAULT, mCallback1);
verify(mWindowSession).setOnBackInvokedCallbackInfo(
Mockito.eq(mWindow),
captor.capture());
IOnBackInvokedCallback iOnBackInvokedCallback = captor.getValue().getCallback();
iOnBackInvokedCallback.onBackStarted(mBackEvent);
waitForIdle();
verify(mCallback1).onBackStarted(any(BackEvent.class));
mDispatcher.unregisterOnBackInvokedCallback(mCallback1);
verify(mCallback1).onBackCancelled();
verifyNoMoreInteractions(mCallback1);
}
}