summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author Schneider Victor-Tulias <victortulias@google.com> 2025-03-27 16:23:45 -0400
committer Android Build Coastguard Worker <android-build-coastguard-worker@google.com> 2025-03-30 15:22:31 -0700
commit7bccee01cb9c4708744fde8240ad1995807b1d75 (patch)
treebb3991d7ab3b9ac0f1305d9ecf60075a371d6fdd
parentc2e92447b5f5af702ccae758f72ce25d5dce858f (diff)
Forcefully finish recents animations when launcher is detroyed
If launcher is destroyed while the recents animation start is pending, then the taskanimationmanager and absswipeuphandler states are not properly cleaned up. Adding a new cleanup flow to handle this case. Flag: EXEMPT bug fix Fixes: 405642423 Test: adb shell cmd uimode night yes/no while TaskAnimationManager.mRecentsAnimationStartPending == true (cherry picked from https://googleplex-android-review.googlesource.com/q/commit:8d72503263e8108aa78a527dde1487eb60c867f6) Merged-In: I7bf1fc4fc07859f92d7aec6cd78deafa1214dd17 Change-Id: I7bf1fc4fc07859f92d7aec6cd78deafa1214dd17
-rw-r--r--quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java19
-rw-r--r--quickstep/src/com/android/quickstep/TaskAnimationManager.java42
-rw-r--r--quickstep/src_protolog/com/android/quickstep/util/ActiveGestureProtoLogProxy.java9
-rw-r--r--quickstep/tests/multivalentTests/src/com/android/quickstep/TaskAnimationManagerTest.java61
4 files changed, 116 insertions, 15 deletions
diff --git a/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java b/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java
index 48630b11d3..f803709a46 100644
--- a/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java
+++ b/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java
@@ -221,15 +221,6 @@ public abstract class AbsSwipeUpHandler<
// The previous task view type before the user quick switches between tasks
private TaskViewType mPreviousTaskViewType;
- private final Runnable mLauncherOnDestroyCallback = () -> {
- ActiveGestureProtoLogProxy.logLauncherDestroyed();
- mRecentsView.removeOnScrollChangedListener(mOnRecentsScrollListener);
- mRecentsView = null;
- mContainer = null;
- mStateCallback.clearState(STATE_LAUNCHER_PRESENT);
- mRecentsAnimationStartCallbacks.clear();
- };
-
private static int FLAG_COUNT = 0;
private static int getNextStateFlag(String name) {
if (DEBUG_STATES) {
@@ -356,6 +347,16 @@ public abstract class AbsSwipeUpHandler<
private final SwipePipToHomeAnimator[] mSwipePipToHomeAnimators =
new SwipePipToHomeAnimator[2];
+ private final Runnable mLauncherOnDestroyCallback = () -> {
+ ActiveGestureProtoLogProxy.logLauncherDestroyed();
+ mRecentsView.removeOnScrollChangedListener(mOnRecentsScrollListener);
+ mRecentsView = null;
+ mContainer = null;
+ mStateCallback.clearState(STATE_LAUNCHER_PRESENT);
+ mRecentsAnimationStartCallbacks.clear();
+ mTaskAnimationManager.onLauncherDestroyed();
+ };
+
// Interpolate RecentsView scale from start of quick switch scroll until this scroll threshold
private final float mQuickSwitchScaleScrollThreshold;
diff --git a/quickstep/src/com/android/quickstep/TaskAnimationManager.java b/quickstep/src/com/android/quickstep/TaskAnimationManager.java
index cf0a3d570f..e552cd978b 100644
--- a/quickstep/src/com/android/quickstep/TaskAnimationManager.java
+++ b/quickstep/src/com/android/quickstep/TaskAnimationManager.java
@@ -425,22 +425,29 @@ public class TaskAnimationManager implements RecentsAnimationCallbacks.RecentsAn
public void finishRunningRecentsAnimation(boolean toHome) {
finishRunningRecentsAnimation(toHome, false /* forceFinish */, null /* forceFinishCb */);
}
+ public void finishRunningRecentsAnimation(
+ boolean toHome, boolean forceFinish, Runnable forceFinishCb) {
+ finishRunningRecentsAnimation(toHome, forceFinish, forceFinishCb, mController);
+ }
/**
* Finishes the running recents animation.
* @param forceFinish will synchronously finish the controller
*/
- public void finishRunningRecentsAnimation(boolean toHome, boolean forceFinish,
- Runnable forceFinishCb) {
- if (mController != null) {
+ public void finishRunningRecentsAnimation(
+ boolean toHome,
+ boolean forceFinish,
+ @Nullable Runnable forceFinishCb,
+ @Nullable RecentsAnimationController controller) {
+ if (controller != null) {
ActiveGestureProtoLogProxy.logFinishRunningRecentsAnimation(toHome);
if (forceFinish) {
- mController.finishController(toHome, forceFinishCb, false /* sendUserLeaveHint */,
+ controller.finishController(toHome, forceFinishCb, false /* sendUserLeaveHint */,
true /* forceFinish */);
} else {
Utilities.postAsyncCallback(MAIN_EXECUTOR.getHandler(), toHome
- ? mController::finishAnimationToHome
- : mController::finishAnimationToApp);
+ ? controller::finishAnimationToHome
+ : controller::finishAnimationToApp);
}
}
}
@@ -465,6 +472,29 @@ public class TaskAnimationManager implements RecentsAnimationCallbacks.RecentsAn
return mController != null;
}
+ void onLauncherDestroyed() {
+ if (!mRecentsAnimationStartPending) {
+ return;
+ }
+ if (mCallbacks == null) {
+ return;
+ }
+ ActiveGestureProtoLogProxy.logQueuingForceFinishRecentsAnimation();
+ mCallbacks.addListener(new RecentsAnimationCallbacks.RecentsAnimationListener() {
+ @Override
+ public void onRecentsAnimationStart(
+ RecentsAnimationController controller,
+ RecentsAnimationTargets targets,
+ @Nullable TransitionInfo transitionInfo) {
+ finishRunningRecentsAnimation(
+ /* toHome= */ false,
+ /* forceFinish= */ true,
+ /* forceFinishCb= */ null,
+ controller);
+ }
+ });
+ }
+
/**
* Cleans up the recents animation entirely.
*/
diff --git a/quickstep/src_protolog/com/android/quickstep/util/ActiveGestureProtoLogProxy.java b/quickstep/src_protolog/com/android/quickstep/util/ActiveGestureProtoLogProxy.java
index 2532fcf8cf..1c8656c54b 100644
--- a/quickstep/src_protolog/com/android/quickstep/util/ActiveGestureProtoLogProxy.java
+++ b/quickstep/src_protolog/com/android/quickstep/util/ActiveGestureProtoLogProxy.java
@@ -570,4 +570,13 @@ public class ActiveGestureProtoLogProxy {
"OtherActivityInputConsumer.startTouchTrackingForWindowAnimation: "
+ "interactionHandler=%s", interactionHandler);
}
+
+ public static void logQueuingForceFinishRecentsAnimation() {
+ ActiveGestureLog.INSTANCE.addLog("Launcher destroyed while mRecentsAnimationStartPending =="
+ + " true, queuing a callback to clean the pending animation up on start",
+ /* gestureEvent= */ ON_START_RECENTS_ANIMATION);
+ if (!enableActiveGestureProtoLog() || !isProtoLogInitialized()) return;
+ ProtoLog.d(ACTIVE_GESTURE_LOG, "Launcher destroyed while mRecentsAnimationStartPending =="
+ + " true, queuing a callback to clean the pending animation up on start");
+ }
}
diff --git a/quickstep/tests/multivalentTests/src/com/android/quickstep/TaskAnimationManagerTest.java b/quickstep/tests/multivalentTests/src/com/android/quickstep/TaskAnimationManagerTest.java
index fd88a5cb27..75b59d734a 100644
--- a/quickstep/tests/multivalentTests/src/com/android/quickstep/TaskAnimationManagerTest.java
+++ b/quickstep/tests/multivalentTests/src/com/android/quickstep/TaskAnimationManagerTest.java
@@ -19,19 +19,30 @@ package com.android.quickstep;
import static org.junit.Assert.assertEquals;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+import android.app.ActivityManager;
import android.app.ActivityOptions;
import android.content.Context;
import android.content.Intent;
+import android.content.res.Configuration;
+import android.graphics.Rect;
+import android.os.Bundle;
import android.view.Display;
+import android.view.RemoteAnimationTarget;
+import android.view.SurfaceControl;
+import android.window.TransitionInfo;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
import androidx.test.platform.app.InstrumentationRegistry;
+import com.android.systemui.shared.system.RecentsAnimationControllerCompat;
+
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -80,6 +91,56 @@ public class TaskAnimationManagerTest {
optionsCaptor.getValue().getPendingIntentBackgroundActivityStartMode());
}
+ @Test
+ public void testLauncherDestroyed_whileRecentsAnimationStartPending_finishesAnimation() {
+ final GestureState gestureState = mock(GestureState.class);
+ final ArgumentCaptor<RecentsAnimationCallbacks> listenerCaptor =
+ ArgumentCaptor.forClass(RecentsAnimationCallbacks.class);
+ final RecentsAnimationControllerCompat controllerCompat =
+ mock(RecentsAnimationControllerCompat.class);
+ final RemoteAnimationTarget remoteAnimationTarget = new RemoteAnimationTarget(
+ /* taskId= */ 0,
+ /* mode= */ RemoteAnimationTarget.MODE_CLOSING,
+ /* leash= */ new SurfaceControl(),
+ /* isTranslucent= */ false,
+ /* clipRect= */ null,
+ /* contentInsets= */ null,
+ /* prefixOrderIndex= */ 0,
+ /* position= */ null,
+ /* localBounds= */ null,
+ /* screenSpaceBounds= */ null,
+ new Configuration().windowConfiguration,
+ /* isNotInRecents= */ false,
+ /* startLeash= */ null,
+ /* startBounds= */ null,
+ /* taskInfo= */ new ActivityManager.RunningTaskInfo(),
+ /* allowEnterPip= */ false);
+
+ doReturn(mock(LauncherActivityInterface.class)).when(gestureState).getContainerInterface();
+ when(mSystemUiProxy
+ .startRecentsActivity(any(), any(), listenerCaptor.capture(), anyBoolean()))
+ .thenReturn(true);
+ when(gestureState.getRunningTaskIds(anyBoolean())).thenReturn(new int[0]);
+
+ runOnMainSync(() -> {
+ mTaskAnimationManager.startRecentsAnimation(
+ gestureState,
+ new Intent(),
+ mock(RecentsAnimationCallbacks.RecentsAnimationListener.class));
+ mTaskAnimationManager.onLauncherDestroyed();
+ listenerCaptor.getValue().onAnimationStart(
+ controllerCompat,
+ new RemoteAnimationTarget[] { remoteAnimationTarget },
+ new RemoteAnimationTarget[] { remoteAnimationTarget },
+ new Rect(),
+ new Rect(),
+ new Bundle(),
+ new TransitionInfo(0, 0));
+ });
+ runOnMainSync(() -> verify(controllerCompat)
+ .finish(/* toHome= */ eq(false), anyBoolean(), any()));
+ }
+
protected static void runOnMainSync(Runnable runnable) {
InstrumentationRegistry.getInstrumentation().runOnMainSync(runnable);
}