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