Add recents animation start timeout handling to TaskAnimationManager

In cases where a recents aniamtion transition was successfully requested but failed to start, we should clean up the gesture state to recover.

Flag: EXEMPT bug fix
Fixes: 415930274
Test: TaskAnimationManagerTest.testRecentsAnimationStartTimeout_cleansUpRecentsAnimation
Change-Id: If1a24d6c7baac7338c49056531bc01665f082ddf
This commit is contained in:
Schneider Victor-Tulias
2025-05-20 11:29:39 -04:00
parent 57de0ebf21
commit 124b010ea9
4 changed files with 86 additions and 24 deletions

View File

@@ -16,7 +16,11 @@
package com.android.quickstep;
import static com.android.quickstep.TaskAnimationManager.RECENTS_ANIMATION_START_TIMEOUT_MS;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyInt;
@@ -34,6 +38,7 @@ import android.content.Intent;
import android.content.res.Configuration;
import android.graphics.Rect;
import android.os.Bundle;
import android.os.SystemClock;
import android.platform.test.annotations.DisableFlags;
import android.platform.test.annotations.EnableFlags;
import android.platform.test.flag.junit.SetFlagsRule;
@@ -111,7 +116,7 @@ public class TaskAnimationManagerTest {
@Test
public void testLauncherDestroyed_whileRecentsAnimationStartPending_finishesAnimation() {
final GestureState gestureState = mock(GestureState.class);
final GestureState gestureState = buildMockGestureState();
final ArgumentCaptor<RecentsAnimationCallbacks> listenerCaptor =
ArgumentCaptor.forClass(RecentsAnimationCallbacks.class);
final RecentsAnimationControllerCompat controllerCompat =
@@ -134,12 +139,10 @@ public class TaskAnimationManagerTest {
/* taskInfo= */ new ActivityManager.RunningTaskInfo(),
/* allowEnterPip= */ false);
doReturn(mock(LauncherActivityInterface.class)).when(gestureState).getContainerInterface();
when(mSystemUiProxy
.startRecentsActivity(any(), any(), listenerCaptor.capture(), anyBoolean(), any(),
anyInt()))
.thenReturn(true);
when(gestureState.getRunningTaskIds(anyBoolean())).thenReturn(new int[0]);
runOnMainSync(() -> {
mTaskAnimationManager.startRecentsAnimation(
@@ -166,10 +169,45 @@ public class TaskAnimationManagerTest {
.finish(/* toHome= */ eq(false), anyBoolean(), any()));
}
@Test
public void testRecentsAnimationStartTimeout_cleansUpRecentsAnimation() {
final GestureState gestureState = buildMockGestureState();
when(mSystemUiProxy
.startRecentsActivity(any(), any(), any(), anyBoolean(), any(), anyInt()))
.thenReturn(true);
runOnMainSync(() -> {
assertNull("Recents animation was started prematurely:",
mTaskAnimationManager.getCurrentCallbacks());
mTaskAnimationManager.startRecentsAnimation(
gestureState,
new Intent(),
mock(RecentsAnimationCallbacks.RecentsAnimationListener.class));
assertNotNull("TaskAnimationManager was cleaned up prematurely:",
mTaskAnimationManager.getCurrentCallbacks());
});
SystemClock.sleep(RECENTS_ANIMATION_START_TIMEOUT_MS);
runOnMainSync(() -> assertNull("TaskAnimationManager was not cleaned up after the timeout:",
mTaskAnimationManager.getCurrentCallbacks()));
}
protected static void runOnMainSync(Runnable runnable) {
InstrumentationRegistry.getInstrumentation().runOnMainSync(runnable);
}
private GestureState buildMockGestureState() {
final GestureState gestureState = mock(GestureState.class);
doReturn(mock(LauncherActivityInterface.class)).when(gestureState).getContainerInterface();
when(gestureState.getRunningTaskIds(anyBoolean())).thenReturn(new int[0]);
return gestureState;
}
/**
* Invokes maybeStartHomeAction on the given TaskAnimationManager and verifies whether the
* provided Runnable was invoked, based on the expectedResult.