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