1 package com.android.wm.shell.windowdecor 2 3 import android.animation.ValueAnimator 4 import android.app.ActivityManager.RunningTaskInfo 5 import android.content.Context 6 import android.graphics.PointF 7 import android.graphics.Rect 8 import android.view.Choreographer 9 import android.view.MotionEvent 10 import android.view.SurfaceControl 11 import android.view.VelocityTracker 12 import com.android.wm.shell.R 13 14 /** 15 * Creates an animator to shrink and position task after a user drags a fullscreen task from 16 * the top of the screen to transition it into freeform and before the user releases the task. The 17 * MoveToDesktopAnimator object also holds information about the state of the task that are 18 * accessed by the EnterDesktopTaskTransitionHandler. 19 */ 20 class MoveToDesktopAnimator @JvmOverloads constructor( 21 private val context: Context, 22 private val startBounds: Rect, 23 private val taskInfo: RunningTaskInfo, 24 private val taskSurface: SurfaceControl, 25 private val transactionFactory: () -> SurfaceControl.Transaction = 26 SurfaceControl::Transaction 27 ) { 28 companion object { 29 // The size of the screen during drag relative to the fullscreen size 30 const val DRAG_FREEFORM_SCALE: Float = 0.4f 31 const val ANIMATION_DURATION = 336 32 } 33 34 private val animatedTaskWidth 35 get() = dragToDesktopAnimator.animatedValue as Float * startBounds.width() 36 val scale: Float 37 get() = dragToDesktopAnimator.animatedValue as Float 38 private val mostRecentInput = PointF() 39 private val velocityTracker = VelocityTracker.obtain() 40 private val dragToDesktopAnimator: ValueAnimator = ValueAnimator.ofFloat(1f, 41 DRAG_FREEFORM_SCALE) 42 .setDuration(ANIMATION_DURATION.toLong()) <lambda>null43 .apply { 44 val t = SurfaceControl.Transaction() 45 addUpdateListener { 46 setTaskPosition(mostRecentInput.x, mostRecentInput.y) 47 t.setScale(taskSurface, scale, scale) 48 .setCornerRadius(taskSurface, cornerRadius) 49 .setScale(taskSurface, scale, scale) 50 .setFrameTimeline(Choreographer.getInstance().vsyncId) 51 .setPosition(taskSurface, position.x, position.y) 52 .apply() 53 } 54 } 55 56 val taskId get() = taskInfo.taskId 57 val position: PointF = PointF(0.0f, 0.0f) 58 val cornerRadius: Float = context.resources 59 .getDimensionPixelSize(R.dimen.desktop_mode_dragged_task_radius).toFloat() 60 61 /** 62 * Whether motion events from the drag gesture should affect the dragged surface or not. Used 63 * to disallow moving the surface's position prematurely since it should not start moving at 64 * all until the drag-to-desktop transition is ready to animate and the wallpaper/home are 65 * ready to be revealed behind the dragged/scaled task. 66 */ 67 private var allowSurfaceChangesOnMove = false 68 69 /** 70 * Starts the animation that scales the task down. 71 */ startAnimationnull72 fun startAnimation() { 73 allowSurfaceChangesOnMove = true 74 dragToDesktopAnimator.start() 75 } 76 77 /** 78 * Uses the position of the motion event of the drag-to-desktop gesture to update the dragged 79 * task's position on screen to follow the touch point. Note that the position change won't 80 * be applied immediately always, such as near the beginning where it waits until the wallpaper 81 * or home are visible behind it. Once they're visible the surface will catch-up to the most 82 * recent touch position. 83 */ updatePositionnull84 fun updatePosition(ev: MotionEvent) { 85 // Using rawX/Y because when dragging a task in split, the local X/Y is relative to the 86 // split stages, but the split task surface is re-parented to the task display area to 87 // allow dragging beyond its stage across any region of the display. Because of that, the 88 // rawX/Y are more true to where the gesture is on screen and where the surface should be 89 // positioned. 90 mostRecentInput.set(ev.rawX, ev.rawY) 91 92 // If animator is running, allow it to set scale and position at the same time. 93 if (!allowSurfaceChangesOnMove || dragToDesktopAnimator.isRunning) { 94 return 95 } 96 velocityTracker.addMovement(ev) 97 setTaskPosition(ev.rawX, ev.rawY) 98 val t = transactionFactory() 99 t.setPosition(taskSurface, position.x, position.y) 100 t.setFrameTimeline(Choreographer.getInstance().vsyncId) 101 t.apply() 102 } 103 104 /** 105 * Calculates the top left corner of task from input coordinates. 106 * Top left will be needed for the resulting surface control transaction. 107 */ setTaskPositionnull108 private fun setTaskPosition(x: Float, y: Float) { 109 position.x = x - animatedTaskWidth / 2 110 position.y = y 111 } 112 113 /** 114 * Cancels the animation, intended to be used when another animator will take over. 115 */ cancelAnimatornull116 fun cancelAnimator() { 117 velocityTracker.clear() 118 dragToDesktopAnimator.cancel() 119 } 120 121 /** 122 * Computes the current velocity per second based on the points that have been collected. 123 */ computeCurrentVelocitynull124 fun computeCurrentVelocity(): PointF { 125 velocityTracker.computeCurrentVelocity(/* units = */ 1000) 126 return PointF(velocityTracker.xVelocity, velocityTracker.yVelocity) 127 } 128 }