/* * Copyright (C) 2023 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.util.drawable import android.content.res.Resources import android.graphics.drawable.Animatable2 import android.graphics.drawable.Drawable import androidx.appcompat.graphics.drawable.DrawableWrapperCompat /** * Create a looped [Animatable2] restarting it when the animation finishes on its own. Calling * [LoopedAnimatable2DrawableWrapper.stop] cancels further looping. */ class LoopedAnimatable2DrawableWrapper private constructor(private val animatable2: Animatable2) : DrawableWrapperCompat(animatable2 as Drawable), Animatable2 { private val loopedCallback = LoopedCallback() private var isLoopedCallbackRegistered: Boolean = false override fun start() { animatable2.start() setLoopingRegistered(true) } override fun stop() { // stop looping if someone stops the animation setLoopingRegistered(false) animatable2.stop() } override fun isRunning(): Boolean = animatable2.isRunning override fun registerAnimationCallback(callback: Animatable2.AnimationCallback) = animatable2.registerAnimationCallback(callback) override fun unregisterAnimationCallback(callback: Animatable2.AnimationCallback): Boolean = animatable2.unregisterAnimationCallback(callback) override fun clearAnimationCallbacks() { animatable2.clearAnimationCallbacks() // re-register looped callback to maintain looped behaviour. LoopedCallback is a static // class and it has no extra references, so it doesn't provoke a memory leak. isLoopedCallbackRegistered = false setLoopingRegistered(true) } private fun setLoopingRegistered(isLooping: Boolean) { if (isLooping == isLoopedCallbackRegistered) { return } isLoopedCallbackRegistered = isLooping if (isLooping) { animatable2.registerAnimationCallback(loopedCallback) } else { animatable2.unregisterAnimationCallback(loopedCallback) } } override fun getConstantState(): ConstantState? = drawable!!.constantState?.let(LoopedAnimatable2DrawableWrapper::LoopedDrawableState) companion object { /** * Creates [LoopedAnimatable2DrawableWrapper] from a [drawable]. The [drawable] should * implement [Animatable2]. * * It supports the following resource tags: * - `` * - `` */ fun fromDrawable(drawable: Drawable): LoopedAnimatable2DrawableWrapper { require(drawable is Animatable2) return LoopedAnimatable2DrawableWrapper(drawable) } } private class LoopedCallback : Animatable2.AnimationCallback() { override fun onAnimationEnd(drawable: Drawable?) { (drawable as? Animatable2)?.start() } } private class LoopedDrawableState(private val nestedState: ConstantState) : ConstantState() { override fun newDrawable(): Drawable = fromDrawable(nestedState.newDrawable()) override fun newDrawable(res: Resources?): Drawable = fromDrawable(nestedState.newDrawable(res)) override fun newDrawable(res: Resources?, theme: Resources.Theme?): Drawable = fromDrawable(nestedState.newDrawable(res, theme)) override fun canApplyTheme(): Boolean = nestedState.canApplyTheme() override fun getChangingConfigurations(): Int = nestedState.changingConfigurations } }