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