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