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 package com.android.wm.shell.back
17
18 import android.content.Context
19 import android.graphics.Rect
20 import android.graphics.RectF
21 import android.util.MathUtils
22 import android.view.SurfaceControl
23 import android.view.animation.Animation
24 import android.view.animation.Transformation
25 import android.window.BackEvent
26 import android.window.BackMotionEvent
27 import android.window.BackNavigationInfo
28 import com.android.internal.R
29 import com.android.internal.policy.TransitionAnimation
30 import com.android.internal.protolog.common.ProtoLog
31 import com.android.wm.shell.RootTaskDisplayAreaOrganizer
32 import com.android.wm.shell.protolog.ShellProtoLogGroup
33 import javax.inject.Inject
34 import kotlin.math.max
35 import kotlin.math.min
36
37 /** Class that handles customized predictive cross activity back animations. */
38 class CustomCrossActivityBackAnimation(
39 context: Context,
40 background: BackAnimationBackground,
41 rootTaskDisplayAreaOrganizer: RootTaskDisplayAreaOrganizer,
42 transaction: SurfaceControl.Transaction,
43 private val customAnimationLoader: CustomAnimationLoader
44 ) :
45 CrossActivityBackAnimation(
46 context,
47 background,
48 rootTaskDisplayAreaOrganizer,
49 transaction
50 ) {
51
52 private var enterAnimation: Animation? = null
53 private var closeAnimation: Animation? = null
54 private val transformation = Transformation()
55
56 override val allowEnteringYShift = false
57
58 @Inject
59 constructor(
60 context: Context,
61 background: BackAnimationBackground,
62 rootTaskDisplayAreaOrganizer: RootTaskDisplayAreaOrganizer
63 ) : this(
64 context,
65 background,
66 rootTaskDisplayAreaOrganizer,
67 SurfaceControl.Transaction(),
68 CustomAnimationLoader(
69 TransitionAnimation(context, false /* debug */, "CustomCrossActivityBackAnimation")
70 )
71 )
72
73 override fun preparePreCommitClosingRectMovement(swipeEdge: Int) {
74 startClosingRect.set(backAnimRect)
75
76 // scale closing target to the left for right-hand-swipe and to the right for
77 // left-hand-swipe
78 targetClosingRect.set(startClosingRect)
79 targetClosingRect.scaleCentered(MAX_SCALE)
80 val offset = if (swipeEdge != BackEvent.EDGE_RIGHT) {
81 startClosingRect.right - targetClosingRect.right - displayBoundsMargin
82 } else {
83 -targetClosingRect.left + displayBoundsMargin
84 }
85 targetClosingRect.offset(offset, 0f)
86 }
87
88 override fun preparePreCommitEnteringRectMovement() {
89 // No movement for the entering rect
90 startEnteringRect.set(startClosingRect)
91 targetEnteringRect.set(startClosingRect)
92 }
93
94 override fun getPostCommitAnimationDuration(): Long {
95 return min(
96 MAX_POST_COMMIT_ANIM_DURATION, max(closeAnimation!!.duration, enterAnimation!!.duration)
97 )
98 }
99
100 override fun getPreCommitEnteringBaseTransformation(progress: Float): Transformation {
101 transformation.clear()
102 enterAnimation!!.getTransformationAt(progress * PRE_COMMIT_MAX_PROGRESS, transformation)
103 return transformation
104 }
105
106 override fun startBackAnimation(backMotionEvent: BackMotionEvent) {
107 super.startBackAnimation(backMotionEvent)
108 if (
109 closeAnimation == null ||
110 enterAnimation == null ||
111 closingTarget == null ||
112 enteringTarget == null
113 ) {
114 ProtoLog.d(
115 ShellProtoLogGroup.WM_SHELL_BACK_PREVIEW,
116 "Enter animation or close animation is null."
117 )
118 return
119 }
120 initializeAnimation(closeAnimation!!, closingTarget!!.localBounds)
121 initializeAnimation(enterAnimation!!, enteringTarget!!.localBounds)
122 }
123
124 override fun onPostCommitProgress(linearProgress: Float) {
125 super.onPostCommitProgress(linearProgress)
126 if (closingTarget == null || enteringTarget == null) return
127
128 val closingProgress = closeAnimation!!.getPostCommitProgress(linearProgress)
129 applyTransform(
130 closingTarget!!.leash,
131 currentClosingRect,
132 closingProgress,
133 closeAnimation!!,
134 FlingMode.FLING_SHRINK
135 )
136 val enteringProgress = MathUtils.lerp(
137 gestureProgress * PRE_COMMIT_MAX_PROGRESS,
138 1f,
139 enterAnimation!!.getPostCommitProgress(linearProgress)
140 )
141 applyTransform(
142 enteringTarget!!.leash,
143 currentEnteringRect,
144 enteringProgress,
145 enterAnimation!!,
146 FlingMode.NO_FLING
147 )
148 applyTransaction()
149 }
150
151 private fun applyTransform(
152 leash: SurfaceControl,
153 rect: RectF,
154 progress: Float,
155 animation: Animation,
156 flingMode: FlingMode
157 ) {
158 transformation.clear()
159 animation.getTransformationAt(progress, transformation)
160 applyTransform(leash, rect, transformation.alpha, transformation, flingMode)
161 }
162
163 override fun finishAnimation() {
164 closeAnimation?.reset()
165 closeAnimation = null
166 enterAnimation?.reset()
167 enterAnimation = null
168 transformation.clear()
169 super.finishAnimation()
170 }
171
172 /** Load customize animation before animation start. */
173 override fun prepareNextAnimation(
174 animationInfo: BackNavigationInfo.CustomAnimationInfo?,
175 letterboxColor: Int
176 ): Boolean {
177 super.prepareNextAnimation(animationInfo, letterboxColor)
178 if (animationInfo == null) return false
179 customAnimationLoader.loadAll(animationInfo)?.let { result ->
180 closeAnimation = result.closeAnimation
181 enterAnimation = result.enterAnimation
182 customizedBackgroundColor = result.backgroundColor
183 return true
184 }
185 return false
186 }
187
188 private fun Animation.getPostCommitProgress(linearProgress: Float): Float {
189 return when (duration) {
190 0L -> 1f
191 else -> min(
192 1f,
193 getPostCommitAnimationDuration() / min(
194 MAX_POST_COMMIT_ANIM_DURATION,
195 duration
196 ).toFloat() * linearProgress
197 )
198 }
199 }
200
201 class AnimationLoadResult {
202 var closeAnimation: Animation? = null
203 var enterAnimation: Animation? = null
204 var backgroundColor = 0
205 }
206
207 companion object {
208 private const val PRE_COMMIT_MAX_PROGRESS = 0.2f
209 private const val MAX_POST_COMMIT_ANIM_DURATION = 2000L
210 }
211 }
212
213 /** Helper class to load custom animation. */
214 class CustomAnimationLoader(private val transitionAnimation: TransitionAnimation) {
215
216 /**
217 * Load both enter and exit animation for the close activity transition. Note that the result is
218 * only valid if the exit animation has set and loaded success. If the entering animation has
219 * not set(i.e. 0), here will load the default entering animation for it.
220 *
221 * @param animationInfo The information of customize animation, which can be set from
222 * [Activity.overrideActivityTransition] and/or [LayoutParams.windowAnimations]
223 */
loadAllnull224 fun loadAll(
225 animationInfo: BackNavigationInfo.CustomAnimationInfo
226 ): CustomCrossActivityBackAnimation.AnimationLoadResult? {
227 if (animationInfo.packageName.isEmpty()) return null
228 val close = loadAnimation(animationInfo, false) ?: return null
229 val open = loadAnimation(animationInfo, true)
230 val result = CustomCrossActivityBackAnimation.AnimationLoadResult()
231 result.closeAnimation = close
232 result.enterAnimation = open
233 result.backgroundColor = animationInfo.customBackground
234 return result
235 }
236
237 /**
238 * Load enter or exit animation from CustomAnimationInfo
239 *
240 * @param animationInfo The information for customize animation.
241 * @param enterAnimation true when load for enter animation, false for exit animation.
242 * @return Loaded animation.
243 */
loadAnimationnull244 fun loadAnimation(
245 animationInfo: BackNavigationInfo.CustomAnimationInfo,
246 enterAnimation: Boolean
247 ): Animation? {
248 var a: Animation? = null
249 // Activity#overrideActivityTransition has higher priority than windowAnimations
250 // Try to get animation from Activity#overrideActivityTransition
251 if (
252 enterAnimation && animationInfo.customEnterAnim != 0 ||
253 !enterAnimation && animationInfo.customExitAnim != 0
254 ) {
255 a =
256 transitionAnimation.loadAppTransitionAnimation(
257 animationInfo.packageName,
258 if (enterAnimation) animationInfo.customEnterAnim
259 else animationInfo.customExitAnim
260 )
261 } else if (animationInfo.windowAnimations != 0) {
262 // try to get animation from LayoutParams#windowAnimations
263 a =
264 transitionAnimation.loadAnimationAttr(
265 animationInfo.packageName,
266 animationInfo.windowAnimations,
267 if (enterAnimation) R.styleable.WindowAnimation_activityCloseEnterAnimation
268 else R.styleable.WindowAnimation_activityCloseExitAnimation,
269 false /* translucent */
270 )
271 }
272 // Only allow to load default animation for opening target.
273 if (a == null && enterAnimation) {
274 a = loadDefaultOpenAnimation()
275 }
276 if (a != null) {
277 ProtoLog.d(ShellProtoLogGroup.WM_SHELL_BACK_PREVIEW, "custom animation loaded %s", a)
278 } else {
279 ProtoLog.e(ShellProtoLogGroup.WM_SHELL_BACK_PREVIEW, "No custom animation loaded")
280 }
281 return a
282 }
283
loadDefaultOpenAnimationnull284 private fun loadDefaultOpenAnimation(): Animation? {
285 return transitionAnimation.loadDefaultAnimationAttr(
286 R.styleable.WindowAnimation_activityCloseEnterAnimation,
287 false /* translucent */
288 )
289 }
290 }
291
initializeAnimationnull292 private fun initializeAnimation(animation: Animation, bounds: Rect) {
293 val width = bounds.width()
294 val height = bounds.height()
295 animation.initialize(width, height, width, height)
296 }
297