1 /*
<lambda>null2  * Copyright (C) 2023 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.wm.shell.desktopmode
18 
19 import android.animation.Animator
20 import android.animation.RectEvaluator
21 import android.animation.ValueAnimator
22 import android.graphics.Rect
23 import android.os.IBinder
24 import android.view.SurfaceControl
25 import android.view.WindowManager.TRANSIT_CHANGE
26 import android.window.TransitionInfo
27 import android.window.TransitionRequestInfo
28 import android.window.WindowContainerTransaction
29 import androidx.core.animation.addListener
30 import com.android.wm.shell.transition.Transitions
31 import com.android.wm.shell.transition.Transitions.TRANSIT_DESKTOP_MODE_TOGGLE_RESIZE
32 import com.android.wm.shell.windowdecor.OnTaskResizeAnimationListener
33 import java.util.function.Supplier
34 
35 /** Handles the animation of quick resizing of desktop tasks. */
36 class ToggleResizeDesktopTaskTransitionHandler(
37     private val transitions: Transitions,
38     private val transactionSupplier: Supplier<SurfaceControl.Transaction>
39 ) : Transitions.TransitionHandler {
40 
41     private val rectEvaluator = RectEvaluator(Rect())
42     private lateinit var onTaskResizeAnimationListener: OnTaskResizeAnimationListener
43 
44     private var boundsAnimator: Animator? = null
45 
46     constructor(
47         transitions: Transitions
48     ) : this(transitions, Supplier { SurfaceControl.Transaction() })
49 
50     /** Starts a quick resize transition. */
51     fun startTransition(wct: WindowContainerTransaction) {
52         transitions.startTransition(TRANSIT_DESKTOP_MODE_TOGGLE_RESIZE, wct, this)
53     }
54 
55     fun setOnTaskResizeAnimationListener(listener: OnTaskResizeAnimationListener) {
56         onTaskResizeAnimationListener = listener
57     }
58 
59     override fun startAnimation(
60         transition: IBinder,
61         info: TransitionInfo,
62         startTransaction: SurfaceControl.Transaction,
63         finishTransaction: SurfaceControl.Transaction,
64         finishCallback: Transitions.TransitionFinishCallback
65     ): Boolean {
66         val change = findRelevantChange(info)
67         val leash = change.leash
68         val taskId = checkNotNull(change.taskInfo).taskId
69         val startBounds = change.startAbsBounds
70         val endBounds = change.endAbsBounds
71 
72         val tx = transactionSupplier.get()
73         boundsAnimator?.cancel()
74         boundsAnimator =
75             ValueAnimator.ofObject(rectEvaluator, startBounds, endBounds)
76                 .setDuration(RESIZE_DURATION_MS)
77                 .apply {
78                     addListener(
79                         onStart = {
80                             startTransaction
81                                 .setPosition(
82                                     leash,
83                                     startBounds.left.toFloat(),
84                                     startBounds.top.toFloat()
85                                 )
86                                 .setWindowCrop(leash, startBounds.width(), startBounds.height())
87                                 .show(leash)
88                             onTaskResizeAnimationListener.onAnimationStart(
89                                 taskId,
90                                 startTransaction,
91                                 startBounds
92                             )
93                         },
94                         onEnd = {
95                             finishTransaction
96                                 .setPosition(
97                                     leash,
98                                     endBounds.left.toFloat(),
99                                     endBounds.top.toFloat()
100                                 )
101                                 .setWindowCrop(leash, endBounds.width(), endBounds.height())
102                                 .show(leash)
103                             onTaskResizeAnimationListener.onAnimationEnd(taskId)
104                             finishCallback.onTransitionFinished(null)
105                             boundsAnimator = null
106                         }
107                     )
108                     addUpdateListener { anim ->
109                         val rect = anim.animatedValue as Rect
110                         tx.setPosition(leash, rect.left.toFloat(), rect.top.toFloat())
111                             .setWindowCrop(leash, rect.width(), rect.height())
112                             .show(leash)
113                         onTaskResizeAnimationListener.onBoundsChange(taskId, tx, rect)
114                     }
115                     start()
116                 }
117         return true
118     }
119 
120     override fun handleRequest(
121         transition: IBinder,
122         request: TransitionRequestInfo
123     ): WindowContainerTransaction? {
124         return null
125     }
126 
127     private fun findRelevantChange(info: TransitionInfo): TransitionInfo.Change {
128         val matchingChanges =
129             info.changes.filter { c ->
130                 !isWallpaper(c) && isValidTaskChange(c) && c.mode == TRANSIT_CHANGE
131             }
132         if (matchingChanges.size != 1) {
133             throw IllegalStateException(
134                 "Expected 1 relevant change but found: ${matchingChanges.size}"
135             )
136         }
137         return matchingChanges.first()
138     }
139 
140     private fun isWallpaper(change: TransitionInfo.Change): Boolean {
141         return (change.flags and TransitionInfo.FLAG_IS_WALLPAPER) != 0
142     }
143 
144     private fun isValidTaskChange(change: TransitionInfo.Change): Boolean {
145         return change.taskInfo != null && change.taskInfo?.taskId != -1
146     }
147 
148     companion object {
149         private const val RESIZE_DURATION_MS = 300L
150     }
151 }
152