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