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