1 /*
2  * Copyright (C) 2022 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.systemui.media.controls.ui.animation
18 
19 import android.graphics.drawable.Animatable2
20 import android.graphics.drawable.Drawable
21 
22 /**
23  * AnimationBindHandler is responsible for tracking the bound animation state and preventing jank
24  * and conflicts due to media notifications arriving at any time during an animation. It does this
25  * in two parts.
26  * - Exit animations fired as a result of user input are tracked. When these are running, any
27  *
28  * ```
29  *      bind actions are delayed until the animation completes (and then fired in sequence).
30  * ```
31  * - Continuous animations are tracked using their rebind id. Later calls using the same
32  *
33  * ```
34  *      rebind id will be totally ignored to prevent the continuous animation from restarting.
35  * ```
36  */
37 internal class AnimationBindHandler : Animatable2.AnimationCallback() {
38     private val onAnimationsComplete = mutableListOf<() -> Unit>()
39     private val registrations = mutableListOf<Animatable2>()
40     private var rebindId: Int? = null
41 
42     val isAnimationRunning: Boolean
<lambda>null43         get() = registrations.any { it.isRunning }
44 
45     /**
46      * This check prevents rebinding to the action button if the identifier has not changed. A null
47      * value is always considered to be changed. This is used to prevent the connecting animation
48      * from rebinding (and restarting) if multiple buffer PlaybackStates are pushed by an
49      * application in a row.
50      */
updateRebindIdnull51     fun updateRebindId(newRebindId: Int?): Boolean {
52         if (rebindId == null || newRebindId == null || rebindId != newRebindId) {
53             rebindId = newRebindId
54             return true
55         }
56         return false
57     }
58 
tryRegisternull59     fun tryRegister(drawable: Drawable?) {
60         if (drawable is Animatable2) {
61             val anim = drawable as Animatable2
62             anim.registerAnimationCallback(this)
63             registrations.add(anim)
64         }
65     }
66 
unregisterAllnull67     fun unregisterAll() {
68         registrations.forEach { it.unregisterAnimationCallback(this) }
69         registrations.clear()
70     }
71 
tryExecutenull72     fun tryExecute(action: () -> Unit) {
73         if (isAnimationRunning) {
74             onAnimationsComplete.add(action)
75         } else {
76             action()
77         }
78     }
79 
onAnimationEndnull80     override fun onAnimationEnd(drawable: Drawable) {
81         super.onAnimationEnd(drawable)
82         if (!isAnimationRunning) {
83             onAnimationsComplete.forEach { it() }
84             onAnimationsComplete.clear()
85         }
86     }
87 }
88