/* * Copyright (C) 2022 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.systemui.media.controls.ui.animation import android.graphics.drawable.Animatable2 import android.graphics.drawable.Drawable import androidx.annotation.VisibleForTesting import com.android.systemui.util.annotations.DeprecatedSysuiVisibleForTesting /** * AnimationBindHandler is responsible for tracking the bound animation state and preventing jank * and conflicts due to media notifications arriving at any time during an animation. It does this * in two parts. * - Exit animations fired as a result of user input are tracked. When these are running, any * * ``` * bind actions are delayed until the animation completes (and then fired in sequence). * ``` * - Continuous animations are tracked using their rebind id. Later calls using the same * * ``` * rebind id will be totally ignored to prevent the continuous animation from restarting. * ``` */ @DeprecatedSysuiVisibleForTesting @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE) class AnimationBindHandler : Animatable2.AnimationCallback() { private val onAnimationsComplete = mutableListOf<() -> Unit>() private val registrations = mutableListOf() private var rebindId: Int? = null val isAnimationRunning: Boolean get() = registrations.any { it.isRunning } /** * This check prevents rebinding to the action button if the identifier has not changed. A null * value is always considered to be changed. This is used to prevent the connecting animation * from rebinding (and restarting) if multiple buffer PlaybackStates are pushed by an * application in a row. */ fun updateRebindId(newRebindId: Int?): Boolean { if (rebindId == null || newRebindId == null || rebindId != newRebindId) { rebindId = newRebindId return true } return false } fun tryRegister(drawable: Drawable?) { if (drawable is Animatable2) { val anim = drawable as Animatable2 anim.registerAnimationCallback(this) registrations.add(anim) } } fun unregisterAll() { registrations.forEach { it.unregisterAnimationCallback(this) } registrations.clear() } fun tryExecute(action: () -> Unit) { if (isAnimationRunning) { onAnimationsComplete.add(action) } else { action() } } override fun onAnimationEnd(drawable: Drawable) { super.onAnimationEnd(drawable) if (!isAnimationRunning) { onAnimationsComplete.forEach { it() } onAnimationsComplete.clear() } } }