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