Make sure animation properties are applied correctly for non-gestural transitions

- In TaskViewSimulator.addAppToCarouselAnim, make sure carouselScale stays at 1f when it's atomicEvent (3 button, KEYCODE_APP_SWITCH, keybaord etc,), as we transition direclty from fullscreen to Overview without involving the carousel
- Made AnimatorSet pass to onPrepareGestureEndAnimation @NonNull, to avoid diverged code path handling null AnimatorSet.
  - For 3-button/Keyboard interactions, pass the gesture AnimatorSet with a boolean that indicates atomic event; some float peroperties will immediately settle while it's atomic: GRID_PROGRESS, TASK_THUMBNAIL_SPLASH_ALPHA
  - For home gesture, pass a new AnimatorSet that will be played immediately with 0 duration
- Converted onPrepareGestureEndAnimation to Kotlin, and removed the code path when animatorSet is null
  - updateGridProperties is now called based on displayOverviewTasksAsGrid, which is effectively the same check as endTarget == GestureState.GestureEndTarget.RECENTS;
  - RECENTS_GRID_PROGRESS is no longer caleld per remoteTargetHandle, which duplicates the animaton for nothing, as it's a per recents rather than per task property

Fix: 405384582
Fix: 407059929
Flag: EXEMPT bug fix
Test: Swipe up/KEYCODE_APP_SWITCH/Recents button with default and 3p Launcher
Change-Id: I56ba9137219f6d7cb982d8e5a3534f09ba3d189d
This commit is contained in:
Alex Chau
2025-03-28 16:44:46 +00:00
parent fbfb7bf8b3
commit 646422456d
7 changed files with 125 additions and 101 deletions
@@ -1767,8 +1767,10 @@ public abstract class AbsSwipeUpHandler<
mLauncherTransitionController = null;
if (mRecentsView != null) {
mRecentsView.onPrepareGestureEndAnimation(null, mGestureState.getEndTarget(),
mRemoteTargetHandles);
AnimatorSet animatorSet = new AnimatorSet();
mRecentsView.onPrepareGestureEndAnimation(animatorSet, mGestureState.getEndTarget(),
mRemoteTargetHandles, /* isHandlingAtomicEvent= */ true);
animatorSet.setDuration(0).start();
}
} else {
AnimatorSet animatorSet = new AnimatorSet();
@@ -1810,9 +1812,10 @@ public abstract class AbsSwipeUpHandler<
animatorSet.play(windowAnim);
if (mRecentsView != null) {
mRecentsView.onPrepareGestureEndAnimation(
mGestureState.isHandlingAtomicEvent() ? null : animatorSet,
animatorSet,
mGestureState.getEndTarget(),
mRemoteTargetHandles);
mRemoteTargetHandles,
mGestureState.isHandlingAtomicEvent());
}
animatorSet.setDuration(duration).setInterpolator(interpolator);
animatorSet.start();
@@ -120,7 +120,8 @@ public abstract class SwipeUpAnimationLogic implements
PendingAnimation pendingAnimation = new PendingAnimation(mTransitionDragLength * 2);
TaskViewSimulator taskViewSimulator = remoteHandle.getTaskViewSimulator();
taskViewSimulator.setDp(dp);
taskViewSimulator.addAppToCarouselAnim(pendingAnimation, LINEAR);
taskViewSimulator.addAppToCarouselAnim(pendingAnimation, LINEAR,
mGestureState.isHandlingAtomicEvent());
AnimatorPlaybackController playbackController =
pendingAnimation.createPlaybackController();
@@ -23,7 +23,6 @@ import static com.android.quickstep.fallback.RecentsState.DEFAULT;
import static com.android.quickstep.fallback.RecentsState.MODAL_TASK;
import static com.android.quickstep.fallback.RecentsState.OVERVIEW_SPLIT_SELECT;
import android.animation.Animator;
import android.animation.AnimatorSet;
import android.content.Context;
import android.util.AttributeSet;
@@ -134,9 +133,10 @@ public class FallbackRecentsView<CONTAINER_TYPE extends Context & RecentsViewCon
*/
@Override
public void onPrepareGestureEndAnimation(
@Nullable AnimatorSet animatorSet, GestureState.GestureEndTarget endTarget,
RemoteTargetHandle[] remoteTargetHandles) {
super.onPrepareGestureEndAnimation(animatorSet, endTarget, remoteTargetHandles);
AnimatorSet animatorSet, GestureState.GestureEndTarget endTarget,
RemoteTargetHandle[] remoteTargetHandles, boolean isHandlingAtomicEvent) {
super.onPrepareGestureEndAnimation(animatorSet, endTarget, remoteTargetHandles,
isHandlingAtomicEvent);
if (mHomeTask != null && endTarget == RECENTS) {
TaskView homeTaskView = getTaskViewByTaskId(mHomeTask.key.id);
if (homeTaskView != null) {
@@ -147,12 +147,7 @@ public class FallbackRecentsView<CONTAINER_TYPE extends Context & RecentsViewCon
pendingAnimation.addEndListener(e -> setCurrentTask(-1));
AnimatorPlaybackController controller = pendingAnimation.createPlaybackController();
controller.dispatchOnStart();
Animator homeDismissAnimator = controller.getAnimationPlayer();
if (animatorSet != null) {
animatorSet.play(homeDismissAnimator);
} else {
homeDismissAnimator.setDuration(0).start();
}
animatorSet.play(controller.getAnimationPlayer());
}
}
}
@@ -317,9 +317,10 @@ public class TaskViewSimulator implements TransformParams.BuilderProxy {
/**
* Adds animation for all the components corresponding to transition from an app to carousel.
*/
public void addAppToCarouselAnim(PendingAnimation pa, Interpolator interpolator) {
public void addAppToCarouselAnim(PendingAnimation pa, Interpolator interpolator,
boolean isHandlingAtomicEvent) {
pa.addFloat(fullScreenProgress, AnimatedFloat.VALUE, 1, 0, interpolator);
if (enableGridOnlyOverview() && mDp.isTablet && mDp.isGestureMode) {
if (enableGridOnlyOverview() && mDp.isTablet && !isHandlingAtomicEvent) {
mIsAnimatingToCarousel = true;
carouselScale.value = mCarouselTaskSize.width() / (float) mFullTaskSize.width();
}
@@ -2969,87 +2969,10 @@ public abstract class RecentsView<
* Called when a gesture from an app has finished, and an end target has been determined.
*/
public void onPrepareGestureEndAnimation(
@Nullable AnimatorSet animatorSet, GestureState.GestureEndTarget endTarget,
RemoteTargetHandle[] remoteTargetHandles) {
Log.d(TAG, "onPrepareGestureEndAnimation - endTarget: " + endTarget);
mCurrentGestureEndTarget = endTarget;
boolean isOverviewEndTarget = endTarget == GestureState.GestureEndTarget.RECENTS;
if (isOverviewEndTarget) {
updateGridProperties();
}
BaseState<?> endState = mSizeStrategy.stateFromGestureEndTarget(endTarget);
// Starting the desk exploded animation when the gesture from an app is released.
if (enableDesktopExplodedView()) {
if (animatorSet == null) {
mUtils.setDeskExplodeProgress(endState.showExplodedDesktopView() ? 1f : 0f);
} else {
animatorSet.play(
ObjectAnimator.ofFloat(this, DESK_EXPLODE_PROGRESS,
endState.showExplodedDesktopView() ? 1f : 0f));
}
for (TaskView taskView : getTaskViews()) {
if (taskView instanceof DesktopTaskView desktopTaskView) {
desktopTaskView.setRemoteTargetHandles(remoteTargetHandles);
}
}
}
if (endState.displayOverviewTasksAsGrid(mContainer.getDeviceProfile())) {
TaskView runningTaskView = getRunningTaskView();
float runningTaskGridTranslationX = 0;
float runningTaskGridTranslationY = 0;
if (runningTaskView != null) {
// Apply the grid translation to running task unless it's being snapped to
// and removes the current translation applied to the running task.
runningTaskGridTranslationX = runningTaskView.getGridTranslationX()
- runningTaskView.getNonGridTranslationX();
runningTaskGridTranslationY = runningTaskView.getGridTranslationY();
}
for (RemoteTargetHandle remoteTargetHandle : remoteTargetHandles) {
TaskViewSimulator tvs = remoteTargetHandle.getTaskViewSimulator();
if (animatorSet == null) {
setGridProgress(1);
if (enableGridOnlyOverview()) {
tvs.taskGridTranslationX.value = runningTaskGridTranslationX;
tvs.taskGridTranslationY.value = runningTaskGridTranslationY;
} else {
tvs.taskPrimaryTranslation.value = runningTaskGridTranslationX;
tvs.taskSecondaryTranslation.value = runningTaskGridTranslationY;
}
} else {
animatorSet.play(ObjectAnimator.ofFloat(this, RECENTS_GRID_PROGRESS, 1));
if (enableGridOnlyOverview()) {
animatorSet.play(tvs.carouselScale.animateToValue(1));
animatorSet.play(tvs.taskGridTranslationX.animateToValue(
runningTaskGridTranslationX));
animatorSet.play(tvs.taskGridTranslationY.animateToValue(
runningTaskGridTranslationY));
} else {
animatorSet.play(tvs.taskPrimaryTranslation.animateToValue(
runningTaskGridTranslationX));
animatorSet.play(tvs.taskSecondaryTranslation.animateToValue(
runningTaskGridTranslationY));
}
}
}
}
int splashAlpha = endState.showTaskThumbnailSplash() ? 1 : 0;
if (animatorSet == null) {
setTaskThumbnailSplashAlpha(splashAlpha);
} else {
animatorSet.play(
ObjectAnimator.ofFloat(this, TASK_THUMBNAIL_SPLASH_ALPHA, splashAlpha));
}
if (enableLargeDesktopWindowingTile()) {
if (animatorSet != null) {
animatorSet.play(
ObjectAnimator.ofFloat(this, DESKTOP_CAROUSEL_DETACH_PROGRESS, 0f));
} else {
DESKTOP_CAROUSEL_DETACH_PROGRESS.set(this, 0f);
}
}
AnimatorSet animatorSet, GestureState.GestureEndTarget endTarget,
RemoteTargetHandle[] remoteTargetHandles, boolean isHandlingAtomicEvent) {
mUtils.onPrepareGestureEndAnimation(animatorSet, endTarget, remoteTargetHandles,
isHandlingAtomicEvent);
}
/**
@@ -3298,7 +3221,7 @@ public abstract class RecentsView<
*
* Skips rebalance.
*/
private void updateGridProperties() {
protected void updateGridProperties() {
updateGridProperties(null);
}
@@ -16,10 +16,13 @@
package com.android.quickstep.views
import android.animation.AnimatorSet
import android.animation.ObjectAnimator
import android.graphics.PointF
import android.graphics.Rect
import android.util.FloatProperty
import android.util.Log
import android.util.Property
import android.view.KeyEvent
import android.view.View
import android.view.View.LAYOUT_DIRECTION_LTR
@@ -27,6 +30,7 @@ import android.view.View.LAYOUT_DIRECTION_RTL
import androidx.core.view.children
import com.android.launcher3.AbstractFloatingView.TYPE_TASK_MENU
import com.android.launcher3.AbstractFloatingView.getTopOpenViewWithType
import com.android.launcher3.Flags.enableDesktopExplodedView
import com.android.launcher3.Flags.enableGridOnlyOverview
import com.android.launcher3.Flags.enableLargeDesktopWindowingTile
import com.android.launcher3.Flags.enableOverviewIconMenu
@@ -34,14 +38,20 @@ import com.android.launcher3.Flags.enableSeparateExternalDisplayTasks
import com.android.launcher3.Utilities.getPivotsForScalingRectToRect
import com.android.launcher3.statehandlers.DesktopVisibilityController
import com.android.launcher3.statehandlers.DesktopVisibilityController.Companion.INACTIVE_DESK_ID
import com.android.launcher3.statemanager.BaseState
import com.android.launcher3.util.IntArray
import com.android.launcher3.util.window.WindowManagerProxy.DesktopVisibilityListener
import com.android.quickstep.GestureState
import com.android.quickstep.RemoteTargetGluer.RemoteTargetHandle
import com.android.quickstep.util.DesksUtils.Companion.areMultiDesksFlagsEnabled
import com.android.quickstep.util.DesktopTask
import com.android.quickstep.util.GroupTask
import com.android.quickstep.util.isExternalDisplay
import com.android.quickstep.views.RecentsView.DESKTOP_CAROUSEL_DETACH_PROGRESS
import com.android.quickstep.views.RecentsView.RECENTS_GRID_PROGRESS
import com.android.quickstep.views.RecentsView.RUNNING_TASK_ATTACH_ALPHA
import com.android.quickstep.views.RecentsView.TAG
import com.android.quickstep.views.RecentsView.TASK_THUMBNAIL_SPLASH_ALPHA
import com.android.systemui.shared.recents.model.Task
import com.android.systemui.shared.recents.model.ThumbnailData
import com.android.wm.shell.shared.GroupedTaskInfo
@@ -563,6 +573,97 @@ class RecentsViewUtils(private val recentsView: RecentsView<*, *>) : DesktopVisi
return matchingTaskView == null
}
fun onPrepareGestureEndAnimation(
animatorSet: AnimatorSet,
endTarget: GestureState.GestureEndTarget,
remoteTargetHandles: Array<RemoteTargetHandle>,
isHandlingAtomicEvent: Boolean,
) {
// Create ObjectAnimator that immediately settles on [endStateValue] when
// [isHandlingAtomicEvent] is true.
fun <T> immediateObjectAnimator(
target: T,
property: Property<T, Float>,
endStateValue: Float,
) =
if (isHandlingAtomicEvent)
ObjectAnimator.ofFloat(target, property, endStateValue, endStateValue)
else ObjectAnimator.ofFloat(target, property, endStateValue)
with(recentsView) {
Log.d(TAG, "onPrepareGestureEndAnimation - endTarget: $endTarget")
mCurrentGestureEndTarget = endTarget
val endState: BaseState<*> = mSizeStrategy.stateFromGestureEndTarget(endTarget)
// Starting the desk exploded animation when the gesture from an app is released.
if (enableDesktopExplodedView()) {
animatorSet.play(
ObjectAnimator.ofFloat(
this,
DESK_EXPLODE_PROGRESS,
if (endState.showExplodedDesktopView()) 1f else 0f,
)
)
taskViews.filterIsInstance<DesktopTaskView>().forEach {
it.remoteTargetHandles = remoteTargetHandles
}
}
if (endState.displayOverviewTasksAsGrid(getDeviceProfile())) {
updateGridProperties()
animatorSet.play(immediateObjectAnimator(this, RECENTS_GRID_PROGRESS, 1f))
val runningTaskView = runningTaskView
var runningTaskGridTranslationX = 0f
var runningTaskGridTranslationY = 0f
if (runningTaskView != null) {
// Apply the grid translation to running task unless it's being snapped to
// and removes the current translation applied to the running task.
runningTaskGridTranslationX =
(runningTaskView.gridTranslationX - runningTaskView.nonGridTranslationX)
runningTaskGridTranslationY = runningTaskView.gridTranslationY
}
remoteTargetHandles.forEach { remoteTargetHandle ->
val taskViewSimulator = remoteTargetHandle.taskViewSimulator
if (enableGridOnlyOverview()) {
animatorSet.play(taskViewSimulator.carouselScale.animateToValue(1f))
animatorSet.play(
taskViewSimulator.taskGridTranslationX.animateToValue(
runningTaskGridTranslationX
)
)
animatorSet.play(
taskViewSimulator.taskGridTranslationY.animateToValue(
runningTaskGridTranslationY
)
)
} else {
animatorSet.play(
taskViewSimulator.taskPrimaryTranslation.animateToValue(
runningTaskGridTranslationX
)
)
animatorSet.play(
taskViewSimulator.taskSecondaryTranslation.animateToValue(
runningTaskGridTranslationY
)
)
}
}
}
animatorSet.play(
immediateObjectAnimator(
this,
TASK_THUMBNAIL_SPLASH_ALPHA,
if (endState.showTaskThumbnailSplash()) 1f else 0f,
)
)
if (enableLargeDesktopWindowingTile()) {
animatorSet.play(ObjectAnimator.ofFloat(this, DESKTOP_CAROUSEL_DETACH_PROGRESS, 0f))
}
}
}
companion object {
class RecentsViewFloatProperty(
private val utilsProperty: KMutableProperty1<RecentsViewUtils, Float>
@@ -423,7 +423,7 @@ constructor(
}
// The following grid translations scales with mGridProgress.
protected var gridTranslationX = 0f
var gridTranslationX = 0f
set(value) {
field = value
applyTranslationX()
@@ -444,7 +444,7 @@ constructor(
// Applied as a complement to gridTranslation, for adjusting the carousel overview and quick
// switch.
protected var nonGridTranslationX = 0f
var nonGridTranslationX = 0f
set(value) {
field = value
applyTranslationX()