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 {@link #getMin()} 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 {@link #getMin()} 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 {@link #getMin()} and
   1429      * {@link #getMax()}
   1430      * @see #setIndeterminate(boolean)
   1431      * @see #isIndeterminate()
   1432      * @see #getSecondaryProgress()
   1433      * @see #incrementSecondaryProgressBy(int)
   1434      */
   1435     @android.view.RemotableViewMethod
   1436     public synchronized void setSecondaryProgress(int secondaryProgress) {
   1437         if (mIndeterminate) {
   1438             return;
   1439         }
   1440 
   1441         if (secondaryProgress < mMin) {
   1442             secondaryProgress = mMin;
   1443         }
   1444 
   1445         if (secondaryProgress > mMax) {
   1446             secondaryProgress = mMax;
   1447         }
   1448 
   1449         if (secondaryProgress != mSecondaryProgress) {
   1450             mSecondaryProgress = secondaryProgress;
   1451             refreshProgress(R.id.secondaryProgress, mSecondaryProgress, false, false);
   1452         }
   1453     }
   1454 
   1455     /**
   1456      * <p>Get the progress bar's current level of progress. Return 0 when the
   1457      * progress bar is in indeterminate mode.</p>
   1458      *
   1459      * @return the current progress, between {@link #getMin()} and {@link #getMax()}
   1460      *
   1461      * @see #setIndeterminate(boolean)
   1462      * @see #isIndeterminate()
   1463      * @see #setProgress(int)
   1464      * @see #setMax(int)
   1465      * @see #getMax()
   1466      */
   1467     @ViewDebug.ExportedProperty(category = "progress")
   1468     public synchronized int getProgress() {
   1469         return mIndeterminate ? 0 : mProgress;
   1470     }
   1471 
   1472     /**
   1473      * <p>Get the progress bar's current level of secondary progress. Return 0 when the
   1474      * progress bar is in indeterminate mode.</p>
   1475      *
   1476      * @return the current secondary progress, between {@link #getMin()} and {@link #getMax()}
   1477      *
   1478      * @see #setIndeterminate(boolean)
   1479      * @see #isIndeterminate()
   1480      * @see #setSecondaryProgress(int)
   1481      * @see #setMax(int)
   1482      * @see #getMax()
   1483      */
   1484     @ViewDebug.ExportedProperty(category = "progress")
   1485     public synchronized int getSecondaryProgress() {
   1486         return mIndeterminate ? 0 : mSecondaryProgress;
   1487     }
   1488 
   1489     /**
   1490      * <p>Return the lower limit of this progress bar's range.</p>
   1491      *
   1492      * @return a positive integer
   1493      *
   1494      * @see #setMin(int)
   1495      * @see #getProgress()
   1496      * @see #getSecondaryProgress()
   1497      */
   1498     @ViewDebug.ExportedProperty(category = "progress")
   1499     public synchronized int getMin() {
   1500         return mMin;
   1501     }
   1502 
   1503     /**
   1504      * <p>Return the upper limit of this progress bar's range.</p>
   1505      *
   1506      * @return a positive integer
   1507      *
   1508      * @see #setMax(int)
   1509      * @see #getProgress()
   1510      * @see #getSecondaryProgress()
   1511      */
   1512     @ViewDebug.ExportedProperty(category = "progress")
   1513     public synchronized int getMax() {
   1514         return mMax;
   1515     }
   1516 
   1517     /**
   1518      * <p>Set the lower range of the progress bar to <tt>min</tt>.</p>
   1519      *
   1520      * @param min the lower range of this progress bar
   1521      *
   1522      * @see #getMin()
   1523      * @see #setProgress(int)
   1524      * @see #setSecondaryProgress(int)
   1525      */
   1526     @android.view.RemotableViewMethod
   1527     public synchronized void setMin(int min) {
   1528         if (mMaxInitialized) {
   1529             if (min > mMax) {
   1530                 min = mMax;
   1531             }
   1532         }
   1533         mMinInitialized = true;
   1534         if (mMaxInitialized && min != mMin) {
   1535             mMin = min;
   1536             postInvalidate();
   1537 
   1538             if (mProgress < min) {
   1539                 mProgress = min;
   1540             }
   1541             refreshProgress(R.id.progress, mProgress, false, false);
   1542         } else {
   1543             mMin = min;
   1544         }
   1545     }
   1546 
   1547     /**
   1548      * <p>Set the upper range of the progress bar <tt>max</tt>.</p>
   1549      *
   1550      * @param max the upper range of this progress bar
   1551      *
   1552      * @see #getMax()
   1553      * @see #setProgress(int)
   1554      * @see #setSecondaryProgress(int)
   1555      */
   1556     @android.view.RemotableViewMethod
   1557     public synchronized void setMax(int max) {
   1558         if (mMinInitialized) {
   1559             if (max < mMin) {
   1560                 max = mMin;
   1561             }
   1562         }
   1563         mMaxInitialized = true;
   1564         if (mMinInitialized && max != mMax) {
   1565             mMax = max;
   1566             postInvalidate();
   1567 
   1568             if (mProgress > max) {
   1569                 mProgress = max;
   1570             }
   1571             refreshProgress(R.id.progress, mProgress, false, false);
   1572         } else {
   1573             mMax = max;
   1574         }
   1575     }
   1576 
   1577     /**
   1578      * <p>Increase the progress bar's progress by the specified amount.</p>
   1579      *
   1580      * @param diff the amount by which the progress must be increased
   1581      *
   1582      * @see #setProgress(int)
   1583      */
   1584     public synchronized final void incrementProgressBy(int diff) {
   1585         setProgress(mProgress + diff);
   1586     }
   1587 
   1588     /**
   1589      * <p>Increase the progress bar's secondary progress by the specified amount.</p>
   1590      *
   1591      * @param diff the amount by which the secondary progress must be increased
   1592      *
   1593      * @see #setSecondaryProgress(int)
   1594      */
   1595     public synchronized final void incrementSecondaryProgressBy(int diff) {
   1596         setSecondaryProgress(mSecondaryProgress + diff);
   1597     }
   1598 
   1599     /**
   1600      * <p>Start the indeterminate progress animation.</p>
   1601      */
   1602     void startAnimation() {
   1603         if (getVisibility() != VISIBLE || getWindowVisibility() != VISIBLE) {
   1604             return;
   1605         }
   1606 
   1607         if (mIndeterminateDrawable instanceof Animatable) {
   1608             mShouldStartAnimationDrawable = true;
   1609             mHasAnimation = false;
   1610         } else {
   1611             mHasAnimation = true;
   1612 
   1613             if (mInterpolator == null) {
   1614                 mInterpolator = new LinearInterpolator();
   1615             }
   1616 
   1617             if (mTransformation == null) {
   1618                 mTransformation = new Transformation();
   1619             } else {
   1620                 mTransformation.clear();
   1621             }
   1622 
   1623             if (mAnimation == null) {
   1624                 mAnimation = new AlphaAnimation(0.0f, 1.0f);
   1625             } else {
   1626                 mAnimation.reset();
   1627             }
   1628 
   1629             mAnimation.setRepeatMode(mBehavior);
   1630             mAnimation.setRepeatCount(Animation.INFINITE);
   1631             mAnimation.setDuration(mDuration);
   1632             mAnimation.setInterpolator(mInterpolator);
   1633             mAnimation.setStartTime(Animation.START_ON_FIRST_FRAME);
   1634         }
   1635         postInvalidate();
   1636     }
   1637 
   1638     /**
   1639      * <p>Stop the indeterminate progress animation.</p>
   1640      */
   1641     void stopAnimation() {
   1642         mHasAnimation = false;
   1643         if (mIndeterminateDrawable instanceof Animatable) {
   1644             ((Animatable) mIndeterminateDrawable).stop();
   1645             mShouldStartAnimationDrawable = false;
   1646         }
   1647         postInvalidate();
   1648     }
   1649 
   1650     /**
   1651      * Sets the acceleration curve for the indeterminate animation.
   1652      * The interpolator is loaded as a resource from the specified context.
   1653      *
   1654      * @param context The application environment
   1655      * @param resID The resource identifier of the interpolator to load
   1656      */
   1657     public void setInterpolator(Context context, @InterpolatorRes int resID) {
   1658         setInterpolator(AnimationUtils.loadInterpolator(context, resID));
   1659     }
   1660 
   1661     /**
   1662      * Sets the acceleration curve for the indeterminate animation.
   1663      * Defaults to a linear interpolation.
   1664      *
   1665      * @param interpolator The interpolator which defines the acceleration curve
   1666      */
   1667     public void setInterpolator(Interpolator interpolator) {
   1668         mInterpolator = interpolator;
   1669     }
   1670 
   1671     /**
   1672      * Gets the acceleration curve type for the indeterminate animation.
   1673      *
   1674      * @return the {@link Interpolator} associated to this animation
   1675      */
   1676     public Interpolator getInterpolator() {
   1677         return mInterpolator;
   1678     }
   1679 
   1680     @Override
   1681     public void onVisibilityAggregated(boolean isVisible) {
   1682         super.onVisibilityAggregated(isVisible);
   1683 
   1684         if (isVisible != mAggregatedIsVisible) {
   1685             mAggregatedIsVisible = isVisible;
   1686 
   1687             if (mIndeterminate) {
   1688                 // let's be nice with the UI thread
   1689                 if (isVisible) {
   1690                     startAnimation();
   1691                 } else {
   1692                     stopAnimation();
   1693                 }
   1694             }
   1695 
   1696             if (mCurrentDrawable != null) {
   1697                 mCurrentDrawable.setVisible(isVisible, false);
   1698             }
   1699         }
   1700     }
   1701 
   1702     @Override
   1703     public void invalidateDrawable(@NonNull Drawable dr) {
   1704         if (!mInDrawing) {
   1705             if (verifyDrawable(dr)) {
   1706                 final Rect dirty = dr.getBounds();
   1707                 final int scrollX = mScrollX + mPaddingLeft;
   1708                 final int scrollY = mScrollY + mPaddingTop;
   1709 
   1710                 invalidate(dirty.left + scrollX, dirty.top + scrollY,
   1711                         dirty.right + scrollX, dirty.bottom + scrollY);
   1712             } else {
   1713                 super.invalidateDrawable(dr);
   1714             }
   1715         }
   1716     }
   1717 
   1718     @Override
   1719     protected void onSizeChanged(int w, int h, int oldw, int oldh) {
   1720         updateDrawableBounds(w, h);
   1721     }
   1722 
   1723     private void updateDrawableBounds(int w, int h) {
   1724         // onDraw will translate the canvas so we draw starting at 0,0.
   1725         // Subtract out padding for the purposes of the calculations below.
   1726         w -= mPaddingRight + mPaddingLeft;
   1727         h -= mPaddingTop + mPaddingBottom;
   1728 
   1729         int right = w;
   1730         int bottom = h;
   1731         int top = 0;
   1732         int left = 0;
   1733 
   1734         if (mIndeterminateDrawable != null) {
   1735             // Aspect ratio logic does not apply to AnimationDrawables
   1736             if (mOnlyIndeterminate && !(mIndeterminateDrawable instanceof AnimationDrawable)) {
   1737                 // Maintain aspect ratio. Certain kinds of animated drawables
   1738                 // get very confused otherwise.
   1739                 final int intrinsicWidth = mIndeterminateDrawable.getIntrinsicWidth();
   1740                 final int intrinsicHeight = mIndeterminateDrawable.getIntrinsicHeight();
   1741                 final float intrinsicAspect = (float) intrinsicWidth / intrinsicHeight;
   1742                 final float boundAspect = (float) w / h;
   1743                 if (intrinsicAspect != boundAspect) {
   1744                     if (boundAspect > intrinsicAspect) {
   1745                         // New width is larger. Make it smaller to match height.
   1746                         final int width = (int) (h * intrinsicAspect);
   1747                         left = (w - width) / 2;
   1748                         right = left + width;
   1749                     } else {
   1750                         // New height is larger. Make it smaller to match width.
   1751                         final int height = (int) (w * (1 / intrinsicAspect));
   1752                         top = (h - height) / 2;
   1753                         bottom = top + height;
   1754                     }
   1755                 }
   1756             }
   1757             if (isLayoutRtl() && mMirrorForRtl) {
   1758                 int tempLeft = left;
   1759                 left = w - right;
   1760                 right = w - tempLeft;
   1761             }
   1762             mIndeterminateDrawable.setBounds(left, top, right, bottom);
   1763         }
   1764 
   1765         if (mProgressDrawable != null) {
   1766             mProgressDrawable.setBounds(0, 0, right, bottom);
   1767         }
   1768     }
   1769 
   1770     @Override
   1771     protected synchronized void onDraw(Canvas canvas) {
   1772         super.onDraw(canvas);
   1773 
   1774         drawTrack(canvas);
   1775     }
   1776 
   1777     /**
   1778      * Draws the progress bar track.
   1779      */
   1780     void drawTrack(Canvas canvas) {
   1781         final Drawable d = mCurrentDrawable;
   1782         if (d != null) {
   1783             // Translate canvas so a indeterminate circular progress bar with padding
   1784             // rotates properly in its animation
   1785             final int saveCount = canvas.save();
   1786 
   1787             if (isLayoutRtl() && mMirrorForRtl) {
   1788                 canvas.translate(getWidth() - mPaddingRight, mPaddingTop);
   1789                 canvas.scale(-1.0f, 1.0f);
   1790             } else {
   1791                 canvas.translate(mPaddingLeft, mPaddingTop);
   1792             }
   1793 
   1794             final long time = getDrawingTime();
   1795             if (mHasAnimation) {
   1796                 mAnimation.getTransformation(time, mTransformation);
   1797                 final float scale = mTransformation.getAlpha();
   1798                 try {
   1799                     mInDrawing = true;
   1800                     d.setLevel((int) (scale * MAX_LEVEL));
   1801                 } finally {
   1802                     mInDrawing = false;
   1803                 }
   1804                 postInvalidateOnAnimation();
   1805             }
   1806 
   1807             d.draw(canvas);
   1808             canvas.restoreToCount(saveCount);
   1809 
   1810             if (mShouldStartAnimationDrawable && d instanceof Animatable) {
   1811                 ((Animatable) d).start();
   1812                 mShouldStartAnimationDrawable = false;
   1813             }
   1814         }
   1815     }
   1816 
   1817     @Override
   1818     protected synchronized void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
   1819         int dw = 0;
   1820         int dh = 0;
   1821 
   1822         final Drawable d = mCurrentDrawable;
   1823         if (d != null) {
   1824             dw = Math.max(mMinWidth, Math.min(mMaxWidth, d.getIntrinsicWidth()));
   1825             dh = Math.max(mMinHeight, Math.min(mMaxHeight, d.getIntrinsicHeight()));
   1826         }
   1827 
   1828         updateDrawableState();
   1829 
   1830         dw += mPaddingLeft + mPaddingRight;
   1831         dh += mPaddingTop + mPaddingBottom;
   1832 
   1833         final int measuredWidth = resolveSizeAndState(dw, widthMeasureSpec, 0);
   1834         final int measuredHeight = resolveSizeAndState(dh, heightMeasureSpec, 0);
   1835         setMeasuredDimension(measuredWidth, measuredHeight);
   1836     }
   1837 
   1838     @Override
   1839     protected void drawableStateChanged() {
   1840         super.drawableStateChanged();
   1841         updateDrawableState();
   1842     }
   1843 
   1844     private void updateDrawableState() {
   1845         final int[] state = getDrawableState();
   1846         boolean changed = false;
   1847 
   1848         final Drawable progressDrawable = mProgressDrawable;
   1849         if (progressDrawable != null && progressDrawable.isStateful()) {
   1850             changed |= progressDrawable.setState(state);
   1851         }
   1852 
   1853         final Drawable indeterminateDrawable = mIndeterminateDrawable;
   1854         if (indeterminateDrawable != null && indeterminateDrawable.isStateful()) {
   1855             changed |= indeterminateDrawable.setState(state);
   1856         }
   1857 
   1858         if (changed) {
   1859             invalidate();
   1860         }
   1861     }
   1862 
   1863     @Override
   1864     public void drawableHotspotChanged(float x, float y) {
   1865         super.drawableHotspotChanged(x, y);
   1866 
   1867         if (mProgressDrawable != null) {
   1868             mProgressDrawable.setHotspot(x, y);
   1869         }
   1870 
   1871         if (mIndeterminateDrawable != null) {
   1872             mIndeterminateDrawable.setHotspot(x, y);
   1873         }
   1874     }
   1875 
   1876     static class SavedState extends BaseSavedState {
   1877         int progress;
   1878         int secondaryProgress;
   1879 
   1880         /**
   1881          * Constructor called from {@link ProgressBar#onSaveInstanceState()}
   1882          */
   1883         SavedState(Parcelable superState) {
   1884             super(superState);
   1885         }
   1886 
   1887         /**
   1888          * Constructor called from {@link #CREATOR}
   1889          */
   1890         private SavedState(Parcel in) {
   1891             super(in);
   1892             progress = in.readInt();
   1893             secondaryProgress = in.readInt();
   1894         }
   1895 
   1896         @Override
   1897         public void writeToParcel(Parcel out, int flags) {
   1898             super.writeToParcel(out, flags);
   1899             out.writeInt(progress);
   1900             out.writeInt(secondaryProgress);
   1901         }
   1902 
   1903         public static final Parcelable.Creator<SavedState> CREATOR
   1904                 = new Parcelable.Creator<SavedState>() {
   1905             public SavedState createFromParcel(Parcel in) {
   1906                 return new SavedState(in);
   1907             }
   1908 
   1909             public SavedState[] newArray(int size) {
   1910                 return new SavedState[size];
   1911             }
   1912         };
   1913     }
   1914 
   1915     @Override
   1916     public Parcelable onSaveInstanceState() {
   1917         // Force our ancestor class to save its state
   1918         Parcelable superState = super.onSaveInstanceState();
   1919         SavedState ss = new SavedState(superState);
   1920 
   1921         ss.progress = mProgress;
   1922         ss.secondaryProgress = mSecondaryProgress;
   1923 
   1924         return ss;
   1925     }
   1926 
   1927     @Override
   1928     public void onRestoreInstanceState(Parcelable state) {
   1929         SavedState ss = (SavedState) state;
   1930         super.onRestoreInstanceState(ss.getSuperState());
   1931 
   1932         setProgress(ss.progress);
   1933         setSecondaryProgress(ss.secondaryProgress);
   1934     }
   1935 
   1936     @Override
   1937     protected void onAttachedToWindow() {
   1938         super.onAttachedToWindow();
   1939         if (mIndeterminate) {
   1940             startAnimation();
   1941         }
   1942         if (mRefreshData != null) {
   1943             synchronized (this) {
   1944                 final int count = mRefreshData.size();
   1945                 for (int i = 0; i < count; i++) {
   1946                     final RefreshData rd = mRefreshData.get(i);
   1947                     doRefreshProgress(rd.id, rd.progress, rd.fromUser, true, rd.animate);
   1948                     rd.recycle();
   1949                 }
   1950                 mRefreshData.clear();
   1951             }
   1952         }
   1953         mAttached = true;
   1954     }
   1955 
   1956     @Override
   1957     protected void onDetachedFromWindow() {
   1958         if (mIndeterminate) {
   1959             stopAnimation();
   1960         }
   1961         if (mRefreshProgressRunnable != null) {
   1962             removeCallbacks(mRefreshProgressRunnable);
   1963             mRefreshIsPosted = false;
   1964         }
   1965         if (mAccessibilityEventSender != null) {
   1966             removeCallbacks(mAccessibilityEventSender);
   1967         }
   1968         // This should come after stopAnimation(), otherwise an invalidate message remains in the
   1969         // queue, which can prevent the entire view hierarchy from being GC'ed during a rotation
   1970         super.onDetachedFromWindow();
   1971         mAttached = false;
   1972     }
   1973 
   1974     @Override
   1975     public CharSequence getAccessibilityClassName() {
   1976         return ProgressBar.class.getName();
   1977     }
   1978 
   1979     /** @hide */
   1980     @Override
   1981     public void onInitializeAccessibilityEventInternal(AccessibilityEvent event) {
   1982         super.onInitializeAccessibilityEventInternal(event);
   1983         event.setItemCount(mMax - mMin);
   1984         event.setCurrentItemIndex(mProgress);
   1985     }
   1986 
   1987     /** @hide */
   1988     @Override
   1989     public void onInitializeAccessibilityNodeInfoInternal(AccessibilityNodeInfo info) {
   1990         super.onInitializeAccessibilityNodeInfoInternal(info);
   1991 
   1992         if (!isIndeterminate()) {
   1993             AccessibilityNodeInfo.RangeInfo rangeInfo = AccessibilityNodeInfo.RangeInfo.obtain(
   1994                     AccessibilityNodeInfo.RangeInfo.RANGE_TYPE_INT, getMin(), getMax(),
   1995                     getProgress());
   1996             info.setRangeInfo(rangeInfo);
   1997         }
   1998     }
   1999 
   2000     /**
   2001      * Schedule a command for sending an accessibility event.
   2002      * </br>
   2003      * Note: A command is used to ensure that accessibility events
   2004      *       are sent at most one in a given time frame to save
   2005      *       system resources while the progress changes quickly.
   2006      */
   2007     private void scheduleAccessibilityEventSender() {
   2008         if (mAccessibilityEventSender == null) {
   2009             mAccessibilityEventSender = new AccessibilityEventSender();
   2010         } else {
   2011             removeCallbacks(mAccessibilityEventSender);
   2012         }
   2013         postDelayed(mAccessibilityEventSender, TIMEOUT_SEND_ACCESSIBILITY_EVENT);
   2014     }
   2015 
   2016     /** @hide */
   2017     @Override
   2018     protected void encodeProperties(@NonNull ViewHierarchyEncoder stream) {
   2019         super.encodeProperties(stream);
   2020 
   2021         stream.addProperty("progress:max", getMax());
   2022         stream.addProperty("progress:progress", getProgress());
   2023         stream.addProperty("progress:secondaryProgress", getSecondaryProgress());
   2024         stream.addProperty("progress:indeterminate", isIndeterminate());
   2025     }
   2026 
   2027     /**
   2028      * Returns whether the ProgressBar is animating or not. This is essentially the same
   2029      * as whether the ProgressBar is {@link #isIndeterminate() indeterminate} and visible,
   2030      * as indeterminate ProgressBars are always animating, and non-indeterminate
   2031      * ProgressBars are not animating.
   2032      *
   2033      * @return true if the ProgressBar is animating, false otherwise.
   2034      */
   2035     public boolean isAnimating() {
   2036         return isIndeterminate() && getWindowVisibility() == VISIBLE && isShown();
   2037     }
   2038 
   2039     /**
   2040      * Command for sending an accessibility event.
   2041      */
   2042     private class AccessibilityEventSender implements Runnable {
   2043         public void run() {
   2044             sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_SELECTED);
   2045         }
   2046     }
   2047 
   2048     private static class ProgressTintInfo {
   2049         ColorStateList mIndeterminateTintList;
   2050         PorterDuff.Mode mIndeterminateTintMode;
   2051         boolean mHasIndeterminateTint;
   2052         boolean mHasIndeterminateTintMode;
   2053 
   2054         ColorStateList mProgressTintList;
   2055         PorterDuff.Mode mProgressTintMode;
   2056         boolean mHasProgressTint;
   2057         boolean mHasProgressTintMode;
   2058 
   2059         ColorStateList mProgressBackgroundTintList;
   2060         PorterDuff.Mode mProgressBackgroundTintMode;
   2061         boolean mHasProgressBackgroundTint;
   2062         boolean mHasProgressBackgroundTintMode;
   2063 
   2064         ColorStateList mSecondaryProgressTintList;
   2065         PorterDuff.Mode mSecondaryProgressTintMode;
   2066         boolean mHasSecondaryProgressTint;
   2067         boolean mHasSecondaryProgressTintMode;
   2068     }
   2069 
   2070     /**
   2071      * Property wrapper around the visual state of the {@code progress} functionality
   2072      * handled by the {@link ProgressBar#setProgress(int, boolean)} method. This does
   2073      * not correspond directly to the actual progress -- only the visual state.
   2074      */
   2075     private final FloatProperty<ProgressBar> VISUAL_PROGRESS =
   2076             new FloatProperty<ProgressBar>("visual_progress") {
   2077                 @Override
   2078                 public void setValue(ProgressBar object, float value) {
   2079                     object.setVisualProgress(R.id.progress, value);
   2080                     object.mVisualProgress = value;
   2081                 }
   2082 
   2083                 @Override
   2084                 public Float get(ProgressBar object) {
   2085                     return object.mVisualProgress;
   2086                 }
   2087             };
   2088 }
   2089