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