diff options
author | 2025-03-27 16:23:45 -0400 | |
---|---|---|
committer | 2025-03-30 15:22:31 -0700 | |
commit | 7bccee01cb9c4708744fde8240ad1995807b1d75 (patch) | |
tree | bb3991d7ab3b9ac0f1305d9ecf60075a371d6fdd | |
parent | c2e92447b5f5af702ccae758f72ce25d5dce858f (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
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); } |