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