Home | History | Annotate | Download | only in widget
      1 /*
      2  * Copyright (C) 2006 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 android.widget;
     18 
     19 import android.animation.ObjectAnimator;
     20 import android.annotation.InterpolatorRes;
     21 import android.annotation.NonNull;
     22 import android.annotation.Nullable;
     23 import android.content.Context;
     24 import android.content.res.ColorStateList;
     25 import android.content.res.TypedArray;
     26 import android.graphics.Canvas;
     27 import android.graphics.PorterDuff;
     28 import android.graphics.Rect;
     29 import android.graphics.Shader;
     30 import android.graphics.drawable.Animatable;
     31 import android.graphics.drawable.AnimationDrawable;
     32 import android.graphics.drawable.BitmapDrawable;
     33 import android.graphics.drawable.ClipDrawable;
     34 import android.graphics.drawable.Drawable;
     35 import android.graphics.drawable.LayerDrawable;
     36 import android.graphics.drawable.StateListDrawable;
     37 import android.graphics.drawable.shapes.RoundRectShape;
     38 import android.graphics.drawable.shapes.Shape;
     39 import android.os.Parcel;
     40 import android.os.Parcelable;
     41 import android.util.AttributeSet;
     42 import android.util.FloatProperty;
     43 import android.util.MathUtils;
     44 import android.util.Pools.SynchronizedPool;
     45 import android.view.Gravity;
     46 import android.view.RemotableViewMethod;
     47 import android.view.View;
     48 import android.view.ViewDebug;
     49 import android.view.ViewHierarchyEncoder;
     50 import android.view.accessibility.AccessibilityEvent;
     51 import android.view.accessibility.AccessibilityManager;
     52 import android.view.accessibility.AccessibilityNodeInfo;
     53 import android.view.animation.AlphaAnimation;
     54 import android.view.animation.Animation;
     55 import android.view.animation.AnimationUtils;
     56 import android.view.animation.DecelerateInterpolator;
     57 import android.view.animation.Interpolator;
     58 import android.view.animation.LinearInterpolator;
     59 import android.view.animation.Transformation;
     60 import android.widget.RemoteViews.RemoteView;
     61 
     62 import com.android.internal.R;
     63 
     64 import java.util.ArrayList;
     65 
     66 /**
     67  * <p>
     68  * A user interface element that indicates the progress of an operation.
     69  * Progress bar supports two modes to represent progress: determinate, and indeterminate. For
     70  * a visual overview of the difference between determinate and indeterminate progress modes, see
     71  * <a href="https://material.io/guidelines/components/progress-activity.html#progress-activity-types-of-indicators">
     72  * Progress & activity</a>.
     73  * Display progress bars to a user in a non-interruptive way.
     74  * Show the progress bar in your app's user interface or in a notification
     75  * instead of within a dialog.
     76  * </p>
     77  * <h3>Indeterminate Progress</h3>
     78  * <p>
     79  * Use indeterminate mode for the progress bar when you do not know how long an
     80  * operation will take.
     81  * Indeterminate mode is the default for progress bar and shows a cyclic animation without a
     82  * specific amount of progress indicated.
     83  * The following example shows an indeterminate progress bar:
     84  * <pre>
     85  * &lt;ProgressBar
     86  *      android:id="@+id/indeterminateBar"
     87  *      android:layout_width="wrap_content"
     88  *      android:layout_height="wrap_content"
     89  *      /&gt;
     90  * </pre>
     91  * </p>
     92  * <h3>Determinate Progress</h3>
     93  * <p>
     94  * Use determinate mode for the progress bar when you want to show that a specific quantity of
     95  * progress has occurred.
     96  * For example, the percent remaining of a file being retrieved, the amount records in
     97  * a batch written to database, or the percent remaining of an audio file that is playing.
     98  * <p>
     99  * <p>
    100  * To indicate determinate progress, you set the style of the progress bar to
    101  * {@link android.R.style#Widget_ProgressBar_Horizontal} and set the amount of progress.
    102  * The following example shows a determinate progress bar that is 25% complete:
    103  * <pre>
    104  * &lt;ProgressBar
    105  *      android:id="@+id/determinateBar"
    106  *      style="@android:style/Widget.ProgressBar.Horizontal"
    107  *      android:layout_width="wrap_content"
    108  *      android:layout_height="wrap_content"
    109  *      android:progress="25"/&gt;
    110  * </pre>
    111  * You can update the percentage of progress displayed by using the
    112  * {@link #setProgress(int)} method, or by calling
    113  * {@link #incrementProgressBy(int)} to increase the current progress completed
    114  * by a specified amount.
    115  * By default, the progress bar is full when the progress value reaches 100.
    116  * You can adjust this default by setting the
    117  * {@link android.R.styleable#ProgressBar_max android:max} attribute.
    118  * </p>
    119  * <p>Other progress bar styles provided by the system include:</p>
    120  * <ul>
    121  * <li>{@link android.R.style#Widget_ProgressBar_Horizontal Widget.ProgressBar.Horizontal}</li>
    122  * <li>{@link android.R.style#Widget_ProgressBar_Small Widget.ProgressBar.Small}</li>
    123  * <li>{@link android.R.style#Widget_ProgressBar_Large Widget.ProgressBar.Large}</li>
    124  * <li>{@link android.R.style#Widget_ProgressBar_Inverse Widget.ProgressBar.Inverse}</li>
    125  * <li>{@link android.R.style#Widget_ProgressBar_Small_Inverse
    126  * Widget.ProgressBar.Small.Inverse}</li>
    127  * <li>{@link android.R.style#Widget_ProgressBar_Large_Inverse
    128  * Widget.ProgressBar.Large.Inverse}</li>
    129  * </ul>
    130  * <p>The "inverse" styles provide an inverse color scheme for the spinner, which may be necessary
    131  * if your application uses a light colored theme (a white background).</p>
    132  *
    133  * <p><strong>XML attributes</b></strong>
    134  * <p>
    135  * See {@link android.R.styleable#ProgressBar ProgressBar Attributes},
    136  * {@link android.R.styleable#View View Attributes}
    137  * </p>
    138  *
    139  * @attr ref android.R.styleable#ProgressBar_animationResolution
    140  * @attr ref android.R.styleable#ProgressBar_indeterminate
    141  * @attr ref android.R.styleable#ProgressBar_indeterminateBehavior
    142  * @attr ref android.R.styleable#ProgressBar_indeterminateDrawable
    143  * @attr ref android.R.styleable#ProgressBar_indeterminateDuration
    144  * @attr ref android.R.styleable#ProgressBar_indeterminateOnly
    145  * @attr ref android.R.styleable#ProgressBar_interpolator
    146  * @attr ref android.R.styleable#ProgressBar_min
    147  * @attr ref android.R.styleable#ProgressBar_max
    148  * @attr ref android.R.styleable#ProgressBar_maxHeight
    149  * @attr ref android.R.styleable#ProgressBar_maxWidth
    150  * @attr ref android.R.styleable#ProgressBar_minHeight
    151  * @attr ref android.R.styleable#ProgressBar_minWidth
    152  * @attr ref android.R.styleable#ProgressBar_mirrorForRtl
    153  * @attr ref android.R.styleable#ProgressBar_progress
    154  * @attr ref android.R.styleable#ProgressBar_progressDrawable
    155  * @attr ref android.R.styleable#ProgressBar_secondaryProgress
    156  */
    157 @RemoteView
    158 public class ProgressBar extends View {
    159 
    160     private static final int MAX_LEVEL = 10000;
    161     private static final int TIMEOUT_SEND_ACCESSIBILITY_EVENT = 200;
    162 
    163     /** Interpolator used for smooth progress animations. */
    164     private static final DecelerateInterpolator PROGRESS_ANIM_INTERPOLATOR =
    165             new DecelerateInterpolator();
    166 
    167     /** Duration of smooth progress animations. */
    168     private static final int PROGRESS_ANIM_DURATION = 80;
    169 
    170     int mMinWidth;
    171     int mMaxWidth;
    172     int mMinHeight;
    173     int mMaxHeight;
    174 
    175     private int mProgress;
    176     private int mSecondaryProgress;
    177     private int mMin;
    178     private boolean mMinInitialized;
    179     private int mMax;
    180     private boolean mMaxInitialized;
    181 
    182     private int mBehavior;
    183     private int mDuration;
    184     private boolean mIndeterminate;
    185     private boolean mOnlyIndeterminate;
    186     private Transformation mTransformation;
    187     private AlphaAnimation mAnimation;
    188     private boolean mHasAnimation;
    189 
    190     private Drawable mIndeterminateDrawable;
    191     private Drawable mProgressDrawable;
    192     private Drawable mCurrentDrawable;
    193     private ProgressTintInfo mProgressTintInfo;
    194 
    195     int mSampleWidth = 0;
    196     private boolean mNoInvalidate;
    197     private Interpolator mInterpolator;
    198     private RefreshProgressRunnable mRefreshProgressRunnable;
    199     private long mUiThreadId;
    200     private boolean mShouldStartAnimationDrawable;
    201 
    202     private boolean mInDrawing;
    203     private boolean mAttached;
    204     private boolean mRefreshIsPosted;
    205 
    206     /** Value used to track progress animation, in the range [0...1]. */
    207     private float mVisualProgress;
    208 
    209     boolean mMirrorForRtl = false;
    210 
    211     private boolean mAggregatedIsVisible;
    212 
    213     private final ArrayList<RefreshData> mRefreshData = new ArrayList<RefreshData>();
    214 
    215     private AccessibilityEventSender mAccessibilityEventSender;
    216 
    217     /**
    218      * Create a new progress bar with range 0...100 and initial progress of 0.
    219      * @param context the application environment
    220      */
    221     public ProgressBar(Context context) {
    222         this(context, null);
    223     }
    224 
    225     public ProgressBar(Context context, AttributeSet attrs) {
    226         this(context, attrs, com.android.internal.R.attr.progressBarStyle);
    227     }
    228 
    229     public ProgressBar(Context context, AttributeSet attrs, int defStyleAttr) {
    230         this(context, attrs, defStyleAttr, 0);
    231     }
    232 
    233     public ProgressBar(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
    234         super(context, attrs, defStyleAttr, defStyleRes);
    235 
    236         mUiThreadId = Thread.currentThread().getId();
    237         initProgressBar();
    238 
    239         final TypedArray a = context.obtainStyledAttributes(
    240                 attrs, R.styleable.ProgressBar, defStyleAttr, defStyleRes);
    241 
    242         mNoInvalidate = true;
    243 
    244         final Drawable progressDrawable = a.getDrawable(R.styleable.ProgressBar_progressDrawable);
    245         if (progressDrawable != null) {
    246             // Calling setProgressDrawable can set mMaxHeight, so make sure the
    247             // corresponding XML attribute for mMaxHeight is read after calling
    248             // this method.
    249             if (needsTileify(progressDrawable)) {
    250                 setProgressDrawableTiled(progressDrawable);
    251             } else {
    252                 setProgressDrawable(progressDrawable);
    253             }
    254         }
    255 
    256 
    257         mDuration = a.getInt(R.styleable.ProgressBar_indeterminateDuration, mDuration);
    258 
    259         mMinWidth = a.getDimensionPixelSize(R.styleable.ProgressBar_minWidth, mMinWidth);
    260         mMaxWidth = a.getDimensionPixelSize(R.styleable.ProgressBar_maxWidth, mMaxWidth);
    261         mMinHeight = a.getDimensionPixelSize(R.styleable.ProgressBar_minHeight, mMinHeight);
    262         mMaxHeight = a.getDimensionPixelSize(R.styleable.ProgressBar_maxHeight, mMaxHeight);
    263 
    264         mBehavior = a.getInt(R.styleable.ProgressBar_indeterminateBehavior, mBehavior);
    265 
    266         final int resID = a.getResourceId(
    267                 com.android.internal.R.styleable.ProgressBar_interpolator,
    268                 android.R.anim.linear_interpolator); // default to linear interpolator
    269         if (resID > 0) {
    270             setInterpolator(context, resID);
    271         }
    272 
    273         setMin(a.getInt(R.styleable.ProgressBar_min, mMin));
    274         setMax(a.getInt(R.styleable.ProgressBar_max, mMax));
    275 
    276         setProgress(a.getInt(R.styleable.ProgressBar_progress, mProgress));
    277 
    278         setSecondaryProgress(a.getInt(
    279                 R.styleable.ProgressBar_secondaryProgress, mSecondaryProgress));
    280 
    281         final Drawable indeterminateDrawable = a.getDrawable(
    282                 R.styleable.ProgressBar_indeterminateDrawable);
    283         if (indeterminateDrawable != null) {
    284             if (needsTileify(indeterminateDrawable)) {
    285                 setIndeterminateDrawableTiled(indeterminateDrawable);
    286             } else {
    287                 setIndeterminateDrawable(indeterminateDrawable);
    288             }
    289         }
    290 
    291         mOnlyIndeterminate = a.getBoolean(
    292                 R.styleable.ProgressBar_indeterminateOnly, mOnlyIndeterminate);
    293 
    294         mNoInvalidate = false;
    295 
    296         setIndeterminate(mOnlyIndeterminate || a.getBoolean(
    297                 R.styleable.ProgressBar_indeterminate, mIndeterminate));
    298 
    299         mMirrorForRtl = a.getBoolean(R.styleable.ProgressBar_mirrorForRtl, mMirrorForRtl);
    300 
    301         if (a.hasValue(R.styleable.ProgressBar_progressTintMode)) {
    302             if (mProgressTintInfo == null) {
    303                 mProgressTintInfo = new ProgressTintInfo();
    304             }
    305             mProgressTintInfo.mProgressTintMode = Drawable.parseTintMode(a.getInt(
    306                     R.styleable.ProgressBar_progressTintMode, -1), null);
    307             mProgressTintInfo.mHasProgressTintMode = true;
    308         }
    309 
    310         if (a.hasValue(R.styleable.ProgressBar_progressTint)) {
    311             if (mProgressTintInfo == null) {
    312                 mProgressTintInfo = new ProgressTintInfo();
    313             }
    314             mProgressTintInfo.mProgressTintList = a.getColorStateList(
    315                     R.styleable.ProgressBar_progressTint);
    316             mProgressTintInfo.mHasProgressTint = true;
    317         }
    318 
    319         if (a.hasValue(R.styleable.ProgressBar_progressBackgroundTintMode)) {
    320             if (mProgressTintInfo == null) {
    321                 mProgressTintInfo = new ProgressTintInfo();
    322             }
    323             mProgressTintInfo.mProgressBackgroundTintMode = Drawable.parseTintMode(a.getInt(
    324                     R.styleable.ProgressBar_progressBackgroundTintMode, -1), null);
    325             mProgressTintInfo.mHasProgressBackgroundTintMode = true;
    326         }
    327 
    328         if (a.hasValue(R.styleable.ProgressBar_progressBackgroundTint)) {
    329             if (mProgressTintInfo == null) {
    330                 mProgressTintInfo = new ProgressTintInfo();
    331             }
    332             mProgressTintInfo.mProgressBackgroundTintList = a.getColorStateList(
    333                     R.styleable.ProgressBar_progressBackgroundTint);
    334             mProgressTintInfo.mHasProgressBackgroundTint = true;
    335         }
    336 
    337         if (a.hasValue(R.styleable.ProgressBar_secondaryProgressTintMode)) {
    338             if (mProgressTintInfo == null) {
    339                 mProgressTintInfo = new ProgressTintInfo();
    340             }
    341             mProgressTintInfo.mSecondaryProgressTintMode = Drawable.parseTintMode(
    342                     a.getInt(R.styleable.ProgressBar_secondaryProgressTintMode, -1), null);
    343             mProgressTintInfo.mHasSecondaryProgressTintMode = true;
    344         }
    345 
    346         if (a.hasValue(R.styleable.ProgressBar_secondaryProgressTint)) {
    347             if (mProgressTintInfo == null) {
    348                 mProgressTintInfo = new ProgressTintInfo();
    349             }
    350             mProgressTintInfo.mSecondaryProgressTintList = a.getColorStateList(
    351                     R.styleable.ProgressBar_secondaryProgressTint);
    352             mProgressTintInfo.mHasSecondaryProgressTint = true;
    353         }
    354 
    355         if (a.hasValue(R.styleable.ProgressBar_indeterminateTintMode)) {
    356             if (mProgressTintInfo == null) {
    357                 mProgressTintInfo = new ProgressTintInfo();
    358             }
    359             mProgressTintInfo.mIndeterminateTintMode = Drawable.parseTintMode(a.getInt(
    360                     R.styleable.ProgressBar_indeterminateTintMode, -1), null);
    361             mProgressTintInfo.mHasIndeterminateTintMode = true;
    362         }
    363 
    364         if (a.hasValue(R.styleable.ProgressBar_indeterminateTint)) {
    365             if (mProgressTintInfo == null) {
    366                 mProgressTintInfo = new ProgressTintInfo();
    367             }
    368             mProgressTintInfo.mIndeterminateTintList = a.getColorStateList(
    369                     R.styleable.ProgressBar_indeterminateTint);
    370             mProgressTintInfo.mHasIndeterminateTint = true;
    371         }
    372 
    373         a.recycle();
    374 
    375         applyProgressTints();
    376         applyIndeterminateTint();
    377 
    378         // If not explicitly specified this view is important for accessibility.
    379         if (getImportantForAccessibility() == View.IMPORTANT_FOR_ACCESSIBILITY_AUTO) {
    380             setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_YES);
    381         }
    382     }
    383 
    384     /**
    385      * Returns {@code true} if the target drawable needs to be tileified.
    386      *
    387      * @param dr the drawable to check
    388      * @return {@code true} if the target drawable needs to be tileified,
    389      *         {@code false} otherwise
    390      */
    391     private static boolean needsTileify(Drawable dr) {
    392         if (dr instanceof LayerDrawable) {
    393             final LayerDrawable orig = (LayerDrawable) dr;
    394             final int N = orig.getNumberOfLayers();
    395             for (int i = 0; i < N; i++) {
    396                 if (needsTileify(orig.getDrawable(i))) {
    397                     return true;
    398                 }
    399             }
    400             return false;
    401         }
    402 
    403         if (dr instanceof StateListDrawable) {
    404             final StateListDrawable in = (StateListDrawable) dr;
    405             final int N = in.getStateCount();
    406             for (int i = 0; i < N; i++) {
    407                 if (needsTileify(in.getStateDrawable(i))) {
    408                     return true;
    409                 }
    410             }
    411             return false;
    412         }
    413 
    414         // If there's a bitmap that's not wrapped with a ClipDrawable or
    415         // ScaleDrawable, we'll need to wrap it and apply tiling.
    416         if (dr instanceof BitmapDrawable) {
    417             return true;
    418         }
    419 
    420         return false;
    421     }
    422 
    423     /**
    424      * Converts a drawable to a tiled version of itself. It will recursively
    425      * traverse layer and state list drawables.
    426      */
    427     private Drawable tileify(Drawable drawable, boolean clip) {
    428         // TODO: This is a terrible idea that potentially destroys any drawable
    429         // that extends any of these classes. We *really* need to remove this.
    430 
    431         if (drawable instanceof LayerDrawable) {
    432             final LayerDrawable orig = (LayerDrawable) drawable;
    433             final int N = orig.getNumberOfLayers();
    434             final Drawable[] outDrawables = new Drawable[N];
    435 
    436             for (int i = 0; i < N; i++) {
    437                 final int id = orig.getId(i);
    438                 outDrawables[i] = tileify(orig.getDrawable(i),
    439                         (id == R.id.progress || id == R.id.secondaryProgress));
    440             }
    441 
    442             final LayerDrawable clone = new LayerDrawable(outDrawables);
    443             for (int i = 0; i < N; i++) {
    444                 clone.setId(i, orig.getId(i));
    445                 clone.setLayerGravity(i, orig.getLayerGravity(i));
    446                 clone.setLayerWidth(i, orig.getLayerWidth(i));
    447                 clone.setLayerHeight(i, orig.getLayerHeight(i));
    448                 clone.setLayerInsetLeft(i, orig.getLayerInsetLeft(i));
    449                 clone.setLayerInsetRight(i, orig.getLayerInsetRight(i));
    450                 clone.setLayerInsetTop(i, orig.getLayerInsetTop(i));
    451                 clone.setLayerInsetBottom(i, orig.getLayerInsetBottom(i));
    452                 clone.setLayerInsetStart(i, orig.getLayerInsetStart(i));
    453                 clone.setLayerInsetEnd(i, orig.getLayerInsetEnd(i));
    454             }
    455 
    456             return clone;
    457         }
    458 
    459         if (drawable instanceof StateListDrawable) {
    460             final StateListDrawable in = (StateListDrawable) drawable;
    461             final StateListDrawable out = new StateListDrawable();
    462             final int N = in.getStateCount();
    463             for (int i = 0; i < N; i++) {
    464                 out.addState(in.getStateSet(i), tileify(in.getStateDrawable(i), clip));
    465             }
    466 
    467             return out;
    468         }
    469 
    470         if (drawable instanceof BitmapDrawable) {
    471             final Drawable.ConstantState cs = drawable.getConstantState();
    472             final BitmapDrawable clone = (BitmapDrawable) cs.newDrawable(getResources());
    473             clone.setTileModeXY(Shader.TileMode.REPEAT, Shader.TileMode.CLAMP);
    474 
    475             if (mSampleWidth <= 0) {
    476                 mSampleWidth = clone.getIntrinsicWidth();
    477             }
    478 
    479             if (clip) {
    480                 return new ClipDrawable(clone, Gravity.LEFT, ClipDrawable.HORIZONTAL);
    481             } else {
    482                 return clone;
    483             }
    484         }
    485 
    486         return drawable;
    487     }
    488 
    489     Shape getDrawableShape() {
    490         final float[] roundedCorners = new float[] { 5, 5, 5, 5, 5, 5, 5, 5 };
    491         return new RoundRectShape(roundedCorners, null, null);
    492     }
    493 
    494     /**
    495      * Convert a AnimationDrawable for use as a barberpole animation.
    496      * Each frame of the animation is wrapped in a ClipDrawable and
    497      * given a tiling BitmapShader.
    498      */
    499     private Drawable tileifyIndeterminate(Drawable drawable) {
    500         if (drawable instanceof AnimationDrawable) {
    501             AnimationDrawable background = (AnimationDrawable) drawable;
    502             final int N = background.getNumberOfFrames();
    503             AnimationDrawable newBg = new AnimationDrawable();
    504             newBg.setOneShot(background.isOneShot());
    505 
    506             for (int i = 0; i < N; i++) {
    507                 Drawable frame = tileify(background.getFrame(i), true);
    508                 frame.setLevel(10000);
    509                 newBg.addFrame(frame, background.getDuration(i));
    510             }
    511             newBg.setLevel(10000);
    512             drawable = newBg;
    513         }
    514         return drawable;
    515     }
    516 
    517     /**
    518      * <p>
    519      * Initialize the progress bar's default values:
    520      * </p>
    521      * <ul>
    522      * <li>progress = 0</li>
    523      * <li>max = 100</li>
    524      * <li>animation duration = 4000 ms</li>
    525      * <li>indeterminate = false</li>
    526      * <li>behavior = repeat</li>
    527      * </ul>
    528      */
    529     private void initProgressBar() {
    530         mMin = 0;
    531         mMax = 100;
    532         mProgress = 0;
    533         mSecondaryProgress = 0;
    534         mIndeterminate = false;
    535         mOnlyIndeterminate = false;
    536         mDuration = 4000;
    537         mBehavior = AlphaAnimation.RESTART;
    538         mMinWidth = 24;
    539         mMaxWidth = 48;
    540         mMinHeight = 24;
    541         mMaxHeight = 48;
    542     }
    543 
    544     /**
    545      * <p>Indicate whether this progress bar is in indeterminate mode.</p>
    546      *
    547      * @return true if the progress bar is in indeterminate mode
    548      */
    549     @ViewDebug.ExportedProperty(category = "progress")
    550     public synchronized boolean isIndeterminate() {
    551         return mIndeterminate;
    552     }
    553 
    554     /**
    555      * <p>Change the indeterminate mode for this progress bar. In indeterminate
    556      * mode, the progress is ignored and the progress bar shows an infinite
    557      * animation instead.</p>
    558      *
    559      * If this progress bar's style only supports indeterminate mode (such as the circular
    560      * progress bars), then this will be ignored.
    561      *
    562      * @param indeterminate true to enable the indeterminate mode
    563      */
    564     @android.view.RemotableViewMethod
    565     public synchronized void setIndeterminate(boolean indeterminate) {
    566         if ((!mOnlyIndeterminate || !mIndeterminate) && indeterminate != mIndeterminate) {
    567             mIndeterminate = indeterminate;
    568 
    569             if (indeterminate) {
    570                 // swap between indeterminate and regular backgrounds
    571                 swapCurrentDrawable(mIndeterminateDrawable);
    572                 startAnimation();
    573             } else {
    574                 swapCurrentDrawable(mProgressDrawable);
    575                 stopAnimation();
    576             }
    577         }
    578     }
    579 
    580     private void swapCurrentDrawable(Drawable newDrawable) {
    581         final Drawable oldDrawable = mCurrentDrawable;
    582         mCurrentDrawable = newDrawable;
    583 
    584         if (oldDrawable != mCurrentDrawable) {
    585             if (oldDrawable != null) {
    586                 oldDrawable.setVisible(false, false);
    587             }
    588             if (mCurrentDrawable != null) {
    589                 mCurrentDrawable.setVisible(getWindowVisibility() == VISIBLE && isShown(), false);
    590             }
    591         }
    592     }
    593 
    594     /**
    595      * <p>Get the drawable used to draw the progress bar in
    596      * indeterminate mode.</p>
    597      *
    598      * @return a {@link android.graphics.drawable.Drawable} instance
    599      *
    600      * @see #setIndeterminateDrawable(android.graphics.drawable.Drawable)
    601      * @see #setIndeterminate(boolean)
    602      */
    603     public Drawable getIndeterminateDrawable() {
    604         return mIndeterminateDrawable;
    605     }
    606 
    607     /**
    608      * Define the drawable used to draw the progress bar in indeterminate mode.
    609      *
    610      * @param d the new drawable
    611      * @see #getIndeterminateDrawable()
    612      * @see #setIndeterminate(boolean)
    613      */
    614     public void setIndeterminateDrawable(Drawable d) {
    615         if (mIndeterminateDrawable != d) {
    616             if (mIndeterminateDrawable != null) {
    617                 mIndeterminateDrawable.setCallback(null);
    618                 unscheduleDrawable(mIndeterminateDrawable);
    619             }
    620 
    621             mIndeterminateDrawable = d;
    622 
    623             if (d != null) {
    624                 d.setCallback(this);
    625                 d.setLayoutDirection(getLayoutDirection());
    626                 if (d.isStateful()) {
    627                     d.setState(getDrawableState());
    628                 }
    629                 applyIndeterminateTint();
    630             }
    631 
    632             if (mIndeterminate) {
    633                 swapCurrentDrawable(d);
    634                 postInvalidate();
    635             }
    636         }
    637     }
    638 
    639     /**
    640      * Applies a tint to the indeterminate drawable. Does not modify the
    641      * current tint mode, which is {@link PorterDuff.Mode#SRC_IN} by default.
    642      * <p>
    643      * Subsequent calls to {@link #setIndeterminateDrawable(Drawable)} will
    644      * automatically mutate the drawable and apply the specified tint and
    645      * tint mode using
    646      * {@link Drawable#setTintList(ColorStateList)}.
    647      *
    648      * @param tint the tint to apply, may be {@code null} to clear tint
    649      *
    650      * @attr ref android.R.styleable#ProgressBar_indeterminateTint
    651      * @see #getIndeterminateTintList()
    652      * @see Drawable#setTintList(ColorStateList)
    653      */
    654     @RemotableViewMethod
    655     public void setIndeterminateTintList(@Nullable ColorStateList tint) {
    656         if (mProgressTintInfo == null) {
    657             mProgressTintInfo = new ProgressTintInfo();
    658         }
    659         mProgressTintInfo.mIndeterminateTintList = tint;
    660         mProgressTintInfo.mHasIndeterminateTint = true;
    661 
    662         applyIndeterminateTint();
    663     }
    664 
    665     /**
    666      * @return the tint applied to the indeterminate drawable
    667      * @attr ref android.R.styleable#ProgressBar_indeterminateTint
    668      * @see #setIndeterminateTintList(ColorStateList)
    669      */
    670     @Nullable
    671     public ColorStateList getIndeterminateTintList() {
    672         return mProgressTintInfo != null ? mProgressTintInfo.mIndeterminateTintList : null;
    673     }
    674 
    675     /**
    676      * Specifies the blending mode used to apply the tint specified by
    677      * {@link #setIndeterminateTintList(ColorStateList)} to the indeterminate
    678      * drawable. The default mode is {@link PorterDuff.Mode#SRC_IN}.
    679      *
    680      * @param tintMode the blending mode used to apply the tint, may be
    681      *                 {@code null} to clear tint
    682      * @attr ref android.R.styleable#ProgressBar_indeterminateTintMode
    683      * @see #setIndeterminateTintList(ColorStateList)
    684      * @see Drawable#setTintMode(PorterDuff.Mode)
    685      */
    686     public void setIndeterminateTintMode(@Nullable PorterDuff.Mode tintMode) {
    687         if (mProgressTintInfo == null) {
    688             mProgressTintInfo = new ProgressTintInfo();
    689         }
    690         mProgressTintInfo.mIndeterminateTintMode = tintMode;
    691         mProgressTintInfo.mHasIndeterminateTintMode = true;
    692 
    693         applyIndeterminateTint();
    694     }
    695 
    696     /**
    697      * Returns the blending mode used to apply the tint to the indeterminate
    698      * drawable, if specified.
    699      *
    700      * @return the blending mode used to apply the tint to the indeterminate
    701      *         drawable
    702      * @attr ref android.R.styleable#ProgressBar_indeterminateTintMode
    703      * @see #setIndeterminateTintMode(PorterDuff.Mode)
    704      */
    705     @Nullable
    706     public PorterDuff.Mode getIndeterminateTintMode() {
    707         return mProgressTintInfo != null ? mProgressTintInfo.mIndeterminateTintMode : null;
    708     }
    709 
    710     private void applyIndeterminateTint() {
    711         if (mIndeterminateDrawable != null && mProgressTintInfo != null) {
    712             final ProgressTintInfo tintInfo = mProgressTintInfo;
    713             if (tintInfo.mHasIndeterminateTint || tintInfo.mHasIndeterminateTintMode) {
    714                 mIndeterminateDrawable = mIndeterminateDrawable.mutate();
    715 
    716                 if (tintInfo.mHasIndeterminateTint) {
    717                     mIndeterminateDrawable.setTintList(tintInfo.mIndeterminateTintList);
    718                 }
    719 
    720                 if (tintInfo.mHasIndeterminateTintMode) {
    721                     mIndeterminateDrawable.setTintMode(tintInfo.mIndeterminateTintMode);
    722                 }
    723 
    724                 // The drawable (or one of its children) may not have been
    725                 // stateful before applying the tint, so let's try again.
    726                 if (mIndeterminateDrawable.isStateful()) {
    727                     mIndeterminateDrawable.setState(getDrawableState());
    728                 }
    729             }
    730         }
    731     }
    732 
    733     /**
    734      * Define the tileable drawable used to draw the progress bar in
    735      * indeterminate mode.
    736      * <p>
    737      * If the drawable is a BitmapDrawable or contains BitmapDrawables, a
    738      * tiled copy will be generated for display as a progress bar.
    739      *
    740      * @param d the new drawable
    741      * @see #getIndeterminateDrawable()
    742      * @see #setIndeterminate(boolean)
    743      */
    744     public void setIndeterminateDrawableTiled(Drawable d) {
    745         if (d != null) {
    746             d = tileifyIndeterminate(d);
    747         }
    748 
    749         setIndeterminateDrawable(d);
    750     }
    751 
    752     /**
    753      * <p>Get the drawable used to draw the progress bar in
    754      * progress mode.</p>
    755      *
    756      * @return a {@link android.graphics.drawable.Drawable} instance
    757      *
    758      * @see #setProgressDrawable(android.graphics.drawable.Drawable)
    759      * @see #setIndeterminate(boolean)
    760      */
    761     public Drawable getProgressDrawable() {
    762         return mProgressDrawable;
    763     }
    764 
    765     /**
    766      * Define the drawable used to draw the progress bar in progress mode.
    767      *
    768      * @param d the new drawable
    769      * @see #getProgressDrawable()
    770      * @see #setIndeterminate(boolean)
    771      */
    772     public void setProgressDrawable(Drawable d) {
    773         if (mProgressDrawable != d) {
    774             if (mProgressDrawable != null) {
    775                 mProgressDrawable.setCallback(null);
    776                 unscheduleDrawable(mProgressDrawable);
    777             }
    778 
    779             mProgressDrawable = d;
    780 
    781             if (d != null) {
    782                 d.setCallback(this);
    783                 d.setLayoutDirection(getLayoutDirection());
    784                 if (d.isStateful()) {
    785                     d.setState(getDrawableState());
    786                 }
    787 
    788                 // Make sure the ProgressBar is always tall enough
    789                 int drawableHeight = d.getMinimumHeight();
    790                 if (mMaxHeight < drawableHeight) {
    791                     mMaxHeight = drawableHeight;
    792                     requestLayout();
    793                 }
    794 
    795                 applyProgressTints();
    796             }
    797 
    798             if (!mIndeterminate) {
    799                 swapCurrentDrawable(d);
    800                 postInvalidate();
    801             }
    802 
    803             updateDrawableBounds(getWidth(), getHeight());
    804             updateDrawableState();
    805 
    806             doRefreshProgress(R.id.progress, mProgress, false, false, false);
    807             doRefreshProgress(R.id.secondaryProgress, mSecondaryProgress, false, false, false);
    808         }
    809     }
    810 
    811     /**
    812      * @hide
    813      */
    814     public boolean getMirrorForRtl() {
    815         return mMirrorForRtl;
    816     }
    817 
    818     /**
    819      * Applies the progress tints in order of increasing specificity.
    820      */
    821     private void applyProgressTints() {
    822         if (mProgressDrawable != null && mProgressTintInfo != null) {
    823             applyPrimaryProgressTint();
    824             applyProgressBackgroundTint();
    825             applySecondaryProgressTint();
    826         }
    827     }
    828 
    829     /**
    830      * Should only be called if we've already verified that mProgressDrawable
    831      * and mProgressTintInfo are non-null.
    832      */
    833     private void applyPrimaryProgressTint() {
    834         if (mProgressTintInfo.mHasProgressTint
    835                 || mProgressTintInfo.mHasProgressTintMode) {
    836             final Drawable target = getTintTarget(R.id.progress, true);
    837             if (target != null) {
    838                 if (mProgressTintInfo.mHasProgressTint) {
    839                     target.setTintList(mProgressTintInfo.mProgressTintList);
    840                 }
    841                 if (mProgressTintInfo.mHasProgressTintMode) {
    842                     target.setTintMode(mProgressTintInfo.mProgressTintMode);
    843                 }
    844 
    845                 // The drawable (or one of its children) may not have been
    846                 // stateful before applying the tint, so let's try again.
    847                 if (target.isStateful()) {
    848                     target.setState(getDrawableState());
    849                 }
    850             }
    851         }
    852     }
    853 
    854     /**
    855      * Should only be called if we've already verified that mProgressDrawable
    856      * and mProgressTintInfo are non-null.
    857      */
    858     private void applyProgressBackgroundTint() {
    859         if (mProgressTintInfo.mHasProgressBackgroundTint
    860                 || mProgressTintInfo.mHasProgressBackgroundTintMode) {
    861             final Drawable target = getTintTarget(R.id.background, false);
    862             if (target != null) {
    863                 if (mProgressTintInfo.mHasProgressBackgroundTint) {
    864                     target.setTintList(mProgressTintInfo.mProgressBackgroundTintList);
    865                 }
    866                 if (mProgressTintInfo.mHasProgressBackgroundTintMode) {
    867                     target.setTintMode(mProgressTintInfo.mProgressBackgroundTintMode);
    868                 }
    869 
    870                 // The drawable (or one of its children) may not have been
    871                 // stateful before applying the tint, so let's try again.
    872                 if (target.isStateful()) {
    873                     target.setState(getDrawableState());
    874                 }
    875             }
    876         }
    877     }
    878 
    879     /**
    880      * Should only be called if we've already verified that mProgressDrawable
    881      * and mProgressTintInfo are non-null.
    882      */
    883     private void applySecondaryProgressTint() {
    884         if (mProgressTintInfo.mHasSecondaryProgressTint
    885                 || mProgressTintInfo.mHasSecondaryProgressTintMode) {
    886             final Drawable target = getTintTarget(R.id.secondaryProgress, false);
    887             if (target != null) {
    888                 if (mProgressTintInfo.mHasSecondaryProgressTint) {
    889                     target.setTintList(mProgressTintInfo.mSecondaryProgressTintList);
    890                 }
    891                 if (mProgressTintInfo.mHasSecondaryProgressTintMode) {
    892                     target.setTintMode(mProgressTintInfo.mSecondaryProgressTintMode);
    893                 }
    894 
    895                 // The drawable (or one of its children) may not have been
    896                 // stateful before applying the tint, so let's try again.
    897                 if (target.isStateful()) {
    898                     target.setState(getDrawableState());
    899                 }
    900             }
    901         }
    902     }
    903 
    904     /**
    905      * Applies a tint to the progress indicator, if one exists, or to the
    906      * entire progress drawable otherwise. Does not modify the current tint
    907      * mode, which is {@link PorterDuff.Mode#SRC_IN} by default.
    908      * <p>
    909      * The progress indicator should be specified as a layer with
    910      * id {@link android.R.id#progress} in a {@link LayerDrawable}
    911      * used as the progress drawable.
    912      * <p>
    913      * Subsequent calls to {@link #setProgressDrawable(Drawable)} will
    914      * automatically mutate the drawable and apply the specified tint and
    915      * tint mode using
    916      * {@link Drawable#setTintList(ColorStateList)}.
    917      *
    918      * @param tint the tint to apply, may be {@code null} to clear tint
    919      *
    920      * @attr ref android.R.styleable#ProgressBar_progressTint
    921      * @see #getProgressTintList()
    922      * @see Drawable#setTintList(ColorStateList)
    923      */
    924     @RemotableViewMethod
    925     public void setProgressTintList(@Nullable ColorStateList tint) {
    926         if (mProgressTintInfo == null) {
    927             mProgressTintInfo = new ProgressTintInfo();
    928         }
    929         mProgressTintInfo.mProgressTintList = tint;
    930         mProgressTintInfo.mHasProgressTint = true;
    931 
    932         if (mProgressDrawable != null) {
    933             applyPrimaryProgressTint();
    934         }
    935     }
    936 
    937     /**
    938      * Returns the tint applied to the progress drawable, if specified.
    939      *
    940      * @return the tint applied to the progress drawable
    941      * @attr ref android.R.styleable#ProgressBar_progressTint
    942      * @see #setProgressTintList(ColorStateList)
    943      */
    944     @Nullable
    945     public ColorStateList getProgressTintList() {
    946         return mProgressTintInfo != null ? mProgressTintInfo.mProgressTintList : null;
    947     }
    948 
    949     /**
    950      * Specifies the blending mode used to apply the tint specified by
    951      * {@link #setProgressTintList(ColorStateList)}} to the progress
    952      * indicator. The default mode is {@link PorterDuff.Mode#SRC_IN}.
    953      *
    954      * @param tintMode the blending mode used to apply the tint, may be
    955      *                 {@code null} to clear tint
    956      * @attr ref android.R.styleable#ProgressBar_progressTintMode
    957      * @see #getProgressTintMode()
    958      * @see Drawable#setTintMode(PorterDuff.Mode)
    959      */
    960     public void setProgressTintMode(@Nullable PorterDuff.Mode tintMode) {
    961         if (mProgressTintInfo == null) {
    962             mProgressTintInfo = new ProgressTintInfo();
    963         }
    964         mProgressTintInfo.mProgressTintMode = tintMode;
    965         mProgressTintInfo.mHasProgressTintMode = true;
    966 
    967         if (mProgressDrawable != null) {
    968             applyPrimaryProgressTint();
    969         }
    970     }
    971 
    972     /**
    973      * Returns the blending mode used to apply the tint to the progress
    974      * drawable, if specified.
    975      *
    976      * @return the blending mode used to apply the tint to the progress
    977      *         drawable
    978      * @attr ref android.R.styleable#ProgressBar_progressTintMode
    979      * @see #setProgressTintMode(PorterDuff.Mode)
    980      */
    981     @Nullable
    982     public PorterDuff.Mode getProgressTintMode() {
    983         return mProgressTintInfo != null ? mProgressTintInfo.mProgressTintMode : null;
    984     }
    985 
    986     /**
    987      * Applies a tint to the progress background, if one exists. Does not
    988      * modify the current tint mode, which is
    989      * {@link PorterDuff.Mode#SRC_ATOP} by default.
    990      * <p>
    991      * The progress background must be specified as a layer with
    992      * id {@link android.R.id#background} in a {@link LayerDrawable}
    993      * used as the progress drawable.
    994      * <p>
    995      * Subsequent calls to {@link #setProgressDrawable(Drawable)} where the
    996      * drawable contains a progress background will automatically mutate the
    997      * drawable and apply the specified tint and tint mode using
    998      * {@link Drawable#setTintList(ColorStateList)}.
    999      *
   1000      * @param tint the tint to apply, may be {@code null} to clear tint
   1001      *
   1002      * @attr ref android.R.styleable#ProgressBar_progressBackgroundTint
   1003      * @see #getProgressBackgroundTintList()
   1004      * @see Drawable#setTintList(ColorStateList)
   1005      */
   1006     @RemotableViewMethod
   1007     public void setProgressBackgroundTintList(@Nullable ColorStateList tint) {
   1008         if (mProgressTintInfo == null) {
   1009             mProgressTintInfo = new ProgressTintInfo();
   1010         }
   1011         mProgressTintInfo.mProgressBackgroundTintList = tint;
   1012         mProgressTintInfo.mHasProgressBackgroundTint = true;
   1013 
   1014         if (mProgressDrawable != null) {
   1015             applyProgressBackgroundTint();
   1016         }
   1017     }
   1018 
   1019     /**
   1020      * Returns the tint applied to the progress background, if specified.
   1021      *
   1022      * @return the tint applied to the progress background
   1023      * @attr ref android.R.styleable#ProgressBar_progressBackgroundTint
   1024      * @see #setProgressBackgroundTintList(ColorStateList)
   1025      */
   1026     @Nullable
   1027     public ColorStateList getProgressBackgroundTintList() {
   1028         return mProgressTintInfo != null ? mProgressTintInfo.mProgressBackgroundTintList : null;
   1029     }
   1030 
   1031     /**
   1032      * Specifies the blending mode used to apply the tint specified by
   1033      * {@link #setProgressBackgroundTintList(ColorStateList)}} to the progress
   1034      * background. The default mode is {@link PorterDuff.Mode#SRC_IN}.
   1035      *
   1036      * @param tintMode the blending mode used to apply the tint, may be
   1037      *                 {@code null} to clear tint
   1038      * @attr ref android.R.styleable#ProgressBar_progressBackgroundTintMode
   1039      * @see #setProgressBackgroundTintList(ColorStateList)
   1040      * @see Drawable#setTintMode(PorterDuff.Mode)
   1041      */
   1042     public void setProgressBackgroundTintMode(@Nullable PorterDuff.Mode tintMode) {
   1043         if (mProgressTintInfo == null) {
   1044             mProgressTintInfo = new ProgressTintInfo();
   1045         }
   1046         mProgressTintInfo.mProgressBackgroundTintMode = tintMode;
   1047         mProgressTintInfo.mHasProgressBackgroundTintMode = true;
   1048 
   1049         if (mProgressDrawable != null) {
   1050             applyProgressBackgroundTint();
   1051         }
   1052     }
   1053 
   1054     /**
   1055      * @return the blending mode used to apply the tint to the progress
   1056      *         background
   1057      * @attr ref android.R.styleable#ProgressBar_progressBackgroundTintMode
   1058      * @see #setProgressBackgroundTintMode(PorterDuff.Mode)
   1059      */
   1060     @Nullable
   1061     public PorterDuff.Mode getProgressBackgroundTintMode() {
   1062         return mProgressTintInfo != null ? mProgressTintInfo.mProgressBackgroundTintMode : null;
   1063     }
   1064 
   1065     /**
   1066      * Applies a tint to the secondary progress indicator, if one exists.
   1067      * Does not modify the current tint mode, which is
   1068      * {@link PorterDuff.Mode#SRC_ATOP} by default.
   1069      * <p>
   1070      * The secondary progress indicator must be specified as a layer with
   1071      * id {@link android.R.id#secondaryProgress} in a {@link LayerDrawable}
   1072      * used as the progress drawable.
   1073      * <p>
   1074      * Subsequent calls to {@link #setProgressDrawable(Drawable)} where the
   1075      * drawable contains a secondary progress indicator will automatically
   1076      * mutate the drawable and apply the specified tint and tint mode using
   1077      * {@link Drawable#setTintList(ColorStateList)}.
   1078      *
   1079      * @param tint the tint to apply, may be {@code null} to clear tint
   1080      *
   1081      * @attr ref android.R.styleable#ProgressBar_secondaryProgressTint
   1082      * @see #getSecondaryProgressTintList()
   1083      * @see Drawable#setTintList(ColorStateList)
   1084      */
   1085     public void setSecondaryProgressTintList(@Nullable ColorStateList tint) {
   1086         if (mProgressTintInfo == null) {
   1087             mProgressTintInfo = new ProgressTintInfo();
   1088         }
   1089         mProgressTintInfo.mSecondaryProgressTintList = tint;
   1090         mProgressTintInfo.mHasSecondaryProgressTint = true;
   1091 
   1092         if (mProgressDrawable != null) {
   1093             applySecondaryProgressTint();
   1094         }
   1095     }
   1096 
   1097     /**
   1098      * Returns the tint applied to the secondary progress drawable, if
   1099      * specified.
   1100      *
   1101      * @return the tint applied to the secondary progress drawable
   1102      * @attr ref android.R.styleable#ProgressBar_secondaryProgressTint
   1103      * @see #setSecondaryProgressTintList(ColorStateList)
   1104      */
   1105     @Nullable
   1106     public ColorStateList getSecondaryProgressTintList() {
   1107         return mProgressTintInfo != null ? mProgressTintInfo.mSecondaryProgressTintList : null;
   1108     }
   1109 
   1110     /**
   1111      * Specifies the blending mode used to apply the tint specified by
   1112      * {@link #setSecondaryProgressTintList(ColorStateList)}} to the secondary
   1113      * progress indicator. The default mode is
   1114      * {@link PorterDuff.Mode#SRC_ATOP}.
   1115      *
   1116      * @param tintMode the blending mode used to apply the tint, may be
   1117      *                 {@code null} to clear tint
   1118      * @attr ref android.R.styleable#ProgressBar_secondaryProgressTintMode
   1119      * @see #setSecondaryProgressTintList(ColorStateList)
   1120      * @see Drawable#setTintMode(PorterDuff.Mode)
   1121      */
   1122     public void setSecondaryProgressTintMode(@Nullable PorterDuff.Mode tintMode) {
   1123         if (mProgressTintInfo == null) {
   1124             mProgressTintInfo = new ProgressTintInfo();
   1125         }
   1126         mProgressTintInfo.mSecondaryProgressTintMode = tintMode;
   1127         mProgressTintInfo.mHasSecondaryProgressTintMode = true;
   1128 
   1129         if (mProgressDrawable != null) {
   1130             applySecondaryProgressTint();
   1131         }
   1132     }
   1133 
   1134     /**
   1135      * Returns the blending mode used to apply the tint to the secondary
   1136      * progress drawable, if specified.
   1137      *
   1138      * @return the blending mode used to apply the tint to the secondary
   1139      *         progress drawable
   1140      * @attr ref android.R.styleable#ProgressBar_secondaryProgressTintMode
   1141      * @see #setSecondaryProgressTintMode(PorterDuff.Mode)
   1142      */
   1143     @Nullable
   1144     public PorterDuff.Mode getSecondaryProgressTintMode() {
   1145         return mProgressTintInfo != null ? mProgressTintInfo.mSecondaryProgressTintMode : null;
   1146     }
   1147 
   1148     /**
   1149      * Returns the drawable to which a tint or tint mode should be applied.
   1150      *
   1151      * @param layerId id of the layer to modify
   1152      * @param shouldFallback whether the base drawable should be returned
   1153      *                       if the id does not exist
   1154      * @return the drawable to modify
   1155      */
   1156     @Nullable
   1157     private Drawable getTintTarget(int layerId, boolean shouldFallback) {
   1158         Drawable layer = null;
   1159 
   1160         final Drawable d = mProgressDrawable;
   1161         if (d != null) {
   1162             mProgressDrawable = d.mutate();
   1163 
   1164             if (d instanceof LayerDrawable) {
   1165                 layer = ((LayerDrawable) d).findDrawableByLayerId(layerId);
   1166             }
   1167 
   1168             if (shouldFallback && layer == null) {
   1169                 layer = d;
   1170             }
   1171         }
   1172 
   1173         return layer;
   1174     }
   1175 
   1176     /**
   1177      * Define the tileable drawable used to draw the progress bar in
   1178      * progress mode.
   1179      * <p>
   1180      * If the drawable is a BitmapDrawable or contains BitmapDrawables, a
   1181      * tiled copy will be generated for display as a progress bar.
   1182      *
   1183      * @param d the new drawable
   1184      * @see #getProgressDrawable()
   1185      * @see #setIndeterminate(boolean)
   1186      */
   1187     public void setProgressDrawableTiled(Drawable d) {
   1188         if (d != null) {
   1189             d = tileify(d, false);
   1190         }
   1191 
   1192         setProgressDrawable(d);
   1193     }
   1194 
   1195     /**
   1196      * @return The drawable currently used to draw the progress bar
   1197      */
   1198     Drawable getCurrentDrawable() {
   1199         return mCurrentDrawable;
   1200     }
   1201 
   1202     @Override
   1203     protected boolean verifyDrawable(@NonNull Drawable who) {
   1204         return who == mProgressDrawable || who == mIndeterminateDrawable
   1205                 || super.verifyDrawable(who);
   1206     }
   1207 
   1208     @Override
   1209     public void jumpDrawablesToCurrentState() {
   1210         super.jumpDrawablesToCurrentState();
   1211         if (mProgressDrawable != null) mProgressDrawable.jumpToCurrentState();
   1212         if (mIndeterminateDrawable != null) mIndeterminateDrawable.jumpToCurrentState();
   1213     }
   1214 
   1215     /**
   1216      * @hide
   1217      */
   1218     @Override
   1219     public void onResolveDrawables(int layoutDirection) {
   1220         final Drawable d = mCurrentDrawable;
   1221         if (d != null) {
   1222             d.setLayoutDirection(layoutDirection);
   1223         }
   1224         if (mIndeterminateDrawable != null) {
   1225             mIndeterminateDrawable.setLayoutDirection(layoutDirection);
   1226         }
   1227         if (mProgressDrawable != null) {
   1228             mProgressDrawable.setLayoutDirection(layoutDirection);
   1229         }
   1230     }
   1231 
   1232     @Override
   1233     public void postInvalidate() {
   1234         if (!mNoInvalidate) {
   1235             super.postInvalidate();
   1236         }
   1237     }
   1238 
   1239     private class RefreshProgressRunnable implements Runnable {
   1240         public void run() {
   1241             synchronized (ProgressBar.this) {
   1242                 final int count = mRefreshData.size();
   1243                 for (int i = 0; i < count; i++) {
   1244                     final RefreshData rd = mRefreshData.get(i);
   1245                     doRefreshProgress(rd.id, rd.progress, rd.fromUser, true, rd.animate);
   1246                     rd.recycle();
   1247                 }
   1248                 mRefreshData.clear();
   1249                 mRefreshIsPosted = false;
   1250             }
   1251         }
   1252     }
   1253 
   1254     private static class RefreshData {
   1255         private static final int POOL_MAX = 24;
   1256         private static final SynchronizedPool<RefreshData> sPool =
   1257                 new SynchronizedPool<RefreshData>(POOL_MAX);
   1258 
   1259         public int id;
   1260         public int progress;
   1261         public boolean fromUser;
   1262         public boolean animate;
   1263 
   1264         public static RefreshData obtain(int id, int progress, boolean fromUser, boolean animate) {
   1265             RefreshData rd = sPool.acquire();
   1266             if (rd == null) {
   1267                 rd = new RefreshData();
   1268             }
   1269             rd.id = id;
   1270             rd.progress = progress;
   1271             rd.fromUser = fromUser;
   1272             rd.animate = animate;
   1273             return rd;
   1274         }
   1275 
   1276         public void recycle() {
   1277             sPool.release(this);
   1278         }
   1279     }
   1280 
   1281     private synchronized void doRefreshProgress(int id, int progress, boolean fromUser,
   1282             boolean callBackToApp, boolean animate) {
   1283         int range = mMax - mMin;
   1284         final float scale = range > 0 ? (progress - mMin) / (float) range : 0;
   1285         final boolean isPrimary = id == R.id.progress;
   1286 
   1287         if (isPrimary && animate) {
   1288             final ObjectAnimator animator = ObjectAnimator.ofFloat(this, VISUAL_PROGRESS, scale);
   1289             animator.setAutoCancel(true);
   1290             animator.setDuration(PROGRESS_ANIM_DURATION);
   1291             animator.setInterpolator(PROGRESS_ANIM_INTERPOLATOR);
   1292             animator.start();
   1293         } else {
   1294             setVisualProgress(id, scale);
   1295         }
   1296 
   1297         if (isPrimary && callBackToApp) {
   1298             onProgressRefresh(scale, fromUser, progress);
   1299         }
   1300     }
   1301 
   1302     void onProgressRefresh(float scale, boolean fromUser, int progress) {
   1303         if (AccessibilityManager.getInstance(mContext).isEnabled()) {
   1304             scheduleAccessibilityEventSender();
   1305         }
   1306     }
   1307 
   1308     /**
   1309      * Sets the visual state of a progress indicator.
   1310      *
   1311      * @param id the identifier of the progress indicator
   1312      * @param progress the visual progress in the range [0...1]
   1313      */
   1314     private void setVisualProgress(int id, float progress) {
   1315         mVisualProgress = progress;
   1316 
   1317         Drawable d = mCurrentDrawable;
   1318 
   1319         if (d instanceof LayerDrawable) {
   1320             d = ((LayerDrawable) d).findDrawableByLayerId(id);
   1321             if (d == null) {
   1322                 // If we can't find the requested layer, fall back to setting
   1323                 // the level of the entire drawable. This will break if
   1324                 // progress is set on multiple elements, but the theme-default
   1325                 // drawable will always have all layer IDs present.
   1326                 d = mCurrentDrawable;
   1327             }
   1328         }
   1329 
   1330         if (d != null) {
   1331             final int level = (int) (progress * MAX_LEVEL);
   1332             d.setLevel(level);
   1333         } else {
   1334             invalidate();
   1335         }
   1336 
   1337         onVisualProgressChanged(id, progress);
   1338     }
   1339 
   1340     /**
   1341      * Called when the visual state of a progress indicator changes.
   1342      *
   1343      * @param id the identifier of the progress indicator
   1344      * @param progress the visual progress in the range [0...1]
   1345      */
   1346     void onVisualProgressChanged(int id, float progress) {
   1347         // Stub method.
   1348     }
   1349 
   1350     private synchronized void refreshProgress(int id, int progress, boolean fromUser,
   1351             boolean animate) {
   1352         if (mUiThreadId == Thread.currentThread().getId()) {
   1353             doRefreshProgress(id, progress, fromUser, true, animate);
   1354         } else {
   1355             if (mRefreshProgressRunnable == null) {
   1356                 mRefreshProgressRunnable = new RefreshProgressRunnable();
   1357             }
   1358 
   1359             final RefreshData rd = RefreshData.obtain(id, progress, fromUser, animate);
   1360             mRefreshData.add(rd);
   1361             if (mAttached && !mRefreshIsPosted) {
   1362                 post(mRefreshProgressRunnable);
   1363                 mRefreshIsPosted = true;
   1364             }
   1365         }
   1366     }
   1367 
   1368     /**
   1369      * Sets the current progress to the specified value. Does not do anything
   1370      * if the progress bar is in indeterminate mode.
   1371      * <p>
   1372      * This method will immediately update the visual position of the progress
   1373      * indicator. To animate the visual position to the target value, use
   1374      * {@link #setProgress(int, boolean)}}.
   1375      *
   1376      * @param progress the new progress, between 0 and {@link #getMax()}
   1377      *
   1378      * @see #setIndeterminate(boolean)
   1379      * @see #isIndeterminate()
   1380      * @see #getProgress()
   1381      * @see #incrementProgressBy(int)
   1382      */
   1383     @android.view.RemotableViewMethod
   1384     public synchronized void setProgress(int progress) {
   1385         setProgressInternal(progress, false, false);
   1386     }
   1387 
   1388     /**
   1389      * Sets the current progress to the specified value, optionally animating
   1390      * the visual position between the current and target values.
   1391      * <p>
   1392      * Animation does not affect the result of {@link #getProgress()}, which
   1393      * will return the target value immediately after this method is called.
   1394      *
   1395      * @param progress the new progress value, between 0 and {@link #getMax()}
   1396      * @param animate {@code true} to animate between the current and target
   1397      *                values or {@code false} to not animate
   1398      */
   1399     public void setProgress(int progress, boolean animate) {
   1400         setProgressInternal(progress, false, animate);
   1401     }
   1402 
   1403     @android.view.RemotableViewMethod
   1404     synchronized boolean setProgressInternal(int progress, boolean fromUser, boolean animate) {
   1405         if (mIndeterminate) {
   1406             // Not applicable.
   1407             return false;
   1408         }
   1409 
   1410         progress = MathUtils.constrain(progress, mMin, mMax);
   1411 
   1412         if (progress == mProgress) {
   1413             // No change from current.
   1414             return false;
   1415         }
   1416 
   1417         mProgress = progress;
   1418         refreshProgress(R.id.progress, mProgress, fromUser, animate);
   1419         return true;
   1420     }
   1421 
   1422     /**
   1423      * <p>
   1424      * Set the current secondary progress to the specified value. Does not do
   1425      * anything if the progress bar is in indeterminate mode.
   1426      * </p>
   1427      *
   1428      * @param secondaryProgress the new secondary progress, between 0 and {@link #getMax()}
   1429      * @see #setIndeterminate(boolean)
   1430      * @see #isIndeterminate()
   1431      * @see #getSecondaryProgress()
   1432      * @see #incrementSecondaryProgressBy(int)
   1433      */
   1434     @android.view.RemotableViewMethod
   1435     public synchronized void setSecondaryProgress(int secondaryProgress) {
   1436         if (mIndeterminate) {
   1437             return;
   1438         }
   1439 
   1440         if (secondaryProgress < mMin) {
   1441             secondaryProgress = mMin;
   1442         }
   1443 
   1444         if (secondaryProgress > mMax) {
   1445             secondaryProgress = mMax;
   1446         }
   1447 
   1448         if (secondaryProgress != mSecondaryProgress) {
   1449             mSecondaryProgress = secondaryProgress;
   1450             refreshProgress(R.id.secondaryProgress, mSecondaryProgress, false, false);
   1451         }
   1452     }
   1453 
   1454     /**
   1455      * <p>Get the progress bar's current level of progress. Return 0 when the
   1456      * progress bar is in indeterminate mode.</p>
   1457      *
   1458      * @return the current progress, between 0 and {@link #getMax()}
   1459      *
   1460      * @see #setIndeterminate(boolean)
   1461      * @see #isIndeterminate()
   1462      * @see #setProgress(int)
   1463      * @see #setMax(int)
   1464      * @see #getMax()
   1465      */
   1466     @ViewDebug.ExportedProperty(category = "progress")
   1467     public synchronized int getProgress() {
   1468         return mIndeterminate ? 0 : mProgress;
   1469     }
   1470 
   1471     /**
   1472      * <p>Get the progress bar's current level of secondary progress. Return 0 when the
   1473      * progress bar is in indeterminate mode.</p>
   1474      *
   1475      * @return the current secondary progress, between 0 and {@link #getMax()}
   1476      *
   1477      * @see #setIndeterminate(boolean)
   1478      * @see #isIndeterminate()
   1479      * @see #setSecondaryProgress(int)
   1480      * @see #setMax(int)
   1481      * @see #getMax()
   1482      */
   1483     @ViewDebug.ExportedProperty(category = "progress")
   1484     public synchronized int getSecondaryProgress() {
   1485         return mIndeterminate ? 0 : mSecondaryProgress;
   1486     }
   1487 
   1488     /**
   1489      * <p>Return the lower limit of this progress bar's range.</p>
   1490      *
   1491      * @return a positive integer
   1492      *
   1493      * @see #setMin(int)
   1494      * @see #getProgress()
   1495      * @see #getSecondaryProgress()
   1496      */
   1497     @ViewDebug.ExportedProperty(category = "progress")
   1498     public synchronized int getMin() {
   1499         return mMin;
   1500     }
   1501 
   1502     /**
   1503      * <p>Return the upper limit of this progress bar's range.</p>
   1504      *
   1505      * @return a positive integer
   1506      *
   1507      * @see #setMax(int)
   1508      * @see #getProgress()
   1509      * @see #getSecondaryProgress()
   1510      */
   1511     @ViewDebug.ExportedProperty(category = "progress")
   1512     public synchronized int getMax() {
   1513         return mMax;
   1514     }
   1515 
   1516     /**
   1517      * <p>Set the lower range of the progress bar to <tt>min</tt>.</p>
   1518      *
   1519      * @param min the lower range of this progress bar
   1520      *
   1521      * @see #getMin()
   1522      * @see #setProgress(int)
   1523      * @see #setSecondaryProgress(int)
   1524      */
   1525     @android.view.RemotableViewMethod
   1526     public synchronized void setMin(int min) {
   1527         if (mMaxInitialized) {
   1528             if (min > mMax) {
   1529                 min = mMax;
   1530             }
   1531         }
   1532         mMinInitialized = true;
   1533         if (mMaxInitialized && min != mMin) {
   1534             mMin = min;
   1535             postInvalidate();
   1536 
   1537             if (mProgress < min) {
   1538                 mProgress = min;
   1539             }
   1540             refreshProgress(R.id.progress, mProgress, false, false);
   1541         } else {
   1542             mMin = min;
   1543         }
   1544     }
   1545 
   1546     /**
   1547      * <p>Set the upper range of the progress bar <tt>max</tt>.</p>
   1548      *
   1549      * @param max the upper range of this progress bar
   1550      *
   1551      * @see #getMax()
   1552      * @see #setProgress(int)
   1553      * @see #setSecondaryProgress(int)
   1554      */
   1555     @android.view.RemotableViewMethod
   1556     public synchronized void setMax(int max) {
   1557         if (mMinInitialized) {
   1558             if (max < mMin) {
   1559                 max = mMin;
   1560             }
   1561         }
   1562         mMaxInitialized = true;
   1563         if (mMinInitialized && max != mMax) {
   1564             mMax = max;
   1565             postInvalidate();
   1566 
   1567             if (mProgress > max) {
   1568                 mProgress = max;
   1569             }
   1570             refreshProgress(R.id.progress, mProgress, false, false);
   1571         } else {
   1572             mMax = max;
   1573         }
   1574     }
   1575 
   1576     /**
   1577      * <p>Increase the progress bar's progress by the specified amount.</p>
   1578      *
   1579      * @param diff the amount by which the progress must be increased
   1580      *
   1581      * @see #setProgress(int)
   1582      */
   1583     public synchronized final void incrementProgressBy(int diff) {
   1584         setProgress(mProgress + diff);
   1585     }
   1586 
   1587     /**
   1588      * <p>Increase the progress bar's secondary progress by the specified amount.</p>
   1589      *
   1590      * @param diff the amount by which the secondary progress must be increased
   1591      *
   1592      * @see #setSecondaryProgress(int)
   1593      */
   1594     public synchronized final void incrementSecondaryProgressBy(int diff) {
   1595         setSecondaryProgress(mSecondaryProgress + diff);
   1596     }
   1597 
   1598     /**
   1599      * <p>Start the indeterminate progress animation.</p>
   1600      */
   1601     void startAnimation() {
   1602         if (getVisibility() != VISIBLE || getWindowVisibility() != VISIBLE) {
   1603             return;
   1604         }
   1605 
   1606         if (mIndeterminateDrawable instanceof Animatable) {
   1607             mShouldStartAnimationDrawable = true;
   1608             mHasAnimation = false;
   1609         } else {
   1610             mHasAnimation = true;
   1611 
   1612             if (mInterpolator == null) {
   1613                 mInterpolator = new LinearInterpolator();
   1614             }
   1615 
   1616             if (mTransformation == null) {
   1617                 mTransformation = new Transformation();
   1618             } else {
   1619                 mTransformation.clear();
   1620             }
   1621 
   1622             if (mAnimation == null) {
   1623                 mAnimation = new AlphaAnimation(0.0f, 1.0f);
   1624             } else {
   1625                 mAnimation.reset();
   1626             }
   1627 
   1628             mAnimation.setRepeatMode(mBehavior);
   1629             mAnimation.setRepeatCount(Animation.INFINITE);
   1630             mAnimation.setDuration(mDuration);
   1631             mAnimation.setInterpolator(mInterpolator);
   1632             mAnimation.setStartTime(Animation.START_ON_FIRST_FRAME);
   1633         }
   1634         postInvalidate();
   1635     }
   1636 
   1637     /**
   1638      * <p>Stop the indeterminate progress animation.</p>
   1639      */
   1640     void stopAnimation() {
   1641         mHasAnimation = false;
   1642         if (mIndeterminateDrawable instanceof Animatable) {
   1643             ((Animatable) mIndeterminateDrawable).stop();
   1644             mShouldStartAnimationDrawable = false;
   1645         }
   1646         postInvalidate();
   1647     }
   1648 
   1649     /**
   1650      * Sets the acceleration curve for the indeterminate animation.
   1651      * The interpolator is loaded as a resource from the specified context.
   1652      *
   1653      * @param context The application environment
   1654      * @param resID The resource identifier of the interpolator to load
   1655      */
   1656     public void setInterpolator(Context context, @InterpolatorRes int resID) {
   1657         setInterpolator(AnimationUtils.loadInterpolator(context, resID));
   1658     }
   1659 
   1660     /**
   1661      * Sets the acceleration curve for the indeterminate animation.
   1662      * Defaults to a linear interpolation.
   1663      *
   1664      * @param interpolator The interpolator which defines the acceleration curve
   1665      */
   1666     public void setInterpolator(Interpolator interpolator) {
   1667         mInterpolator = interpolator;
   1668     }
   1669 
   1670     /**
   1671      * Gets the acceleration curve type for the indeterminate animation.
   1672      *
   1673      * @return the {@link Interpolator} associated to this animation
   1674      */
   1675     public Interpolator getInterpolator() {
   1676         return mInterpolator;
   1677     }
   1678 
   1679     @Override
   1680     public void onVisibilityAggregated(boolean isVisible) {
   1681         super.onVisibilityAggregated(isVisible);
   1682 
   1683         if (isVisible != mAggregatedIsVisible) {
   1684             mAggregatedIsVisible = isVisible;
   1685 
   1686             if (mIndeterminate) {
   1687                 // let's be nice with the UI thread
   1688                 if (isVisible) {
   1689                     startAnimation();
   1690                 } else {
   1691                     stopAnimation();
   1692                 }
   1693             }
   1694 
   1695             if (mCurrentDrawable != null) {
   1696                 mCurrentDrawable.setVisible(isVisible, false);
   1697             }
   1698         }
   1699     }
   1700 
   1701     @Override
   1702     public void invalidateDrawable(@NonNull Drawable dr) {
   1703         if (!mInDrawing) {
   1704             if (verifyDrawable(dr)) {
   1705                 final Rect dirty = dr.getBounds();
   1706                 final int scrollX = mScrollX + mPaddingLeft;
   1707                 final int scrollY = mScrollY + mPaddingTop;
   1708 
   1709                 invalidate(dirty.left + scrollX, dirty.top + scrollY,
   1710                         dirty.right + scrollX, dirty.bottom + scrollY);
   1711             } else {
   1712                 super.invalidateDrawable(dr);
   1713             }
   1714         }
   1715     }
   1716 
   1717     @Override
   1718     protected void onSizeChanged(int w, int h, int oldw, int oldh) {
   1719         updateDrawableBounds(w, h);
   1720     }
   1721 
   1722     private void updateDrawableBounds(int w, int h) {
   1723         // onDraw will translate the canvas so we draw starting at 0,0.
   1724         // Subtract out padding for the purposes of the calculations below.
   1725         w -= mPaddingRight + mPaddingLeft;
   1726         h -= mPaddingTop + mPaddingBottom;
   1727 
   1728         int right = w;
   1729         int bottom = h;
   1730         int top = 0;
   1731         int left = 0;
   1732 
   1733         if (mIndeterminateDrawable != null) {
   1734             // Aspect ratio logic does not apply to AnimationDrawables
   1735             if (mOnlyIndeterminate && !(mIndeterminateDrawable instanceof AnimationDrawable)) {
   1736                 // Maintain aspect ratio. Certain kinds of animated drawables
   1737                 // get very confused otherwise.
   1738                 final int intrinsicWidth = mIndeterminateDrawable.getIntrinsicWidth();
   1739                 final int intrinsicHeight = mIndeterminateDrawable.getIntrinsicHeight();
   1740                 final float intrinsicAspect = (float) intrinsicWidth / intrinsicHeight;
   1741                 final float boundAspect = (float) w / h;
   1742                 if (intrinsicAspect != boundAspect) {
   1743                     if (boundAspect > intrinsicAspect) {
   1744                         // New width is larger. Make it smaller to match height.
   1745                         final int width = (int) (h * intrinsicAspect);
   1746                         left = (w - width) / 2;
   1747                         right = left + width;
   1748                     } else {
   1749                         // New height is larger. Make it smaller to match width.
   1750                         final int height = (int) (w * (1 / intrinsicAspect));
   1751                         top = (h - height) / 2;
   1752                         bottom = top + height;
   1753                     }
   1754                 }
   1755             }
   1756             if (isLayoutRtl() && mMirrorForRtl) {
   1757                 int tempLeft = left;
   1758                 left = w - right;
   1759                 right = w - tempLeft;
   1760             }
   1761             mIndeterminateDrawable.setBounds(left, top, right, bottom);
   1762         }
   1763 
   1764         if (mProgressDrawable != null) {
   1765             mProgressDrawable.setBounds(0, 0, right, bottom);
   1766         }
   1767     }
   1768 
   1769     @Override
   1770     protected synchronized void onDraw(Canvas canvas) {
   1771         super.onDraw(canvas);
   1772 
   1773         drawTrack(canvas);
   1774     }
   1775 
   1776     /**
   1777      * Draws the progress bar track.
   1778      */
   1779     void drawTrack(Canvas canvas) {
   1780         final Drawable d = mCurrentDrawable;
   1781         if (d != null) {
   1782             // Translate canvas so a indeterminate circular progress bar with padding
   1783             // rotates properly in its animation
   1784             final int saveCount = canvas.save();
   1785 
   1786             if (isLayoutRtl() && mMirrorForRtl) {
   1787                 canvas.translate(getWidth() - mPaddingRight, mPaddingTop);
   1788                 canvas.scale(-1.0f, 1.0f);
   1789             } else {
   1790                 canvas.translate(mPaddingLeft, mPaddingTop);
   1791             }
   1792 
   1793             final long time = getDrawingTime();
   1794             if (mHasAnimation) {
   1795                 mAnimation.getTransformation(time, mTransformation);
   1796                 final float scale = mTransformation.getAlpha();
   1797                 try {
   1798                     mInDrawing = true;
   1799                     d.setLevel((int) (scale * MAX_LEVEL));
   1800                 } finally {
   1801                     mInDrawing = false;
   1802                 }
   1803                 postInvalidateOnAnimation();
   1804             }
   1805 
   1806             d.draw(canvas);
   1807             canvas.restoreToCount(saveCount);
   1808 
   1809             if (mShouldStartAnimationDrawable && d instanceof Animatable) {
   1810                 ((Animatable) d).start();
   1811                 mShouldStartAnimationDrawable = false;
   1812             }
   1813         }
   1814     }
   1815 
   1816     @Override
   1817     protected synchronized void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
   1818         int dw = 0;
   1819         int dh = 0;
   1820 
   1821         final Drawable d = mCurrentDrawable;
   1822         if (d != null) {
   1823             dw = Math.max(mMinWidth, Math.min(mMaxWidth, d.getIntrinsicWidth()));
   1824             dh = Math.max(mMinHeight, Math.min(mMaxHeight, d.getIntrinsicHeight()));
   1825         }
   1826 
   1827         updateDrawableState();
   1828 
   1829         dw += mPaddingLeft + mPaddingRight;
   1830         dh += mPaddingTop + mPaddingBottom;
   1831 
   1832         final int measuredWidth = resolveSizeAndState(dw, widthMeasureSpec, 0);
   1833         final int measuredHeight = resolveSizeAndState(dh, heightMeasureSpec, 0);
   1834         setMeasuredDimension(measuredWidth, measuredHeight);
   1835     }
   1836 
   1837     @Override
   1838     protected void drawableStateChanged() {
   1839         super.drawableStateChanged();
   1840         updateDrawableState();
   1841     }
   1842 
   1843     private void updateDrawableState() {
   1844         final int[] state = getDrawableState();
   1845         boolean changed = false;
   1846 
   1847         final Drawable progressDrawable = mProgressDrawable;
   1848         if (progressDrawable != null && progressDrawable.isStateful()) {
   1849             changed |= progressDrawable.setState(state);
   1850         }
   1851 
   1852         final Drawable indeterminateDrawable = mIndeterminateDrawable;
   1853         if (indeterminateDrawable != null && indeterminateDrawable.isStateful()) {
   1854             changed |= indeterminateDrawable.setState(state);
   1855         }
   1856 
   1857         if (changed) {
   1858             invalidate();
   1859         }
   1860     }
   1861 
   1862     @Override
   1863     public void drawableHotspotChanged(float x, float y) {
   1864         super.drawableHotspotChanged(x, y);
   1865 
   1866         if (mProgressDrawable != null) {
   1867             mProgressDrawable.setHotspot(x, y);
   1868         }
   1869 
   1870         if (mIndeterminateDrawable != null) {
   1871             mIndeterminateDrawable.setHotspot(x, y);
   1872         }
   1873     }
   1874 
   1875     static class SavedState extends BaseSavedState {
   1876         int progress;
   1877         int secondaryProgress;
   1878 
   1879         /**
   1880          * Constructor called from {@link ProgressBar#onSaveInstanceState()}
   1881          */
   1882         SavedState(Parcelable superState) {
   1883             super(superState);
   1884         }
   1885 
   1886         /**
   1887          * Constructor called from {@link #CREATOR}
   1888          */
   1889         private SavedState(Parcel in) {
   1890             super(in);
   1891             progress = in.readInt();
   1892             secondaryProgress = in.readInt();
   1893         }
   1894 
   1895         @Override
   1896         public void writeToParcel(Parcel out, int flags) {
   1897             super.writeToParcel(out, flags);
   1898             out.writeInt(progress);
   1899             out.writeInt(secondaryProgress);
   1900         }
   1901 
   1902         public static final Parcelable.Creator<SavedState> CREATOR
   1903                 = new Parcelable.Creator<SavedState>() {
   1904             public SavedState createFromParcel(Parcel in) {
   1905                 return new SavedState(in);
   1906             }
   1907 
   1908             public SavedState[] newArray(int size) {
   1909                 return new SavedState[size];
   1910             }
   1911         };
   1912     }
   1913 
   1914     @Override
   1915     public Parcelable onSaveInstanceState() {
   1916         // Force our ancestor class to save its state
   1917         Parcelable superState = super.onSaveInstanceState();
   1918         SavedState ss = new SavedState(superState);
   1919 
   1920         ss.progress = mProgress;
   1921         ss.secondaryProgress = mSecondaryProgress;
   1922 
   1923         return ss;
   1924     }
   1925 
   1926     @Override
   1927     public void onRestoreInstanceState(Parcelable state) {
   1928         SavedState ss = (SavedState) state;
   1929         super.onRestoreInstanceState(ss.getSuperState());
   1930 
   1931         setProgress(ss.progress);
   1932         setSecondaryProgress(ss.secondaryProgress);
   1933     }
   1934 
   1935     @Override
   1936     protected void onAttachedToWindow() {
   1937         super.onAttachedToWindow();
   1938         if (mIndeterminate) {
   1939             startAnimation();
   1940         }
   1941         if (mRefreshData != null) {
   1942             synchronized (this) {
   1943                 final int count = mRefreshData.size();
   1944                 for (int i = 0; i < count; i++) {
   1945                     final RefreshData rd = mRefreshData.get(i);
   1946                     doRefreshProgress(rd.id, rd.progress, rd.fromUser, true, rd.animate);
   1947                     rd.recycle();
   1948                 }
   1949                 mRefreshData.clear();
   1950             }
   1951         }
   1952         mAttached = true;
   1953     }
   1954 
   1955     @Override
   1956     protected void onDetachedFromWindow() {
   1957         if (mIndeterminate) {
   1958             stopAnimation();
   1959         }
   1960         if (mRefreshProgressRunnable != null) {
   1961             removeCallbacks(mRefreshProgressRunnable);
   1962             mRefreshIsPosted = false;
   1963         }
   1964         if (mAccessibilityEventSender != null) {
   1965             removeCallbacks(mAccessibilityEventSender);
   1966         }
   1967         // This should come after stopAnimation(), otherwise an invalidate message remains in the
   1968         // queue, which can prevent the entire view hierarchy from being GC'ed during a rotation
   1969         super.onDetachedFromWindow();
   1970         mAttached = false;
   1971     }
   1972 
   1973     @Override
   1974     public CharSequence getAccessibilityClassName() {
   1975         return ProgressBar.class.getName();
   1976     }
   1977 
   1978     /** @hide */
   1979     @Override
   1980     public void onInitializeAccessibilityEventInternal(AccessibilityEvent event) {
   1981         super.onInitializeAccessibilityEventInternal(event);
   1982         event.setItemCount(mMax - mMin);
   1983         event.setCurrentItemIndex(mProgress);
   1984     }
   1985 
   1986     /** @hide */
   1987     @Override
   1988     public void onInitializeAccessibilityNodeInfoInternal(AccessibilityNodeInfo info) {
   1989         super.onInitializeAccessibilityNodeInfoInternal(info);
   1990 
   1991         if (!isIndeterminate()) {
   1992             AccessibilityNodeInfo.RangeInfo rangeInfo = AccessibilityNodeInfo.RangeInfo.obtain(
   1993                     AccessibilityNodeInfo.RangeInfo.RANGE_TYPE_INT, 0, getMax(), getProgress());
   1994             info.setRangeInfo(rangeInfo);
   1995         }
   1996     }
   1997 
   1998     /**
   1999      * Schedule a command for sending an accessibility event.
   2000      * </br>
   2001      * Note: A command is used to ensure that accessibility events
   2002      *       are sent at most one in a given time frame to save
   2003      *       system resources while the progress changes quickly.
   2004      */
   2005     private void scheduleAccessibilityEventSender() {
   2006         if (mAccessibilityEventSender == null) {
   2007             mAccessibilityEventSender = new AccessibilityEventSender();
   2008         } else {
   2009             removeCallbacks(mAccessibilityEventSender);
   2010         }
   2011         postDelayed(mAccessibilityEventSender, TIMEOUT_SEND_ACCESSIBILITY_EVENT);
   2012     }
   2013 
   2014     /** @hide */
   2015     @Override
   2016     protected void encodeProperties(@NonNull ViewHierarchyEncoder stream) {
   2017         super.encodeProperties(stream);
   2018 
   2019         stream.addProperty("progress:max", getMax());
   2020         stream.addProperty("progress:progress", getProgress());
   2021         stream.addProperty("progress:secondaryProgress", getSecondaryProgress());
   2022         stream.addProperty("progress:indeterminate", isIndeterminate());
   2023     }
   2024 
   2025     /**
   2026      * Returns whether the ProgressBar is animating or not. This is essentially the same
   2027      * as whether the ProgressBar is {@link #isIndeterminate() indeterminate} and visible,
   2028      * as indeterminate ProgressBars are always animating, and non-indeterminate
   2029      * ProgressBars are not animating.
   2030      *
   2031      * @return true if the ProgressBar is animating, false otherwise.
   2032      */
   2033     public boolean isAnimating() {
   2034         return isIndeterminate() && getWindowVisibility() == VISIBLE && isShown();
   2035     }
   2036 
   2037     /**
   2038      * Command for sending an accessibility event.
   2039      */
   2040     private class AccessibilityEventSender implements Runnable {
   2041         public void run() {
   2042             sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_SELECTED);
   2043         }
   2044     }
   2045 
   2046     private static class ProgressTintInfo {
   2047         ColorStateList mIndeterminateTintList;
   2048         PorterDuff.Mode mIndeterminateTintMode;
   2049         boolean mHasIndeterminateTint;
   2050         boolean mHasIndeterminateTintMode;
   2051 
   2052         ColorStateList mProgressTintList;
   2053         PorterDuff.Mode mProgressTintMode;
   2054         boolean mHasProgressTint;
   2055         boolean mHasProgressTintMode;
   2056 
   2057         ColorStateList mProgressBackgroundTintList;
   2058         PorterDuff.Mode mProgressBackgroundTintMode;
   2059         boolean mHasProgressBackgroundTint;
   2060         boolean mHasProgressBackgroundTintMode;
   2061 
   2062         ColorStateList mSecondaryProgressTintList;
   2063         PorterDuff.Mode mSecondaryProgressTintMode;
   2064         boolean mHasSecondaryProgressTint;
   2065         boolean mHasSecondaryProgressTintMode;
   2066     }
   2067 
   2068     /**
   2069      * Property wrapper around the visual state of the {@code progress} functionality
   2070      * handled by the {@link ProgressBar#setProgress(int, boolean)} method. This does
   2071      * not correspond directly to the actual progress -- only the visual state.
   2072      */
   2073     private final FloatProperty<ProgressBar> VISUAL_PROGRESS =
   2074             new FloatProperty<ProgressBar>("visual_progress") {
   2075                 @Override
   2076                 public void setValue(ProgressBar object, float value) {
   2077                     object.setVisualProgress(R.id.progress, value);
   2078                     object.mVisualProgress = value;
   2079                 }
   2080 
   2081                 @Override
   2082                 public Float get(ProgressBar object) {
   2083                     return object.mVisualProgress;
   2084                 }
   2085             };
   2086 }
   2087