chromium/components/browser_ui/widget/android/java/src/org/chromium/components/browser_ui/widget/ClipDrawableProgressBar.java

// Copyright 2015 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.components.browser_ui.widget;

import android.content.Context;
import android.graphics.Color;
import android.graphics.Rect;
import android.graphics.drawable.ClipDrawable;
import android.graphics.drawable.ColorDrawable;
import android.view.Gravity;
import android.view.ViewGroup.LayoutParams;
import android.widget.ImageView;

import androidx.annotation.VisibleForTesting;
import androidx.core.view.ViewCompat;

import org.chromium.components.browser_ui.styles.SemanticColorUtils;

/** An alternative progress bar implemented using ClipDrawable for simplicity and performance. */
public class ClipDrawableProgressBar extends ImageView {
    /** Structure that has complete {@link ClipDrawableProgressBar} drawing information. */
    public static class DrawingInfo {
        public final Rect progressBarRect = new Rect();
        public final Rect progressBarBackgroundRect = new Rect();

        public int progressBarColor;
        public int progressBarBackgroundColor;
    }

    /** An observer for visible progress updates. */
    @VisibleForTesting
    public interface ProgressBarObserver {
        /**
         * A notification that the visible progress has been updated. This may not coincide with
         * updates from the web page due to animations for the progress bar running.
         */
        void onVisibleProgressUpdated();

        /** A notification that the visibility of the progress bar has changed. */
        void onVisibilityChanged();
    }

    // ClipDrawable's max is a fixed constant 10000.
    // http://developer.android.com/reference/android/graphics/drawable/ClipDrawable.html
    private static final int CLIP_DRAWABLE_MAX = 10000;

    private final ColorDrawable mForegroundDrawable;
    private int mBackgroundColor;
    private float mProgress;
    private int mDesiredVisibility;

    /** An observer of updates to the progress bar. */
    private ProgressBarObserver mProgressBarObserver;

    /**
     * Create the progress bar with a custom height.
     * @param context An Android context.
     * @param height The height in px of the progress bar.
     */
    public ClipDrawableProgressBar(Context context, int height) {
        super(context);

        mDesiredVisibility = getVisibility();

        int foregroundColor = SemanticColorUtils.getProgressBarForeground(getContext());
        mBackgroundColor = getContext().getColor(R.color.progress_bar_bg_color_list);

        mForegroundDrawable = new ColorDrawable(foregroundColor);
        setImageDrawable(
                new ClipDrawable(mForegroundDrawable, Gravity.START, ClipDrawable.HORIZONTAL));
        setBackgroundColor(mBackgroundColor);

        setLayoutParams(new LayoutParams(LayoutParams.MATCH_PARENT, height));
    }

    /** @param observer An update observer for the progress bar. */
    @VisibleForTesting
    public void setProgressBarObserver(ProgressBarObserver observer) {
        assert mProgressBarObserver == null;
        mProgressBarObserver = observer;
    }

    /**
     * Get the progress bar's current level of progress.
     *
     * @return The current progress, between 0.0 and 1.0.
     */
    public float getProgress() {
        return mProgress;
    }

    /**
     * Set the current progress to the specified value.
     *
     * @param progress The new progress, between 0.0 and 1.0.
     */
    public void setProgress(float progress) {
        assert 0.0f <= progress && progress <= 1.0f;
        if (mProgress == progress) return;

        mProgress = progress;
        getDrawable().setLevel(Math.round(progress * CLIP_DRAWABLE_MAX));
        if (mProgressBarObserver != null) mProgressBarObserver.onVisibleProgressUpdated();
    }

    /** @return Foreground color of the progress bar. */
    public int getForegroundColor() {
        return mForegroundDrawable.getColor();
    }

    /**
     * @return Background color of the progress bar.
     */
    public int getBackgroundColor() {
        return mBackgroundColor;
    }

    /**
     * Get progress bar drawing information.
     *
     * @param drawingInfoOut An instance that the result will be written.
     */
    public void getDrawingInfo(DrawingInfo drawingInfoOut) {
        int foregroundColor = mForegroundDrawable.getColor();
        float effectiveAlpha = getVisibility() == VISIBLE ? getAlpha() : 0.0f;
        drawingInfoOut.progressBarColor = applyAlpha(foregroundColor, effectiveAlpha);
        drawingInfoOut.progressBarBackgroundColor = applyAlpha(mBackgroundColor, effectiveAlpha);

        if (ViewCompat.getLayoutDirection(this) == LAYOUT_DIRECTION_LTR) {
            drawingInfoOut.progressBarRect.set(
                    getLeft(),
                    getTop(),
                    getLeft() + Math.round(mProgress * getWidth()),
                    getBottom());
            drawingInfoOut.progressBarBackgroundRect.set(
                    drawingInfoOut.progressBarRect.right, getTop(), getRight(), getBottom());
        } else {
            drawingInfoOut.progressBarRect.set(
                    getRight() - Math.round(mProgress * getWidth()),
                    getTop(),
                    getRight(),
                    getBottom());
            drawingInfoOut.progressBarBackgroundRect.set(
                    getLeft(), getTop(), drawingInfoOut.progressBarRect.left, getBottom());
        }
    }

    private void updateInternalVisibility() {
        int oldVisibility = getVisibility();
        int newVisibility = mDesiredVisibility;
        if (getAlpha() == 0 && mDesiredVisibility == VISIBLE) newVisibility = INVISIBLE;
        if (oldVisibility != newVisibility) {
            super.setVisibility(newVisibility);
            if (mProgressBarObserver != null) mProgressBarObserver.onVisibilityChanged();
        }
    }

    private int applyAlpha(int color, float alpha) {
        return (Math.round(alpha * (color >>> 24)) << 24) | (0x00ffffff & color);
    }

    // View implementations.

    /**
     * Note that this visibility might not be respected for optimization. For example, if alpha
     * is 0, it will remain View#INVISIBLE even if this is called with View#VISIBLE.
     */
    @Override
    public void setVisibility(int visibility) {
        mDesiredVisibility = visibility;
        updateInternalVisibility();
    }

    @Override
    public void setBackgroundColor(int color) {
        if (color == Color.TRANSPARENT) {
            setBackground(null);
        } else {
            super.setBackgroundColor(color);
        }

        mBackgroundColor = color;
    }

    /**
     * Sets the color for the foreground (i.e. the moving part) of the progress bar.
     * @param color The new color of the progress bar foreground.
     */
    public void setForegroundColor(int color) {
        mForegroundDrawable.setColor(color);
    }

    @Override
    protected boolean onSetAlpha(int alpha) {
        updateInternalVisibility();
        return super.onSetAlpha(alpha);
    }
}