chromium/ui/android/java/src/org/chromium/ui/drawable/AnimationLooper.java

// Copyright 2018 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

package org.chromium.ui.drawable;

import android.animation.ValueAnimator;
import android.graphics.drawable.Animatable;
import android.graphics.drawable.Drawable;
import android.os.Build;
import android.os.Handler;
import android.provider.Settings;

import androidx.annotation.Nullable;
import androidx.vectordrawable.graphics.drawable.Animatable2Compat;
import androidx.vectordrawable.graphics.drawable.AnimatedVectorDrawableCompat;

import org.chromium.base.ContextUtils;
import org.chromium.base.ResettersForTesting;

/**
 * Encapsulates the logic to loop animated drawables from both Android Framework. The animation
 * should be started and stopped using {@link #start()} and {@link #stop()}.
 */
public class AnimationLooper {
    private static @Nullable Boolean sAreAnimatorsEnabledForTests;

    private final Handler mHandler;
    private final Animatable mAnimatable;
    private final Animatable2Compat.AnimationCallback mAnimationCallback;

    private boolean mIsRunning;

    /**
     * @param drawable The drawable should be drawable and animatable at the same time.
     */
    public AnimationLooper(Drawable drawable) {
        mHandler = new Handler();
        mAnimatable = (Animatable) drawable;
        mAnimationCallback =
                new Animatable2Compat.AnimationCallback() {
                    @Override
                    public void onAnimationEnd(Drawable drawable) {
                        mHandler.post(mAnimatable::start);
                    }
                };
    }

    /** Starts the animation of the associated drawable. */
    public void start() {
        if (areAnimatorsEnabled()) {
            assert !mIsRunning : "Animation is already running!";
            AnimatedVectorDrawableCompat.registerAnimationCallback(
                    (Drawable) mAnimatable, mAnimationCallback);
            mAnimatable.start();
            mIsRunning = true;
        }
    }

    /** Stops the animation of the associated drawable. */
    public void stop() {
        if (mIsRunning) {
            AnimatedVectorDrawableCompat.unregisterAnimationCallback(
                    (Drawable) mAnimatable, mAnimationCallback);
            mAnimatable.stop();
            mIsRunning = false;
        }
    }

    private static boolean areAnimatorsEnabled() {
        if (sAreAnimatorsEnabledForTests != null) return sAreAnimatorsEnabledForTests;

        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            return ValueAnimator.areAnimatorsEnabled();
        } else {
            return Settings.Global.getFloat(
                            ContextUtils.getApplicationContext().getContentResolver(),
                            Settings.Global.ANIMATOR_DURATION_SCALE,
                            1.0f)
                    != 0.0f;
        }
    }

    static void setAreAnimatorsEnabledForTests(@Nullable Boolean areAnimatorsEnabled) {
        sAreAnimatorsEnabledForTests = areAnimatorsEnabled;
        ResettersForTesting.register(() -> sAreAnimatorsEnabledForTests = null);
    }
}