From ee7406faeae7b933ddcb62c6fa9ecf5f46df6f94 Mon Sep 17 00:00:00 2001 From: Maryam Dehaini Date: Tue, 13 Jun 2023 13:22:09 -0700 Subject: [PATCH] Set position during TRANSIT_ENTER_DESKTOP_MODE transition Currently, relayout runs during the TRANSIT_ENTER_DESKTOP_MODE transition and sets the surface's position in startT and finishT. This change overrides that by calculating and setting the surface control's position to the correct value. We also introduced the MoveToDesktop animator since we previously had an animator running at the same time as the enter desktop animation that was also positioning the task which meant we had to pass in a lot of information over to the transition handler. Now, we only pass in the MoveToDesktop animator which holds all the information we need. Test: Drag to freeform and check that there is no flash Bug: 277780635 Change-Id: Iaed5191f9d56375c42c32e9afce3ffc54ca5f351 --- .../desktopmode/DesktopTasksController.kt | 17 +++-- .../EnterDesktopTaskTransitionHandler.java | 73 ++++++++++++++++--- .../DesktopModeWindowDecorViewModel.java | 54 +++++--------- .../windowdecor/MoveToDesktopAnimator.kt | 72 ++++++++++++++++++ ...EnterDesktopTaskTransitionHandlerTest.java | 16 +++- 5 files changed, 175 insertions(+), 57 deletions(-) create mode 100644 libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/MoveToDesktopAnimator.kt diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt index 4fda4b7896c23..e80e8ba53ec2d 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt @@ -63,6 +63,7 @@ import com.android.wm.shell.sysui.ShellSharedConstants import com.android.wm.shell.transition.Transitions import com.android.wm.shell.util.KtProtoLog import com.android.wm.shell.windowdecor.DesktopModeWindowDecoration +import com.android.wm.shell.windowdecor.MoveToDesktopAnimator import java.io.PrintWriter import java.util.concurrent.Executor import java.util.function.Consumer @@ -204,7 +205,11 @@ class DesktopTasksController( * Moves a single task to freeform and sets the taskBounds to the passed in bounds, * startBounds */ - fun moveToFreeform(taskInfo: RunningTaskInfo, startBounds: Rect) { + fun moveToFreeform( + taskInfo: RunningTaskInfo, + startBounds: Rect, + dragToDesktopValueAnimator: MoveToDesktopAnimator + ) { KtProtoLog.v( WM_SHELL_DESKTOP_MODE, "DesktopTasksController: moveToFreeform with bounds taskId=%d", @@ -216,8 +221,8 @@ class DesktopTasksController( wct.setBounds(taskInfo.token, startBounds) if (Transitions.ENABLE_SHELL_TRANSITIONS) { - enterDesktopTaskTransitionHandler.startTransition( - Transitions.TRANSIT_ENTER_FREEFORM, wct, mOnAnimationFinishedCallback) + enterDesktopTaskTransitionHandler.startMoveToFreeformAnimation(wct, + dragToDesktopValueAnimator, mOnAnimationFinishedCallback) } else { shellTaskOrganizer.applyTransaction(wct) } @@ -270,7 +275,7 @@ class DesktopTasksController( * Move a task to fullscreen after being dragged from fullscreen and released back into * status bar area */ - fun cancelMoveToFreeform(task: RunningTaskInfo, position: Point) { + fun cancelMoveToFreeform(task: RunningTaskInfo, moveToDesktopAnimator: MoveToDesktopAnimator) { KtProtoLog.v( WM_SHELL_DESKTOP_MODE, "DesktopTasksController: cancelMoveToFreeform taskId=%d", @@ -280,8 +285,8 @@ class DesktopTasksController( wct.setBounds(task.token, null) if (Transitions.ENABLE_SHELL_TRANSITIONS) { - enterDesktopTaskTransitionHandler.startCancelMoveToDesktopMode( - wct, position) { t -> + enterDesktopTaskTransitionHandler.startCancelMoveToDesktopMode(wct, + moveToDesktopAnimator) { t -> val callbackWCT = WindowContainerTransaction() visualIndicator?.releaseVisualIndicator(t) visualIndicator = null diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/EnterDesktopTaskTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/EnterDesktopTaskTransitionHandler.java index 3e175f3a9ae29..650cac5cb9993 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/EnterDesktopTaskTransitionHandler.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/EnterDesktopTaskTransitionHandler.java @@ -22,9 +22,10 @@ import android.animation.Animator; import android.animation.AnimatorListenerAdapter; import android.animation.ValueAnimator; import android.app.ActivityManager; -import android.graphics.Point; +import android.graphics.PointF; import android.graphics.Rect; import android.os.IBinder; +import android.util.Slog; import android.view.SurfaceControl; import android.view.WindowManager; import android.window.TransitionInfo; @@ -35,6 +36,7 @@ import androidx.annotation.NonNull; import androidx.annotation.Nullable; import com.android.wm.shell.transition.Transitions; +import com.android.wm.shell.windowdecor.MoveToDesktopAnimator; import java.util.ArrayList; import java.util.List; @@ -47,18 +49,17 @@ import java.util.function.Supplier; */ public class EnterDesktopTaskTransitionHandler implements Transitions.TransitionHandler { + private static final String TAG = "EnterDesktopTaskTransitionHandler"; private final Transitions mTransitions; private final Supplier mTransactionSupplier; - // The size of the screen during drag relative to the fullscreen size - public static final float DRAG_FREEFORM_SCALE = 0.4f; // The size of the screen after drag relative to the fullscreen size public static final float FINAL_FREEFORM_SCALE = 0.6f; public static final int FREEFORM_ANIMATION_DURATION = 336; private final List mPendingTransitionTokens = new ArrayList<>(); - private Point mPosition; private Consumer mOnAnimationFinishedCallback; + private MoveToDesktopAnimator mMoveToDesktopAnimator; public EnterDesktopTaskTransitionHandler( Transitions transitions) { @@ -86,16 +87,31 @@ public class EnterDesktopTaskTransitionHandler implements Transitions.Transition mPendingTransitionTokens.add(token); } + /** + * Starts Transition of type TRANSIT_ENTER_FREEFORM + * @param wct WindowContainerTransaction for transition + * @param moveToDesktopAnimator Animator that shrinks and positions task during two part move + * to desktop animation + * @param onAnimationEndCallback to be called after animation + */ + public void startMoveToFreeformAnimation(@NonNull WindowContainerTransaction wct, + @NonNull MoveToDesktopAnimator moveToDesktopAnimator, + Consumer onAnimationEndCallback) { + mMoveToDesktopAnimator = moveToDesktopAnimator; + startTransition(Transitions.TRANSIT_ENTER_FREEFORM, wct, onAnimationEndCallback); + } + /** * Starts Transition of type TRANSIT_CANCEL_ENTERING_DESKTOP_MODE * @param wct WindowContainerTransaction for transition - * @param position Position of task when transition is triggered + * @param moveToDesktopAnimator Animator that shrinks and positions task during two part move + * to desktop animation * @param onAnimationEndCallback to be called after animation */ public void startCancelMoveToDesktopMode(@NonNull WindowContainerTransaction wct, - Point position, + MoveToDesktopAnimator moveToDesktopAnimator, Consumer onAnimationEndCallback) { - mPosition = position; + mMoveToDesktopAnimator = moveToDesktopAnimator; startTransition(Transitions.TRANSIT_CANCEL_ENTERING_DESKTOP_MODE, wct, onAnimationEndCallback); } @@ -145,9 +161,23 @@ public class EnterDesktopTaskTransitionHandler implements Transitions.Transition // to null and we don't require an animation final SurfaceControl sc = change.getLeash(); startT.setWindowCrop(sc, null); + + if (mMoveToDesktopAnimator == null + || mMoveToDesktopAnimator.getTaskId() != change.getTaskInfo().taskId) { + Slog.e(TAG, "No animator available for this transition"); + return false; + } + + // Calculate and set position of the task + final PointF position = mMoveToDesktopAnimator.getPosition(); + startT.setPosition(sc, position.x, position.y); + finishT.setPosition(sc, position.x, position.y); + startT.apply(); + mTransitions.getMainExecutor().execute( () -> finishCallback.onTransitionFinished(null, null)); + return true; } @@ -162,12 +192,18 @@ public class EnterDesktopTaskTransitionHandler implements Transitions.Transition endBounds.height()); startT.apply(); + // End the animation that shrinks the window when task is first dragged from fullscreen + if (mMoveToDesktopAnimator != null) { + mMoveToDesktopAnimator.endAnimator(); + } + // We want to find the scale of the current bounds relative to the end bounds. The // task is currently scaled to DRAG_FREEFORM_SCALE and the final bounds will be // scaled to FINAL_FREEFORM_SCALE. So, it is scaled to // DRAG_FREEFORM_SCALE / FINAL_FREEFORM_SCALE relative to the freeform bounds final ValueAnimator animator = - ValueAnimator.ofFloat(DRAG_FREEFORM_SCALE / FINAL_FREEFORM_SCALE, 1f); + ValueAnimator.ofFloat( + MoveToDesktopAnimator.DRAG_FREEFORM_SCALE / FINAL_FREEFORM_SCALE, 1f); animator.setDuration(FREEFORM_ANIMATION_DURATION); final SurfaceControl.Transaction t = mTransactionSupplier.get(); animator.addUpdateListener(animation -> { @@ -199,8 +235,7 @@ public class EnterDesktopTaskTransitionHandler implements Transitions.Transition } if (type == Transitions.TRANSIT_CANCEL_ENTERING_DESKTOP_MODE - && taskInfo.getWindowingMode() == WINDOWING_MODE_FREEFORM - && mPosition != null) { + && taskInfo.getWindowingMode() == WINDOWING_MODE_FREEFORM) { // This Transition animates a task to fullscreen after being dragged from the status // bar and then released back into the status bar area final SurfaceControl sc = change.getLeash(); @@ -210,13 +245,27 @@ public class EnterDesktopTaskTransitionHandler implements Transitions.Transition .setWindowCrop(sc, endBounds.width(), endBounds.height()) .apply(); + if (mMoveToDesktopAnimator == null + || mMoveToDesktopAnimator.getTaskId() != change.getTaskInfo().taskId) { + Slog.e(TAG, "No animator available for this transition"); + return false; + } + + // End the animation that shrinks the window when task is first dragged from fullscreen + mMoveToDesktopAnimator.endAnimator(); + final ValueAnimator animator = new ValueAnimator(); - animator.setFloatValues(DRAG_FREEFORM_SCALE, 1f); + animator.setFloatValues(MoveToDesktopAnimator.DRAG_FREEFORM_SCALE, 1f); animator.setDuration(FREEFORM_ANIMATION_DURATION); final SurfaceControl.Transaction t = mTransactionSupplier.get(); + + // Get position of the task + final float x = mMoveToDesktopAnimator.getPosition().x; + final float y = mMoveToDesktopAnimator.getPosition().y; + animator.addUpdateListener(animation -> { final float scale = (float) animation.getAnimatedValue(); - t.setPosition(sc, mPosition.x * (1 - scale), mPosition.y * (1 - scale)) + t.setPosition(sc, x * (1 - scale), y * (1 - scale)) .setScale(sc, scale, scale) .show(sc) .apply(); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java index 7245bc91cfca4..14f2f9b9fcf57 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java @@ -24,9 +24,9 @@ import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED; import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_BOTTOM_OR_RIGHT; import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_TOP_OR_LEFT; -import static com.android.wm.shell.desktopmode.EnterDesktopTaskTransitionHandler.DRAG_FREEFORM_SCALE; import static com.android.wm.shell.desktopmode.EnterDesktopTaskTransitionHandler.FINAL_FREEFORM_SCALE; import static com.android.wm.shell.desktopmode.EnterDesktopTaskTransitionHandler.FREEFORM_ANIMATION_DURATION; +import static com.android.wm.shell.windowdecor.MoveToDesktopAnimator.DRAG_FREEFORM_SCALE; import android.animation.Animator; import android.animation.AnimatorListenerAdapter; @@ -112,9 +112,8 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { private SplitScreenController mSplitScreenController; - private ValueAnimator mDragToDesktopValueAnimator; + private MoveToDesktopAnimator mMoveToDesktopAnimator; private final Rect mDragToDesktopAnimationStartBounds = new Rect(); - private boolean mDragToDesktopAnimationStarted; public DesktopModeWindowDecorViewModel( Context context, @@ -233,7 +232,6 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { removeTaskFromEventReceiver(oldTaskInfo.displayId); incrementEventReceiverTasks(taskInfo.displayId); } - decoration.relayout(taskInfo); } @@ -599,7 +597,7 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { } case MotionEvent.ACTION_UP: { if (relevantDecor == null) { - mDragToDesktopAnimationStarted = false; + mMoveToDesktopAnimator = null; mTransitionDragActive = false; return; } @@ -613,14 +611,14 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { } else if (DesktopModeStatus.isProto1Enabled()) { mDesktopModeController.ifPresent(c -> c.setDesktopModeActive(true)); } - mDragToDesktopAnimationStarted = false; + mMoveToDesktopAnimator = null; return; - } else if (mDragToDesktopAnimationStarted) { - Point position = new Point((int) ev.getX(), (int) ev.getY()); + } else if (mMoveToDesktopAnimator != null) { relevantDecor.incrementRelayoutBlock(); mDesktopTasksController.ifPresent( - c -> c.cancelMoveToFreeform(relevantDecor.mTaskInfo, position)); - mDragToDesktopAnimationStarted = false; + c -> c.cancelMoveToFreeform(relevantDecor.mTaskInfo, + mMoveToDesktopAnimator)); + mMoveToDesktopAnimator = null; return; } } @@ -640,21 +638,19 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { final int statusBarHeight = getStatusBarHeight( relevantDecor.mTaskInfo.displayId); if (ev.getY() > statusBarHeight) { - if (!mDragToDesktopAnimationStarted) { - mDragToDesktopAnimationStarted = true; + if (mMoveToDesktopAnimator == null) { + mMoveToDesktopAnimator = new MoveToDesktopAnimator( + mDragToDesktopAnimationStartBounds, relevantDecor.mTaskInfo, + relevantDecor.mTaskSurface); mDesktopTasksController.ifPresent( c -> c.moveToFreeform(relevantDecor.mTaskInfo, - mDragToDesktopAnimationStartBounds)); - startAnimation(relevantDecor); + mDragToDesktopAnimationStartBounds, + mMoveToDesktopAnimator)); + mMoveToDesktopAnimator.startAnimation(); } } - if (mDragToDesktopAnimationStarted) { - Transaction t = mTransactionFactory.get(); - float width = (float) mDragToDesktopValueAnimator.getAnimatedValue() - * mDragToDesktopAnimationStartBounds.width(); - float x = ev.getX() - (width / 2); - t.setPosition(relevantDecor.mTaskSurface, x, ev.getY()); - t.apply(); + if (mMoveToDesktopAnimator != null) { + mMoveToDesktopAnimator.updatePosition(ev); } } break; @@ -662,7 +658,7 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { case MotionEvent.ACTION_CANCEL: { mTransitionDragActive = false; - mDragToDesktopAnimationStarted = false; + mMoveToDesktopAnimator = null; } } } @@ -729,20 +725,6 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { animator.start(); } - private void startAnimation(@NonNull DesktopModeWindowDecoration focusedDecor) { - mDragToDesktopValueAnimator = ValueAnimator.ofFloat(1f, DRAG_FREEFORM_SCALE); - mDragToDesktopValueAnimator.setDuration(FREEFORM_ANIMATION_DURATION); - final Transaction t = mTransactionFactory.get(); - mDragToDesktopValueAnimator.addUpdateListener(animation -> { - final float animatorValue = (float) animation.getAnimatedValue(); - SurfaceControl sc = focusedDecor.mTaskSurface; - t.setScale(sc, animatorValue, animatorValue); - t.apply(); - }); - - mDragToDesktopValueAnimator.start(); - } - @Nullable private DesktopModeWindowDecoration getRelevantWindowDecor(MotionEvent ev) { if (mSplitScreenController != null && mSplitScreenController.isSplitScreenVisible()) { diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/MoveToDesktopAnimator.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/MoveToDesktopAnimator.kt new file mode 100644 index 0000000000000..b2267ddb6ba76 --- /dev/null +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/MoveToDesktopAnimator.kt @@ -0,0 +1,72 @@ +package com.android.wm.shell.windowdecor + +import android.animation.ValueAnimator +import android.app.ActivityManager.RunningTaskInfo +import android.graphics.PointF +import android.graphics.Rect +import android.view.MotionEvent +import android.view.SurfaceControl + +/** + * Creates an animator to shrink and position task after a user drags a fullscreen task from + * the top of the screen to transition it into freeform and before the user releases the task. The + * MoveToDesktopAnimator object also holds information about the state of the task that are + * accessed by the EnterDesktopTaskTransitionHandler. + */ +class MoveToDesktopAnimator @JvmOverloads constructor( + private val startBounds: Rect, + private val taskInfo: RunningTaskInfo, + private val taskSurface: SurfaceControl, + private val transactionFactory: () -> SurfaceControl.Transaction = + SurfaceControl::Transaction +) { + companion object { + // The size of the screen during drag relative to the fullscreen size + const val DRAG_FREEFORM_SCALE: Float = 0.4f + const val ANIMATION_DURATION = 336 + } + + private val animatedTaskWidth + get() = dragToDesktopAnimator.animatedValue as Float * startBounds.width() + private val dragToDesktopAnimator: ValueAnimator = ValueAnimator.ofFloat(1f, + DRAG_FREEFORM_SCALE) + .setDuration(ANIMATION_DURATION.toLong()) + .apply { + val t = SurfaceControl.Transaction() + addUpdateListener { animation -> + val animatorValue = animation.animatedValue as Float + t.setScale(taskSurface, animatorValue, animatorValue) + .apply() + } + } + + val taskId get() = taskInfo.taskId + val position: PointF = PointF(0.0f, 0.0f) + + /** + * Starts the animation that scales the task down. + */ + fun startAnimation() { + dragToDesktopAnimator.start() + } + + /** + * Uses the position of the motion event and the current scale of the task as defined by the + * ValueAnimator to update the local position variable and set the task surface's position + */ + fun updatePosition(ev: MotionEvent) { + position.x = ev.x - animatedTaskWidth / 2 + position.y = ev.y + + val t = transactionFactory() + t.setPosition(taskSurface, position.x, position.y) + t.apply() + } + + /** + * Ends the animation, setting the scale and position to the final animation value + */ + fun endAnimator() { + dragToDesktopAnimator.end() + } +} \ No newline at end of file diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/EnterDesktopTaskTransitionHandlerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/EnterDesktopTaskTransitionHandlerTest.java index 8592dea192890..c6642f3472f01 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/EnterDesktopTaskTransitionHandlerTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/EnterDesktopTaskTransitionHandlerTest.java @@ -28,6 +28,7 @@ import static org.mockito.Mockito.verify; import android.annotation.NonNull; import android.app.ActivityManager; import android.app.WindowConfiguration; +import android.graphics.PointF; import android.graphics.Rect; import android.os.IBinder; import android.view.SurfaceControl; @@ -41,6 +42,7 @@ import androidx.test.filters.SmallTest; import com.android.wm.shell.common.ShellExecutor; import com.android.wm.shell.transition.Transitions; +import com.android.wm.shell.windowdecor.MoveToDesktopAnimator; import junit.framework.AssertionFailedError; @@ -73,6 +75,10 @@ public class EnterDesktopTaskTransitionHandlerTest { ShellExecutor mExecutor; @Mock SurfaceControl mSurfaceControl; + @Mock + MoveToDesktopAnimator mMoveToDesktopAnimator; + @Mock + PointF mPosition; private EnterDesktopTaskTransitionHandler mEnterDesktopTaskTransitionHandler; @@ -82,6 +88,7 @@ public class EnterDesktopTaskTransitionHandlerTest { doReturn(mExecutor).when(mTransitions).getMainExecutor(); doReturn(mAnimationT).when(mTransactionFactory).get(); + doReturn(mPosition).when(mMoveToDesktopAnimator).getPosition(); mEnterDesktopTaskTransitionHandler = new EnterDesktopTaskTransitionHandler(mTransitions, mTransactionFactory); @@ -89,12 +96,15 @@ public class EnterDesktopTaskTransitionHandlerTest { @Test public void testEnterFreeformAnimation() { - final int transitionType = Transitions.TRANSIT_ENTER_FREEFORM; final int taskId = 1; WindowContainerTransaction wct = new WindowContainerTransaction(); doReturn(mToken).when(mTransitions) - .startTransition(transitionType, wct, mEnterDesktopTaskTransitionHandler); - mEnterDesktopTaskTransitionHandler.startTransition(transitionType, wct, null); + .startTransition(Transitions.TRANSIT_ENTER_FREEFORM, wct, + mEnterDesktopTaskTransitionHandler); + doReturn(taskId).when(mMoveToDesktopAnimator).getTaskId(); + + mEnterDesktopTaskTransitionHandler.startMoveToFreeformAnimation(wct, + mMoveToDesktopAnimator, null); TransitionInfo.Change change = createChange(WindowManager.TRANSIT_CHANGE, taskId, WINDOWING_MODE_FREEFORM);