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