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