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 }