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