• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 }