• 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 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