Loading libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeVisualIndicator.java +13 −2 Original line number Diff line number Diff line Loading @@ -27,6 +27,7 @@ import android.animation.AnimatorListenerAdapter; import android.animation.RectEvaluator; import android.animation.ValueAnimator; import android.annotation.NonNull; import android.annotation.Nullable; import android.app.ActivityManager; import android.app.WindowConfiguration; import android.content.Context; Loading Loading @@ -262,12 +263,22 @@ public class DesktopModeVisualIndicator { /** * Fade out indicator without fully releasing it. Animator fades it out while shrinking bounds. * * @param finishCallback called when animation ends or gets cancelled */ private void fadeOutIndicator() { void fadeOutIndicator(@Nullable Runnable finishCallback) { final VisualIndicatorAnimator animator = VisualIndicatorAnimator .fadeBoundsOut(mView, mCurrentType, mDisplayController.getDisplayLayout(mTaskInfo.displayId)); animator.start(); if (finishCallback != null) { animator.addListener(new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator animation) { finishCallback.run(); } }); } mCurrentType = IndicatorType.NO_INDICATOR; } Loading @@ -282,7 +293,7 @@ public class DesktopModeVisualIndicator { if (mCurrentType == IndicatorType.NO_INDICATOR) { fadeInIndicator(newType); } else if (newType == IndicatorType.NO_INDICATOR) { fadeOutIndicator(); fadeOutIndicator(null /* finishCallback */); } else { final VisualIndicatorAnimator animator = VisualIndicatorAnimator.animateIndicatorType( mView, mDisplayController.getDisplayLayout(mTaskInfo.displayId), mCurrentType, Loading libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt +6 −4 Original line number Diff line number Diff line Loading @@ -163,10 +163,12 @@ class DesktopTasksController( } private fun removeVisualIndicator(tx: SurfaceControl.Transaction) { visualIndicator?.fadeOutIndicator { visualIndicator?.releaseVisualIndicator(tx) visualIndicator = null } } } /** Task id of the task currently being dragged from fullscreen/split. */ val draggingTaskId Loading @@ -193,7 +195,7 @@ class DesktopTasksController( ) transitions.addHandler(this) taskRepository.addVisibleTasksListener(taskVisibilityListener, mainExecutor) dragToDesktopTransitionHandler.setDragToDesktopStateListener(dragToDesktopStateListener) dragToDesktopTransitionHandler.dragToDesktopStateListener = dragToDesktopStateListener recentsTransitionHandler.addTransitionStateListener( object : RecentsTransitionStateListener { override fun onAnimationStateChanged(running: Boolean) { Loading @@ -213,7 +215,7 @@ class DesktopTasksController( fun setOnTaskResizeAnimationListener(listener: OnTaskResizeAnimationListener) { toggleResizeDesktopTaskTransitionHandler.setOnTaskResizeAnimationListener(listener) enterDesktopTaskTransitionHandler.setOnTaskResizeAnimationListener(listener) dragToDesktopTransitionHandler.setOnTaskResizeAnimatorListener(listener) dragToDesktopTransitionHandler.onTaskResizeAnimationListener = listener } fun setOnTaskRepositionAnimationListener(listener: OnTaskRepositionAnimationListener) { Loading libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandler.kt +143 −17 Original line number Diff line number Diff line Loading @@ -28,17 +28,20 @@ import android.window.TransitionInfo import android.window.TransitionInfo.Change import android.window.TransitionRequestInfo import android.window.WindowContainerTransaction import androidx.dynamicanimation.animation.SpringForce import com.android.internal.jank.Cuj.CUJ_DESKTOP_MODE_ENTER_APP_HANDLE_DRAG_HOLD import com.android.internal.jank.Cuj.CUJ_DESKTOP_MODE_ENTER_APP_HANDLE_DRAG_RELEASE import com.android.internal.jank.InteractionJankMonitor import com.android.internal.protolog.ProtoLog import com.android.wm.shell.RootTaskDisplayAreaOrganizer import com.android.wm.shell.animation.FloatProperties import com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_BOTTOM_OR_RIGHT import com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_TOP_OR_LEFT import com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_UNDEFINED import com.android.wm.shell.common.split.SplitScreenConstants.SplitPosition import com.android.wm.shell.protolog.ShellProtoLogGroup import com.android.wm.shell.shared.TransitionUtil import com.android.wm.shell.shared.animation.PhysicsAnimator import com.android.wm.shell.splitscreen.SplitScreenController import com.android.wm.shell.transition.Transitions import com.android.wm.shell.transition.Transitions.TRANSIT_DESKTOP_MODE_CANCEL_DRAG_TO_DESKTOP Loading @@ -49,6 +52,7 @@ import com.android.wm.shell.windowdecor.MoveToDesktopAnimator import com.android.wm.shell.windowdecor.MoveToDesktopAnimator.Companion.DRAG_FREEFORM_SCALE import com.android.wm.shell.windowdecor.OnTaskResizeAnimationListener import java.util.function.Supplier import kotlin.math.max /** * Handles the transition to enter desktop from fullscreen by dragging on the handle bar. It also Loading @@ -64,17 +68,15 @@ sealed class DragToDesktopTransitionHandler( private val context: Context, private val transitions: Transitions, private val taskDisplayAreaOrganizer: RootTaskDisplayAreaOrganizer, private val interactionJankMonitor: InteractionJankMonitor, private val transactionSupplier: Supplier<SurfaceControl.Transaction>, protected val interactionJankMonitor: InteractionJankMonitor, protected val transactionSupplier: Supplier<SurfaceControl.Transaction>, ) : TransitionHandler { private val rectEvaluator = RectEvaluator(Rect()) protected val rectEvaluator = RectEvaluator(Rect()) private val launchHomeIntent = Intent(Intent.ACTION_MAIN).addCategory(Intent.CATEGORY_HOME) private var dragToDesktopStateListener: DragToDesktopStateListener? = null private lateinit var splitScreenController: SplitScreenController private var transitionState: TransitionState? = null private lateinit var onTaskResizeAnimationListener: OnTaskResizeAnimationListener /** Whether a drag-to-desktop transition is in progress. */ val inProgress: Boolean Loading @@ -83,20 +85,18 @@ sealed class DragToDesktopTransitionHandler( /** The task id of the task currently being dragged from fullscreen/split. */ val draggingTaskId: Int get() = transitionState?.draggedTaskId ?: INVALID_TASK_ID /** Sets a listener to receive callback about events during the transition animation. */ fun setDragToDesktopStateListener(listener: DragToDesktopStateListener) { dragToDesktopStateListener = listener } /** Listener to receive callback about events during the transition animation. */ var dragToDesktopStateListener: DragToDesktopStateListener? = null /** Task listener for animation start, task bounds resize, and the animation finish */ lateinit var onTaskResizeAnimationListener: OnTaskResizeAnimationListener /** Setter needed to avoid cyclic dependency. */ fun setSplitScreenController(controller: SplitScreenController) { splitScreenController = controller } fun setOnTaskResizeAnimatorListener(listener: OnTaskResizeAnimationListener) { onTaskResizeAnimationListener = listener } /** * Starts a transition that performs a transient launch of Home so that Home is brought to the * front while still keeping the currently focused task that is being dragged resumed. This Loading Loading @@ -503,6 +503,7 @@ sealed class DragToDesktopTransitionHandler( finishTransaction: SurfaceControl.Transaction ) { val state = requireTransitionState() val freeformTaskChanges = mutableListOf<Change>() info.changes.forEachIndexed { i, change -> when { state is TransitionState.FromSplit && Loading @@ -527,12 +528,15 @@ sealed class DragToDesktopTransitionHandler( ?: error("Expected dragged leash to be non-null") startTransaction.setRelativeLayer(change.leash, draggedTaskLeash, -i) finishTransaction.setRelativeLayer(change.leash, draggedTaskLeash, -i) freeformTaskChanges.add(change) } } } state.freeformTaskChanges = freeformTaskChanges } private fun animateEndDragToDesktop( protected open fun animateEndDragToDesktop( startTransaction: SurfaceControl.Transaction, startTransitionFinishCb: Transitions.TransitionFinishCallback ) { Loading Loading @@ -597,7 +601,7 @@ sealed class DragToDesktopTransitionHandler( object : AnimatorListenerAdapter() { override fun onAnimationEnd(animation: Animator) { onTaskResizeAnimationListener.onAnimationEnd(state.draggedTaskId) startTransitionFinishCb.onTransitionFinished(null /* null */) startTransitionFinishCb.onTransitionFinished(/* wct = */ null) clearState() interactionJankMonitor.end( CUJ_DESKTOP_MODE_ENTER_APP_HANDLE_DRAG_RELEASE Loading Loading @@ -728,7 +732,7 @@ sealed class DragToDesktopTransitionHandler( wct.restoreTransientOrder(homeWc) } private fun clearState() { protected fun clearState() { transitionState = null } Loading Loading @@ -778,6 +782,7 @@ sealed class DragToDesktopTransitionHandler( abstract var cancelTransitionToken: IBinder? abstract var homeChange: Change? abstract var draggedTaskChange: Change? abstract var freeformTaskChanges: List<Change> abstract var cancelState: CancelState abstract var startAborted: Boolean Loading @@ -790,6 +795,7 @@ sealed class DragToDesktopTransitionHandler( override var cancelTransitionToken: IBinder? = null, override var homeChange: Change? = null, override var draggedTaskChange: Change? = null, override var freeformTaskChanges: List<Change> = emptyList(), override var cancelState: CancelState = CancelState.NO_CANCEL, override var startAborted: Boolean = false, var otherRootChanges: MutableList<Change> = mutableListOf() Loading @@ -804,6 +810,7 @@ sealed class DragToDesktopTransitionHandler( override var cancelTransitionToken: IBinder? = null, override var homeChange: Change? = null, override var draggedTaskChange: Change? = null, override var freeformTaskChanges: List<Change> = emptyList(), override var cancelState: CancelState = CancelState.NO_CANCEL, override var startAborted: Boolean = false, var splitRootChange: Change? = null, Loading @@ -825,7 +832,7 @@ sealed class DragToDesktopTransitionHandler( companion object { /** The duration of the animation to commit or cancel the drag-to-desktop gesture. */ private const val DRAG_TO_DESKTOP_FINISH_ANIM_DURATION_MS = 336L internal const val DRAG_TO_DESKTOP_FINISH_ANIM_DURATION_MS = 336L } } Loading Loading @@ -885,6 +892,15 @@ constructor( transactionSupplier ) { private val positionSpringConfig = PhysicsAnimator.SpringConfig( SpringForce.STIFFNESS_LOW, SpringForce.DAMPING_RATIO_LOW_BOUNCY ) private val sizeSpringConfig = PhysicsAnimator.SpringConfig(SpringForce.STIFFNESS_LOW, SpringForce.DAMPING_RATIO_NO_BOUNCY) /** * @return layers in order: * - appLayers - below everything z < 0, effectively hides the leash Loading @@ -911,5 +927,115 @@ constructor( val homeLeash = state.homeChange?.leash ?: error("Expects home leash to be non-null") // Hide home on finish to prevent flickering when wallpaper activity flag is enabled finishTransaction.hide(homeLeash) // Setup freeform tasks before animation state.freeformTaskChanges.forEach { change -> val startScale = DRAG_TO_DESKTOP_FREEFORM_TASK_INITIAL_SCALE val startX = change.endAbsBounds.left + change.endAbsBounds.width() * (1 - startScale) / 2 val startY = change.endAbsBounds.top + change.endAbsBounds.height() * (1 - startScale) / 2 startTransaction.setPosition(change.leash, startX, startY) startTransaction.setScale(change.leash, startScale, startScale) startTransaction.setAlpha(change.leash, 0f) } } override fun animateEndDragToDesktop( startTransaction: SurfaceControl.Transaction, startTransitionFinishCb: Transitions.TransitionFinishCallback ) { val state = requireTransitionState() val draggedTaskChange = state.draggedTaskChange ?: error("Expected non-null change of dragged task") val draggedTaskLeash = draggedTaskChange.leash val freeformTaskChanges = state.freeformTaskChanges val startBounds = draggedTaskChange.startAbsBounds val endBounds = draggedTaskChange.endAbsBounds val currentVelocity = state.dragAnimator.computeCurrentVelocity() // Cancel any animation that may be currently playing; we will use the relevant // details of that animation here. state.dragAnimator.cancelAnimator() // We still apply scale to task bounds; as we animate the bounds to their // end value, animate scale to 1. val startScale = state.dragAnimator.scale val startPosition = state.dragAnimator.position val startBoundsWithOffset = Rect(startBounds).apply { offset(startPosition.x.toInt(), startPosition.y.toInt()) } dragToDesktopStateListener?.onCommitToDesktopAnimationStart(startTransaction) // Accept the merge by applying the merging transaction (applied by #showResizeVeil) // and finish callback. Show the veil and position the task at the first frame before // starting the final animation. onTaskResizeAnimationListener.onAnimationStart( state.draggedTaskId, startTransaction, startBoundsWithOffset ) val tx: SurfaceControl.Transaction = transactionSupplier.get() PhysicsAnimator.getInstance(startBoundsWithOffset) .spring( FloatProperties.RECT_X, endBounds.left.toFloat(), currentVelocity.x, positionSpringConfig ) .spring( FloatProperties.RECT_Y, endBounds.top.toFloat(), currentVelocity.y, positionSpringConfig ) .spring(FloatProperties.RECT_WIDTH, endBounds.width().toFloat(), sizeSpringConfig) .spring(FloatProperties.RECT_HEIGHT, endBounds.height().toFloat(), sizeSpringConfig) .addUpdateListener { animBounds, _ -> val animFraction = (animBounds.width() - startBounds.width()).toFloat() / (endBounds.width() - startBounds.width()) val animScale = startScale + animFraction * (1 - startScale) // Freeform animation starts 50% in the animation val freeformAnimFraction = max(animFraction - 0.5f, 0f) * 2f val freeformStartScale = DRAG_TO_DESKTOP_FREEFORM_TASK_INITIAL_SCALE val freeformAnimScale = freeformStartScale + freeformAnimFraction * (1 - freeformStartScale) tx.apply { // Update dragged task setScale(draggedTaskLeash, animScale, animScale) setPosition( draggedTaskLeash, animBounds.left.toFloat(), animBounds.top.toFloat() ) // Update freeform tasks freeformTaskChanges.forEach { val startX = it.endAbsBounds.left + it.endAbsBounds.width() * (1 - freeformAnimScale) / 2 val startY = it.endAbsBounds.top + it.endAbsBounds.height() * (1 - freeformAnimScale) / 2 setPosition(it.leash, startX, startY) setScale(it.leash, freeformAnimScale, freeformAnimScale) setAlpha(it.leash, freeformAnimFraction) } } onTaskResizeAnimationListener.onBoundsChange(state.draggedTaskId, tx, animBounds) } .withEndActions({ onTaskResizeAnimationListener.onAnimationEnd(state.draggedTaskId) startTransitionFinishCb.onTransitionFinished(/* wct = */ null) clearState() interactionJankMonitor.end(CUJ_DESKTOP_MODE_ENTER_APP_HANDLE_DRAG_RELEASE) }) .start() } companion object { /** * The initial scale of the freeform tasks in the animation to commit the drag-to-desktop * gesture. */ private const val DRAG_TO_DESKTOP_FREEFORM_TASK_INITIAL_SCALE = 0.9f } } libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/MoveToDesktopAnimator.kt +12 −0 Original line number Diff line number Diff line Loading @@ -7,6 +7,7 @@ import android.graphics.PointF import android.graphics.Rect import android.view.MotionEvent import android.view.SurfaceControl import android.view.VelocityTracker import com.android.wm.shell.R /** Loading Loading @@ -34,6 +35,7 @@ class MoveToDesktopAnimator @JvmOverloads constructor( val scale: Float get() = dragToDesktopAnimator.animatedValue as Float private val mostRecentInput = PointF() private val velocityTracker = VelocityTracker.obtain() private val dragToDesktopAnimator: ValueAnimator = ValueAnimator.ofFloat(1f, DRAG_FREEFORM_SCALE) .setDuration(ANIMATION_DURATION.toLong()) Loading Loading @@ -90,6 +92,7 @@ class MoveToDesktopAnimator @JvmOverloads constructor( if (!allowSurfaceChangesOnMove || dragToDesktopAnimator.isRunning) { return } velocityTracker.addMovement(ev) setTaskPosition(ev.rawX, ev.rawY) val t = transactionFactory() t.setPosition(taskSurface, position.x, position.y) Loading @@ -109,6 +112,15 @@ class MoveToDesktopAnimator @JvmOverloads constructor( * Cancels the animation, intended to be used when another animator will take over. */ fun cancelAnimator() { velocityTracker.clear() dragToDesktopAnimator.cancel() } /** * Computes the current velocity per second based on the points that have been collected. */ fun computeCurrentVelocity(): PointF { velocityTracker.computeCurrentVelocity(/* units = */ 1000) return PointF(velocityTracker.xVelocity, velocityTracker.yVelocity) } } No newline at end of file libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandlerTest.kt +3 −2 Original line number Diff line number Diff line Loading @@ -304,7 +304,7 @@ class DragToDesktopTransitionHandlerTest : ShellTestCase() { val task = createTask() val startTransition = startDrag(defaultHandler, task, finishTransaction = playingFinishTransaction) defaultHandler.setOnTaskResizeAnimatorListener(mock()) defaultHandler.onTaskResizeAnimationListener = mock() defaultHandler.mergeAnimation( transition = mock(), Loading @@ -327,13 +327,14 @@ class DragToDesktopTransitionHandlerTest : ShellTestCase() { @Test fun mergeAnimation_endTransition_springHandler_hidesHome() { whenever(dragAnimator.computeCurrentVelocity()).thenReturn(PointF()) val playingFinishTransaction = mock<SurfaceControl.Transaction>() val mergedStartTransaction = mock<SurfaceControl.Transaction>() val finishCallback = mock<Transitions.TransitionFinishCallback>() val task = createTask() val startTransition = startDrag(springHandler, task, finishTransaction = playingFinishTransaction) springHandler.setOnTaskResizeAnimatorListener(mock()) springHandler.onTaskResizeAnimationListener = mock() springHandler.mergeAnimation( transition = mock(), Loading Loading
libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeVisualIndicator.java +13 −2 Original line number Diff line number Diff line Loading @@ -27,6 +27,7 @@ import android.animation.AnimatorListenerAdapter; import android.animation.RectEvaluator; import android.animation.ValueAnimator; import android.annotation.NonNull; import android.annotation.Nullable; import android.app.ActivityManager; import android.app.WindowConfiguration; import android.content.Context; Loading Loading @@ -262,12 +263,22 @@ public class DesktopModeVisualIndicator { /** * Fade out indicator without fully releasing it. Animator fades it out while shrinking bounds. * * @param finishCallback called when animation ends or gets cancelled */ private void fadeOutIndicator() { void fadeOutIndicator(@Nullable Runnable finishCallback) { final VisualIndicatorAnimator animator = VisualIndicatorAnimator .fadeBoundsOut(mView, mCurrentType, mDisplayController.getDisplayLayout(mTaskInfo.displayId)); animator.start(); if (finishCallback != null) { animator.addListener(new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator animation) { finishCallback.run(); } }); } mCurrentType = IndicatorType.NO_INDICATOR; } Loading @@ -282,7 +293,7 @@ public class DesktopModeVisualIndicator { if (mCurrentType == IndicatorType.NO_INDICATOR) { fadeInIndicator(newType); } else if (newType == IndicatorType.NO_INDICATOR) { fadeOutIndicator(); fadeOutIndicator(null /* finishCallback */); } else { final VisualIndicatorAnimator animator = VisualIndicatorAnimator.animateIndicatorType( mView, mDisplayController.getDisplayLayout(mTaskInfo.displayId), mCurrentType, Loading
libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt +6 −4 Original line number Diff line number Diff line Loading @@ -163,10 +163,12 @@ class DesktopTasksController( } private fun removeVisualIndicator(tx: SurfaceControl.Transaction) { visualIndicator?.fadeOutIndicator { visualIndicator?.releaseVisualIndicator(tx) visualIndicator = null } } } /** Task id of the task currently being dragged from fullscreen/split. */ val draggingTaskId Loading @@ -193,7 +195,7 @@ class DesktopTasksController( ) transitions.addHandler(this) taskRepository.addVisibleTasksListener(taskVisibilityListener, mainExecutor) dragToDesktopTransitionHandler.setDragToDesktopStateListener(dragToDesktopStateListener) dragToDesktopTransitionHandler.dragToDesktopStateListener = dragToDesktopStateListener recentsTransitionHandler.addTransitionStateListener( object : RecentsTransitionStateListener { override fun onAnimationStateChanged(running: Boolean) { Loading @@ -213,7 +215,7 @@ class DesktopTasksController( fun setOnTaskResizeAnimationListener(listener: OnTaskResizeAnimationListener) { toggleResizeDesktopTaskTransitionHandler.setOnTaskResizeAnimationListener(listener) enterDesktopTaskTransitionHandler.setOnTaskResizeAnimationListener(listener) dragToDesktopTransitionHandler.setOnTaskResizeAnimatorListener(listener) dragToDesktopTransitionHandler.onTaskResizeAnimationListener = listener } fun setOnTaskRepositionAnimationListener(listener: OnTaskRepositionAnimationListener) { Loading
libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandler.kt +143 −17 Original line number Diff line number Diff line Loading @@ -28,17 +28,20 @@ import android.window.TransitionInfo import android.window.TransitionInfo.Change import android.window.TransitionRequestInfo import android.window.WindowContainerTransaction import androidx.dynamicanimation.animation.SpringForce import com.android.internal.jank.Cuj.CUJ_DESKTOP_MODE_ENTER_APP_HANDLE_DRAG_HOLD import com.android.internal.jank.Cuj.CUJ_DESKTOP_MODE_ENTER_APP_HANDLE_DRAG_RELEASE import com.android.internal.jank.InteractionJankMonitor import com.android.internal.protolog.ProtoLog import com.android.wm.shell.RootTaskDisplayAreaOrganizer import com.android.wm.shell.animation.FloatProperties import com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_BOTTOM_OR_RIGHT import com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_TOP_OR_LEFT import com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_UNDEFINED import com.android.wm.shell.common.split.SplitScreenConstants.SplitPosition import com.android.wm.shell.protolog.ShellProtoLogGroup import com.android.wm.shell.shared.TransitionUtil import com.android.wm.shell.shared.animation.PhysicsAnimator import com.android.wm.shell.splitscreen.SplitScreenController import com.android.wm.shell.transition.Transitions import com.android.wm.shell.transition.Transitions.TRANSIT_DESKTOP_MODE_CANCEL_DRAG_TO_DESKTOP Loading @@ -49,6 +52,7 @@ import com.android.wm.shell.windowdecor.MoveToDesktopAnimator import com.android.wm.shell.windowdecor.MoveToDesktopAnimator.Companion.DRAG_FREEFORM_SCALE import com.android.wm.shell.windowdecor.OnTaskResizeAnimationListener import java.util.function.Supplier import kotlin.math.max /** * Handles the transition to enter desktop from fullscreen by dragging on the handle bar. It also Loading @@ -64,17 +68,15 @@ sealed class DragToDesktopTransitionHandler( private val context: Context, private val transitions: Transitions, private val taskDisplayAreaOrganizer: RootTaskDisplayAreaOrganizer, private val interactionJankMonitor: InteractionJankMonitor, private val transactionSupplier: Supplier<SurfaceControl.Transaction>, protected val interactionJankMonitor: InteractionJankMonitor, protected val transactionSupplier: Supplier<SurfaceControl.Transaction>, ) : TransitionHandler { private val rectEvaluator = RectEvaluator(Rect()) protected val rectEvaluator = RectEvaluator(Rect()) private val launchHomeIntent = Intent(Intent.ACTION_MAIN).addCategory(Intent.CATEGORY_HOME) private var dragToDesktopStateListener: DragToDesktopStateListener? = null private lateinit var splitScreenController: SplitScreenController private var transitionState: TransitionState? = null private lateinit var onTaskResizeAnimationListener: OnTaskResizeAnimationListener /** Whether a drag-to-desktop transition is in progress. */ val inProgress: Boolean Loading @@ -83,20 +85,18 @@ sealed class DragToDesktopTransitionHandler( /** The task id of the task currently being dragged from fullscreen/split. */ val draggingTaskId: Int get() = transitionState?.draggedTaskId ?: INVALID_TASK_ID /** Sets a listener to receive callback about events during the transition animation. */ fun setDragToDesktopStateListener(listener: DragToDesktopStateListener) { dragToDesktopStateListener = listener } /** Listener to receive callback about events during the transition animation. */ var dragToDesktopStateListener: DragToDesktopStateListener? = null /** Task listener for animation start, task bounds resize, and the animation finish */ lateinit var onTaskResizeAnimationListener: OnTaskResizeAnimationListener /** Setter needed to avoid cyclic dependency. */ fun setSplitScreenController(controller: SplitScreenController) { splitScreenController = controller } fun setOnTaskResizeAnimatorListener(listener: OnTaskResizeAnimationListener) { onTaskResizeAnimationListener = listener } /** * Starts a transition that performs a transient launch of Home so that Home is brought to the * front while still keeping the currently focused task that is being dragged resumed. This Loading Loading @@ -503,6 +503,7 @@ sealed class DragToDesktopTransitionHandler( finishTransaction: SurfaceControl.Transaction ) { val state = requireTransitionState() val freeformTaskChanges = mutableListOf<Change>() info.changes.forEachIndexed { i, change -> when { state is TransitionState.FromSplit && Loading @@ -527,12 +528,15 @@ sealed class DragToDesktopTransitionHandler( ?: error("Expected dragged leash to be non-null") startTransaction.setRelativeLayer(change.leash, draggedTaskLeash, -i) finishTransaction.setRelativeLayer(change.leash, draggedTaskLeash, -i) freeformTaskChanges.add(change) } } } state.freeformTaskChanges = freeformTaskChanges } private fun animateEndDragToDesktop( protected open fun animateEndDragToDesktop( startTransaction: SurfaceControl.Transaction, startTransitionFinishCb: Transitions.TransitionFinishCallback ) { Loading Loading @@ -597,7 +601,7 @@ sealed class DragToDesktopTransitionHandler( object : AnimatorListenerAdapter() { override fun onAnimationEnd(animation: Animator) { onTaskResizeAnimationListener.onAnimationEnd(state.draggedTaskId) startTransitionFinishCb.onTransitionFinished(null /* null */) startTransitionFinishCb.onTransitionFinished(/* wct = */ null) clearState() interactionJankMonitor.end( CUJ_DESKTOP_MODE_ENTER_APP_HANDLE_DRAG_RELEASE Loading Loading @@ -728,7 +732,7 @@ sealed class DragToDesktopTransitionHandler( wct.restoreTransientOrder(homeWc) } private fun clearState() { protected fun clearState() { transitionState = null } Loading Loading @@ -778,6 +782,7 @@ sealed class DragToDesktopTransitionHandler( abstract var cancelTransitionToken: IBinder? abstract var homeChange: Change? abstract var draggedTaskChange: Change? abstract var freeformTaskChanges: List<Change> abstract var cancelState: CancelState abstract var startAborted: Boolean Loading @@ -790,6 +795,7 @@ sealed class DragToDesktopTransitionHandler( override var cancelTransitionToken: IBinder? = null, override var homeChange: Change? = null, override var draggedTaskChange: Change? = null, override var freeformTaskChanges: List<Change> = emptyList(), override var cancelState: CancelState = CancelState.NO_CANCEL, override var startAborted: Boolean = false, var otherRootChanges: MutableList<Change> = mutableListOf() Loading @@ -804,6 +810,7 @@ sealed class DragToDesktopTransitionHandler( override var cancelTransitionToken: IBinder? = null, override var homeChange: Change? = null, override var draggedTaskChange: Change? = null, override var freeformTaskChanges: List<Change> = emptyList(), override var cancelState: CancelState = CancelState.NO_CANCEL, override var startAborted: Boolean = false, var splitRootChange: Change? = null, Loading @@ -825,7 +832,7 @@ sealed class DragToDesktopTransitionHandler( companion object { /** The duration of the animation to commit or cancel the drag-to-desktop gesture. */ private const val DRAG_TO_DESKTOP_FINISH_ANIM_DURATION_MS = 336L internal const val DRAG_TO_DESKTOP_FINISH_ANIM_DURATION_MS = 336L } } Loading Loading @@ -885,6 +892,15 @@ constructor( transactionSupplier ) { private val positionSpringConfig = PhysicsAnimator.SpringConfig( SpringForce.STIFFNESS_LOW, SpringForce.DAMPING_RATIO_LOW_BOUNCY ) private val sizeSpringConfig = PhysicsAnimator.SpringConfig(SpringForce.STIFFNESS_LOW, SpringForce.DAMPING_RATIO_NO_BOUNCY) /** * @return layers in order: * - appLayers - below everything z < 0, effectively hides the leash Loading @@ -911,5 +927,115 @@ constructor( val homeLeash = state.homeChange?.leash ?: error("Expects home leash to be non-null") // Hide home on finish to prevent flickering when wallpaper activity flag is enabled finishTransaction.hide(homeLeash) // Setup freeform tasks before animation state.freeformTaskChanges.forEach { change -> val startScale = DRAG_TO_DESKTOP_FREEFORM_TASK_INITIAL_SCALE val startX = change.endAbsBounds.left + change.endAbsBounds.width() * (1 - startScale) / 2 val startY = change.endAbsBounds.top + change.endAbsBounds.height() * (1 - startScale) / 2 startTransaction.setPosition(change.leash, startX, startY) startTransaction.setScale(change.leash, startScale, startScale) startTransaction.setAlpha(change.leash, 0f) } } override fun animateEndDragToDesktop( startTransaction: SurfaceControl.Transaction, startTransitionFinishCb: Transitions.TransitionFinishCallback ) { val state = requireTransitionState() val draggedTaskChange = state.draggedTaskChange ?: error("Expected non-null change of dragged task") val draggedTaskLeash = draggedTaskChange.leash val freeformTaskChanges = state.freeformTaskChanges val startBounds = draggedTaskChange.startAbsBounds val endBounds = draggedTaskChange.endAbsBounds val currentVelocity = state.dragAnimator.computeCurrentVelocity() // Cancel any animation that may be currently playing; we will use the relevant // details of that animation here. state.dragAnimator.cancelAnimator() // We still apply scale to task bounds; as we animate the bounds to their // end value, animate scale to 1. val startScale = state.dragAnimator.scale val startPosition = state.dragAnimator.position val startBoundsWithOffset = Rect(startBounds).apply { offset(startPosition.x.toInt(), startPosition.y.toInt()) } dragToDesktopStateListener?.onCommitToDesktopAnimationStart(startTransaction) // Accept the merge by applying the merging transaction (applied by #showResizeVeil) // and finish callback. Show the veil and position the task at the first frame before // starting the final animation. onTaskResizeAnimationListener.onAnimationStart( state.draggedTaskId, startTransaction, startBoundsWithOffset ) val tx: SurfaceControl.Transaction = transactionSupplier.get() PhysicsAnimator.getInstance(startBoundsWithOffset) .spring( FloatProperties.RECT_X, endBounds.left.toFloat(), currentVelocity.x, positionSpringConfig ) .spring( FloatProperties.RECT_Y, endBounds.top.toFloat(), currentVelocity.y, positionSpringConfig ) .spring(FloatProperties.RECT_WIDTH, endBounds.width().toFloat(), sizeSpringConfig) .spring(FloatProperties.RECT_HEIGHT, endBounds.height().toFloat(), sizeSpringConfig) .addUpdateListener { animBounds, _ -> val animFraction = (animBounds.width() - startBounds.width()).toFloat() / (endBounds.width() - startBounds.width()) val animScale = startScale + animFraction * (1 - startScale) // Freeform animation starts 50% in the animation val freeformAnimFraction = max(animFraction - 0.5f, 0f) * 2f val freeformStartScale = DRAG_TO_DESKTOP_FREEFORM_TASK_INITIAL_SCALE val freeformAnimScale = freeformStartScale + freeformAnimFraction * (1 - freeformStartScale) tx.apply { // Update dragged task setScale(draggedTaskLeash, animScale, animScale) setPosition( draggedTaskLeash, animBounds.left.toFloat(), animBounds.top.toFloat() ) // Update freeform tasks freeformTaskChanges.forEach { val startX = it.endAbsBounds.left + it.endAbsBounds.width() * (1 - freeformAnimScale) / 2 val startY = it.endAbsBounds.top + it.endAbsBounds.height() * (1 - freeformAnimScale) / 2 setPosition(it.leash, startX, startY) setScale(it.leash, freeformAnimScale, freeformAnimScale) setAlpha(it.leash, freeformAnimFraction) } } onTaskResizeAnimationListener.onBoundsChange(state.draggedTaskId, tx, animBounds) } .withEndActions({ onTaskResizeAnimationListener.onAnimationEnd(state.draggedTaskId) startTransitionFinishCb.onTransitionFinished(/* wct = */ null) clearState() interactionJankMonitor.end(CUJ_DESKTOP_MODE_ENTER_APP_HANDLE_DRAG_RELEASE) }) .start() } companion object { /** * The initial scale of the freeform tasks in the animation to commit the drag-to-desktop * gesture. */ private const val DRAG_TO_DESKTOP_FREEFORM_TASK_INITIAL_SCALE = 0.9f } }
libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/MoveToDesktopAnimator.kt +12 −0 Original line number Diff line number Diff line Loading @@ -7,6 +7,7 @@ import android.graphics.PointF import android.graphics.Rect import android.view.MotionEvent import android.view.SurfaceControl import android.view.VelocityTracker import com.android.wm.shell.R /** Loading Loading @@ -34,6 +35,7 @@ class MoveToDesktopAnimator @JvmOverloads constructor( val scale: Float get() = dragToDesktopAnimator.animatedValue as Float private val mostRecentInput = PointF() private val velocityTracker = VelocityTracker.obtain() private val dragToDesktopAnimator: ValueAnimator = ValueAnimator.ofFloat(1f, DRAG_FREEFORM_SCALE) .setDuration(ANIMATION_DURATION.toLong()) Loading Loading @@ -90,6 +92,7 @@ class MoveToDesktopAnimator @JvmOverloads constructor( if (!allowSurfaceChangesOnMove || dragToDesktopAnimator.isRunning) { return } velocityTracker.addMovement(ev) setTaskPosition(ev.rawX, ev.rawY) val t = transactionFactory() t.setPosition(taskSurface, position.x, position.y) Loading @@ -109,6 +112,15 @@ class MoveToDesktopAnimator @JvmOverloads constructor( * Cancels the animation, intended to be used when another animator will take over. */ fun cancelAnimator() { velocityTracker.clear() dragToDesktopAnimator.cancel() } /** * Computes the current velocity per second based on the points that have been collected. */ fun computeCurrentVelocity(): PointF { velocityTracker.computeCurrentVelocity(/* units = */ 1000) return PointF(velocityTracker.xVelocity, velocityTracker.yVelocity) } } No newline at end of file
libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandlerTest.kt +3 −2 Original line number Diff line number Diff line Loading @@ -304,7 +304,7 @@ class DragToDesktopTransitionHandlerTest : ShellTestCase() { val task = createTask() val startTransition = startDrag(defaultHandler, task, finishTransaction = playingFinishTransaction) defaultHandler.setOnTaskResizeAnimatorListener(mock()) defaultHandler.onTaskResizeAnimationListener = mock() defaultHandler.mergeAnimation( transition = mock(), Loading @@ -327,13 +327,14 @@ class DragToDesktopTransitionHandlerTest : ShellTestCase() { @Test fun mergeAnimation_endTransition_springHandler_hidesHome() { whenever(dragAnimator.computeCurrentVelocity()).thenReturn(PointF()) val playingFinishTransaction = mock<SurfaceControl.Transaction>() val mergedStartTransaction = mock<SurfaceControl.Transaction>() val finishCallback = mock<Transitions.TransitionFinishCallback>() val task = createTask() val startTransition = startDrag(springHandler, task, finishTransaction = playingFinishTransaction) springHandler.setOnTaskResizeAnimatorListener(mock()) springHandler.onTaskResizeAnimationListener = mock() springHandler.mergeAnimation( transition = mock(), Loading