• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
<lambda>null2  * Copyright (C) 2024 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.AnimatorSet
21 import android.animation.RectEvaluator
22 import android.animation.ValueAnimator
23 import android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM
24 import android.content.Context
25 import android.graphics.Rect
26 import android.os.Handler
27 import android.os.IBinder
28 import android.util.TypedValue
29 import android.view.Choreographer
30 import android.view.SurfaceControl.Transaction
31 import android.view.WindowManager
32 import android.window.TransitionInfo
33 import android.window.TransitionRequestInfo
34 import android.window.WindowContainerTransaction
35 import androidx.core.animation.addListener
36 import com.android.app.animation.Interpolators
37 import com.android.internal.jank.Cuj.CUJ_DESKTOP_MODE_CLOSE_TASK
38 import com.android.internal.jank.InteractionJankMonitor
39 import com.android.wm.shell.common.ShellExecutor
40 import com.android.wm.shell.transition.Transitions
41 import java.util.function.Supplier
42 
43 /** The [Transitions.TransitionHandler] that handles transitions for closing desktop mode tasks. */
44 class CloseDesktopTaskTransitionHandler
45 @JvmOverloads
46 constructor(
47     private val context: Context,
48     private val mainExecutor: ShellExecutor,
49     private val animExecutor: ShellExecutor,
50     private val transactionSupplier: Supplier<Transaction> = Supplier { Transaction() },
51     private val animHandler: Handler,
52 ) : Transitions.TransitionHandler {
53 
54     private val runningAnimations = mutableMapOf<IBinder, List<Animator>>()
55     private val interactionJankMonitor = InteractionJankMonitor.getInstance()
56 
57     /** Returns null, as it only handles transitions started from Shell. */
handleRequestnull58     override fun handleRequest(
59         transition: IBinder,
60         request: TransitionRequestInfo,
61     ): WindowContainerTransaction? = null
62 
63     override fun startAnimation(
64         transition: IBinder,
65         info: TransitionInfo,
66         startTransaction: Transaction,
67         finishTransaction: Transaction,
68         finishCallback: Transitions.TransitionFinishCallback,
69     ): Boolean {
70         if (info.type != WindowManager.TRANSIT_CLOSE) return false
71         val animations = mutableListOf<Animator>()
72         val onAnimFinish: (Animator) -> Unit = { animator ->
73             mainExecutor.execute {
74                 // Animation completed
75                 animations.remove(animator)
76                 if (animations.isEmpty()) {
77                     // All animations completed, finish the transition
78                     runningAnimations.remove(transition)
79                     finishCallback.onTransitionFinished(/* wct= */ null)
80                     interactionJankMonitor.end(CUJ_DESKTOP_MODE_CLOSE_TASK)
81                 }
82             }
83         }
84         val closingChanges =
85             info.changes.filter {
86                 it.mode == WindowManager.TRANSIT_CLOSE &&
87                     it.taskInfo?.windowingMode == WINDOWING_MODE_FREEFORM
88             }
89         animations +=
90             closingChanges.map { createCloseAnimation(it, finishTransaction, onAnimFinish) }
91         if (animations.isEmpty()) return false
92         runningAnimations[transition] = animations
93         closingChanges.lastOrNull()?.leash?.let { lastChangeLeash ->
94             interactionJankMonitor.begin(
95                 lastChangeLeash,
96                 context,
97                 animHandler,
98                 CUJ_DESKTOP_MODE_CLOSE_TASK,
99             )
100         }
101         animExecutor.execute { animations.forEach(Animator::start) }
102         return true
103     }
104 
createCloseAnimationnull105     private fun createCloseAnimation(
106         change: TransitionInfo.Change,
107         finishTransaction: Transaction,
108         onAnimFinish: (Animator) -> Unit,
109     ): Animator {
110         finishTransaction.hide(change.leash)
111         return AnimatorSet().apply {
112             playTogether(createBoundsCloseAnimation(change), createAlphaCloseAnimation(change))
113             addListener(onEnd = onAnimFinish)
114         }
115     }
116 
createBoundsCloseAnimationnull117     private fun createBoundsCloseAnimation(change: TransitionInfo.Change): Animator {
118         val startBounds = change.startAbsBounds
119         val endBounds =
120             Rect(startBounds).apply {
121                 // Scale the end bounds of the window down with an anchor in the center
122                 inset(
123                     (startBounds.width().toFloat() * (1 - CLOSE_ANIM_SCALE) / 2).toInt(),
124                     (startBounds.height().toFloat() * (1 - CLOSE_ANIM_SCALE) / 2).toInt(),
125                 )
126                 val offsetY =
127                     TypedValue.applyDimension(
128                             TypedValue.COMPLEX_UNIT_DIP,
129                             CLOSE_ANIM_OFFSET_Y,
130                             context.resources.displayMetrics,
131                         )
132                         .toInt()
133                 offset(/* dx= */ 0, offsetY)
134             }
135         return ValueAnimator.ofObject(RectEvaluator(), startBounds, endBounds).apply {
136             duration = CLOSE_ANIM_DURATION_BOUNDS
137             interpolator = Interpolators.STANDARD_ACCELERATE
138             addUpdateListener { animation ->
139                 val animBounds = animation.animatedValue as Rect
140                 val animScale = 1 - (1 - CLOSE_ANIM_SCALE) * animation.animatedFraction
141                 transactionSupplier
142                     .get()
143                     .setPosition(change.leash, animBounds.left.toFloat(), animBounds.top.toFloat())
144                     .setScale(change.leash, animScale, animScale)
145                     .setFrameTimeline(Choreographer.getInstance().vsyncId)
146                     .apply()
147             }
148         }
149     }
150 
createAlphaCloseAnimationnull151     private fun createAlphaCloseAnimation(change: TransitionInfo.Change): Animator =
152         ValueAnimator.ofFloat(1f, 0f).apply {
153             duration = CLOSE_ANIM_DURATION_ALPHA
154             interpolator = Interpolators.LINEAR
155             addUpdateListener { animation ->
156                 transactionSupplier
157                     .get()
158                     .setAlpha(change.leash, animation.animatedValue as Float)
159                     .apply()
160             }
161         }
162 
163     private companion object {
164         const val CLOSE_ANIM_DURATION_BOUNDS = 200L
165         const val CLOSE_ANIM_DURATION_ALPHA = 100L
166         const val CLOSE_ANIM_SCALE = 0.95f
167         const val CLOSE_ANIM_OFFSET_Y = 36.0f
168     }
169 }
170