• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
<lambda>null2  * Copyright (C) 2025 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.launcher3.desktop
18 
19 import android.animation.Animator
20 import android.animation.AnimatorSet
21 import android.animation.ValueAnimator
22 import android.content.Context
23 import android.graphics.Rect
24 import android.util.Log
25 import android.view.Choreographer
26 import android.view.SurfaceControl.Transaction
27 import android.view.WindowManager.TRANSIT_CLOSE
28 import android.view.WindowManager.TRANSIT_OPEN
29 import android.view.WindowManager.TRANSIT_TO_BACK
30 import android.window.DesktopModeFlags
31 import android.window.TransitionInfo
32 import android.window.TransitionInfo.Change
33 import androidx.core.animation.addListener
34 import androidx.core.util.Supplier
35 import com.android.app.animation.Interpolators
36 import com.android.internal.jank.Cuj
37 import com.android.internal.jank.InteractionJankMonitor
38 import com.android.internal.policy.ScreenDecorationsUtils
39 import com.android.launcher3.desktop.DesktopAppLaunchTransition.AppLaunchType
40 import com.android.launcher3.desktop.DesktopAppLaunchTransition.Companion.LAUNCH_CHANGE_MODES
41 import com.android.wm.shell.shared.animation.MinimizeAnimator
42 import com.android.wm.shell.shared.animation.WindowAnimator
43 
44 /**
45  * Helper class responsible for creating and managing animators for desktop app launch and related
46  * transitions.
47  *
48  * <p>This class handles the complex logic of creating various animators, including launch,
49  * minimize, and trampoline close animations, based on the provided transition information and
50  * launch type. It also utilizes {@link InteractionJankMonitor} to monitor animation jank.
51  *
52  * @param context The application context.
53  * @param launchType The type of app launch, containing animation parameters.
54  * @param cujType The CUJ (Critical User Journey) type for jank monitoring.
55  */
56 class DesktopAppLaunchAnimatorHelper(
57     private val context: Context,
58     private val launchType: AppLaunchType,
59     @Cuj.CujType private val cujType: Int,
60     private val transactionSupplier: Supplier<Transaction>,
61 ) {
62 
63     private val interactionJankMonitor = InteractionJankMonitor.getInstance()
64 
65     fun createAnimators(info: TransitionInfo, finishCallback: (Animator) -> Unit): List<Animator> {
66         val launchChange = getLaunchChange(info)
67         if (launchChange == null) {
68             val tasksInfo =
69                 info.changes.joinToString(", ") { change ->
70                     "${change.taskInfo?.taskId}:${change.taskInfo?.isFreeform}"
71                 }
72             Log.e(TAG, "No launch change found: Transition info=$info, tasks state=$tasksInfo")
73             return emptyList()
74         }
75 
76         val transaction = transactionSupplier.get()
77 
78         val minimizeChange = getMinimizeChange(info)
79         val trampolineCloseChange = getTrampolineCloseChange(info)
80 
81         val launchAnimator =
82             createLaunchAnimator(
83                 launchChange,
84                 transaction,
85                 finishCallback,
86                 isTrampoline = trampolineCloseChange != null,
87             )
88         val animatorsList = mutableListOf(launchAnimator)
89         if (minimizeChange != null) {
90             val minimizeAnimator =
91                 createMinimizeAnimator(minimizeChange, transaction, finishCallback)
92             animatorsList.add(minimizeAnimator)
93         }
94         if (trampolineCloseChange != null) {
95             val trampolineCloseAnimator =
96                 createTrampolineCloseAnimator(trampolineCloseChange, transaction, finishCallback)
97             animatorsList.add(trampolineCloseAnimator)
98         }
99         return animatorsList
100     }
101 
102     private fun getLaunchChange(info: TransitionInfo): Change? =
103         info.changes.firstOrNull { change ->
104             change.mode in LAUNCH_CHANGE_MODES && change.taskInfo?.isFreeform == true
105         }
106 
107     private fun getMinimizeChange(info: TransitionInfo): Change? =
108         info.changes.firstOrNull { change ->
109             change.mode == TRANSIT_TO_BACK && change.taskInfo?.isFreeform == true
110         }
111 
112     private fun getTrampolineCloseChange(info: TransitionInfo): Change? {
113         if (
114             info.changes.size < 2 ||
115             !DesktopModeFlags.ENABLE_DESKTOP_TRAMPOLINE_CLOSE_ANIMATION_BUGFIX.isTrue
116         ) {
117             return null
118         }
119         val openChange =
120             info.changes.firstOrNull { change ->
121                 change.mode == TRANSIT_OPEN && change.taskInfo?.isFreeform == true
122             }
123         val closeChange =
124             info.changes.firstOrNull { change ->
125                 change.mode == TRANSIT_CLOSE && change.taskInfo?.isFreeform == true
126             }
127         val openPackage = openChange?.taskInfo?.baseIntent?.component?.packageName
128         val closePackage = closeChange?.taskInfo?.baseIntent?.component?.packageName
129         return if (openPackage != null && closePackage != null && openPackage == closePackage) {
130             closeChange
131         } else {
132             null
133         }
134     }
135 
136     private fun createLaunchAnimator(
137         change: Change,
138         transaction: Transaction,
139         onAnimFinish: (Animator) -> Unit,
140         isTrampoline: Boolean,
141     ): Animator {
142         val boundsAnimator =
143             WindowAnimator.createBoundsAnimator(
144                 context.resources.displayMetrics,
145                 launchType.boundsAnimationParams,
146                 change,
147                 transaction,
148             )
149         val alphaAnimator =
150             ValueAnimator.ofFloat(0f, 1f).apply {
151                 duration = launchType.alphaDurationMs
152                 interpolator = Interpolators.LINEAR
153                 addUpdateListener { animation ->
154                     transaction
155                         .setAlpha(change.leash, animation.animatedValue as Float)
156                         .setFrameTimeline(Choreographer.getInstance().vsyncId)
157                         .apply()
158                 }
159             }
160         val clipRect = Rect(change.endAbsBounds).apply { offsetTo(0, 0) }
161         transaction.setCrop(change.leash, clipRect)
162         transaction.setCornerRadius(
163             change.leash,
164             ScreenDecorationsUtils.getWindowCornerRadius(context),
165         )
166         return AnimatorSet().apply {
167             interactionJankMonitor.begin(change.leash, context, context.mainThreadHandler, cujType)
168             if (isTrampoline) {
169                 play(alphaAnimator)
170             } else {
171                 playTogether(boundsAnimator, alphaAnimator)
172             }
173             addListener(
174                 onEnd = { animation ->
175                     onAnimFinish(animation)
176                     interactionJankMonitor.end(cujType)
177                 }
178             )
179         }
180     }
181 
182     private fun createMinimizeAnimator(
183         change: Change,
184         transaction: Transaction,
185         onAnimFinish: (Animator) -> Unit,
186     ): Animator {
187         return MinimizeAnimator.create(
188             context,
189             change,
190             transaction,
191             onAnimFinish,
192             interactionJankMonitor,
193             context.mainThreadHandler,
194         )
195     }
196 
197     private fun createTrampolineCloseAnimator(
198         change: Change,
199         transaction: Transaction,
200         onAnimFinish: (Animator) -> Unit,
201     ): Animator {
202         return ValueAnimator.ofFloat(1f, 0f).apply {
203             duration = 100L
204             interpolator = Interpolators.LINEAR
205             addUpdateListener { animation ->
206                 transaction.setAlpha(change.leash, animation.animatedValue as Float).apply()
207             }
208             addListener(
209                 onEnd = { animation ->
210                     onAnimFinish(animation)
211                 }
212             )
213         }
214     }
215 
216     private companion object {
217         const val TAG = "DesktopAppLaunchAnimatorHelper"
218     }
219 }
220