OSDN Git Service

50d701aac1d067518fd4795bfe78a2c6995914e8
[android-x86/frameworks-base.git] / core / java / android / widget / ProgressBar.java
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.annotation.Nullable;
20 import android.graphics.PorterDuff;
21
22 import com.android.internal.R;
23
24 import android.annotation.InterpolatorRes;
25 import android.content.Context;
26 import android.content.res.ColorStateList;
27 import android.content.res.TypedArray;
28 import android.graphics.Bitmap;
29 import android.graphics.BitmapShader;
30 import android.graphics.Canvas;
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.ShapeDrawable;
40 import android.graphics.drawable.StateListDrawable;
41 import android.graphics.drawable.shapes.RoundRectShape;
42 import android.graphics.drawable.shapes.Shape;
43 import android.os.Parcel;
44 import android.os.Parcelable;
45 import android.util.AttributeSet;
46 import android.util.Pools.SynchronizedPool;
47 import android.view.Gravity;
48 import android.view.RemotableViewMethod;
49 import android.view.View;
50 import android.view.ViewDebug;
51 import android.view.accessibility.AccessibilityEvent;
52 import android.view.accessibility.AccessibilityManager;
53 import android.view.animation.AlphaAnimation;
54 import android.view.animation.Animation;
55 import android.view.animation.AnimationUtils;
56 import android.view.animation.Interpolator;
57 import android.view.animation.LinearInterpolator;
58 import android.view.animation.Transformation;
59 import android.widget.RemoteViews.RemoteView;
60
61 import java.util.ArrayList;
62
63
64 /**
65  * <p>
66  * Visual indicator of progress in some operation.  Displays a bar to the user
67  * representing how far the operation has progressed; the application can
68  * change the amount of progress (modifying the length of the bar) as it moves
69  * forward.  There is also a secondary progress displayable on a progress bar
70  * which is useful for displaying intermediate progress, such as the buffer
71  * level during a streaming playback progress bar.
72  * </p>
73  *
74  * <p>
75  * A progress bar can also be made indeterminate. In indeterminate mode, the
76  * progress bar shows a cyclic animation without an indication of progress. This mode is used by
77  * applications when the length of the task is unknown. The indeterminate progress bar can be either
78  * a spinning wheel or a horizontal bar.
79  * </p>
80  *
81  * <p>The following code example shows how a progress bar can be used from
82  * a worker thread to update the user interface to notify the user of progress:
83  * </p>
84  *
85  * <pre>
86  * public class MyActivity extends Activity {
87  *     private static final int PROGRESS = 0x1;
88  *
89  *     private ProgressBar mProgress;
90  *     private int mProgressStatus = 0;
91  *
92  *     private Handler mHandler = new Handler();
93  *
94  *     protected void onCreate(Bundle icicle) {
95  *         super.onCreate(icicle);
96  *
97  *         setContentView(R.layout.progressbar_activity);
98  *
99  *         mProgress = (ProgressBar) findViewById(R.id.progress_bar);
100  *
101  *         // Start lengthy operation in a background thread
102  *         new Thread(new Runnable() {
103  *             public void run() {
104  *                 while (mProgressStatus &lt; 100) {
105  *                     mProgressStatus = doWork();
106  *
107  *                     // Update the progress bar
108  *                     mHandler.post(new Runnable() {
109  *                         public void run() {
110  *                             mProgress.setProgress(mProgressStatus);
111  *                         }
112  *                     });
113  *                 }
114  *             }
115  *         }).start();
116  *     }
117  * }</pre>
118  *
119  * <p>To add a progress bar to a layout file, you can use the {@code &lt;ProgressBar&gt;} element.
120  * By default, the progress bar is a spinning wheel (an indeterminate indicator). To change to a
121  * horizontal progress bar, apply the {@link android.R.style#Widget_ProgressBar_Horizontal
122  * Widget.ProgressBar.Horizontal} style, like so:</p>
123  *
124  * <pre>
125  * &lt;ProgressBar
126  *     style="@android:style/Widget.ProgressBar.Horizontal"
127  *     ... /&gt;</pre>
128  *
129  * <p>If you will use the progress bar to show real progress, you must use the horizontal bar. You
130  * can then increment the  progress with {@link #incrementProgressBy incrementProgressBy()} or
131  * {@link #setProgress setProgress()}. By default, the progress bar is full when it reaches 100. If
132  * necessary, you can adjust the maximum value (the value for a full bar) using the {@link
133  * android.R.styleable#ProgressBar_max android:max} attribute. Other attributes available are listed
134  * below.</p>
135  *
136  * <p>Another common style to apply to the progress bar is {@link
137  * android.R.style#Widget_ProgressBar_Small Widget.ProgressBar.Small}, which shows a smaller
138  * version of the spinning wheel&mdash;useful when waiting for content to load.
139  * For example, you can insert this kind of progress bar into your default layout for
140  * a view that will be populated by some content fetched from the Internet&mdash;the spinning wheel
141  * appears immediately and when your application receives the content, it replaces the progress bar
142  * with the loaded content. For example:</p>
143  *
144  * <pre>
145  * &lt;LinearLayout
146  *     android:orientation="horizontal"
147  *     ... &gt;
148  *     &lt;ProgressBar
149  *         android:layout_width="wrap_content"
150  *         android:layout_height="wrap_content"
151  *         style="@android:style/Widget.ProgressBar.Small"
152  *         android:layout_marginRight="5dp" /&gt;
153  *     &lt;TextView
154  *         android:layout_width="wrap_content"
155  *         android:layout_height="wrap_content"
156  *         android:text="@string/loading" /&gt;
157  * &lt;/LinearLayout&gt;</pre>
158  *
159  * <p>Other progress bar styles provided by the system include:</p>
160  * <ul>
161  * <li>{@link android.R.style#Widget_ProgressBar_Horizontal Widget.ProgressBar.Horizontal}</li>
162  * <li>{@link android.R.style#Widget_ProgressBar_Small Widget.ProgressBar.Small}</li>
163  * <li>{@link android.R.style#Widget_ProgressBar_Large Widget.ProgressBar.Large}</li>
164  * <li>{@link android.R.style#Widget_ProgressBar_Inverse Widget.ProgressBar.Inverse}</li>
165  * <li>{@link android.R.style#Widget_ProgressBar_Small_Inverse
166  * Widget.ProgressBar.Small.Inverse}</li>
167  * <li>{@link android.R.style#Widget_ProgressBar_Large_Inverse
168  * Widget.ProgressBar.Large.Inverse}</li>
169  * </ul>
170  * <p>The "inverse" styles provide an inverse color scheme for the spinner, which may be necessary
171  * if your application uses a light colored theme (a white background).</p>
172  *
173  * <p><strong>XML attributes</b></strong>
174  * <p>
175  * See {@link android.R.styleable#ProgressBar ProgressBar Attributes},
176  * {@link android.R.styleable#View View Attributes}
177  * </p>
178  *
179  * @attr ref android.R.styleable#ProgressBar_animationResolution
180  * @attr ref android.R.styleable#ProgressBar_indeterminate
181  * @attr ref android.R.styleable#ProgressBar_indeterminateBehavior
182  * @attr ref android.R.styleable#ProgressBar_indeterminateDrawable
183  * @attr ref android.R.styleable#ProgressBar_indeterminateDuration
184  * @attr ref android.R.styleable#ProgressBar_indeterminateOnly
185  * @attr ref android.R.styleable#ProgressBar_interpolator
186  * @attr ref android.R.styleable#ProgressBar_max
187  * @attr ref android.R.styleable#ProgressBar_maxHeight
188  * @attr ref android.R.styleable#ProgressBar_maxWidth
189  * @attr ref android.R.styleable#ProgressBar_minHeight
190  * @attr ref android.R.styleable#ProgressBar_minWidth
191  * @attr ref android.R.styleable#ProgressBar_mirrorForRtl
192  * @attr ref android.R.styleable#ProgressBar_progress
193  * @attr ref android.R.styleable#ProgressBar_progressDrawable
194  * @attr ref android.R.styleable#ProgressBar_secondaryProgress
195  */
196 @RemoteView
197 public class ProgressBar extends View {
198     private static final int MAX_LEVEL = 10000;
199     private static final int TIMEOUT_SEND_ACCESSIBILITY_EVENT = 200;
200
201     int mMinWidth;
202     int mMaxWidth;
203     int mMinHeight;
204     int mMaxHeight;
205
206     private int mProgress;
207     private int mSecondaryProgress;
208     private int mMax;
209
210     private int mBehavior;
211     private int mDuration;
212     private boolean mIndeterminate;
213     private boolean mOnlyIndeterminate;
214     private Transformation mTransformation;
215     private AlphaAnimation mAnimation;
216     private boolean mHasAnimation;
217
218     private Drawable mIndeterminateDrawable;
219     private Drawable mProgressDrawable;
220     private Drawable mCurrentDrawable;
221     private ProgressTintInfo mProgressTintInfo;
222
223     Bitmap mSampleTile;
224     private boolean mNoInvalidate;
225     private Interpolator mInterpolator;
226     private RefreshProgressRunnable mRefreshProgressRunnable;
227     private long mUiThreadId;
228     private boolean mShouldStartAnimationDrawable;
229
230     private boolean mInDrawing;
231     private boolean mAttached;
232     private boolean mRefreshIsPosted;
233
234     boolean mMirrorForRtl = false;
235
236     private final ArrayList<RefreshData> mRefreshData = new ArrayList<RefreshData>();
237
238     private AccessibilityEventSender mAccessibilityEventSender;
239
240     /**
241      * Create a new progress bar with range 0...100 and initial progress of 0.
242      * @param context the application environment
243      */
244     public ProgressBar(Context context) {
245         this(context, null);
246     }
247
248     public ProgressBar(Context context, AttributeSet attrs) {
249         this(context, attrs, com.android.internal.R.attr.progressBarStyle);
250     }
251
252     public ProgressBar(Context context, AttributeSet attrs, int defStyleAttr) {
253         this(context, attrs, defStyleAttr, 0);
254     }
255
256     public ProgressBar(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
257         super(context, attrs, defStyleAttr, defStyleRes);
258
259         mUiThreadId = Thread.currentThread().getId();
260         initProgressBar();
261
262         final TypedArray a = context.obtainStyledAttributes(
263                 attrs, R.styleable.ProgressBar, defStyleAttr, defStyleRes);
264
265         mNoInvalidate = true;
266
267         final Drawable progressDrawable = a.getDrawable(R.styleable.ProgressBar_progressDrawable);
268         if (progressDrawable != null) {
269             // Calling this method can set mMaxHeight, make sure the corresponding
270             // XML attribute for mMaxHeight is read after calling this method
271             setProgressDrawableTiled(progressDrawable);
272         }
273
274
275         mDuration = a.getInt(R.styleable.ProgressBar_indeterminateDuration, mDuration);
276
277         mMinWidth = a.getDimensionPixelSize(R.styleable.ProgressBar_minWidth, mMinWidth);
278         mMaxWidth = a.getDimensionPixelSize(R.styleable.ProgressBar_maxWidth, mMaxWidth);
279         mMinHeight = a.getDimensionPixelSize(R.styleable.ProgressBar_minHeight, mMinHeight);
280         mMaxHeight = a.getDimensionPixelSize(R.styleable.ProgressBar_maxHeight, mMaxHeight);
281
282         mBehavior = a.getInt(R.styleable.ProgressBar_indeterminateBehavior, mBehavior);
283
284         final int resID = a.getResourceId(
285                 com.android.internal.R.styleable.ProgressBar_interpolator,
286                 android.R.anim.linear_interpolator); // default to linear interpolator
287         if (resID > 0) {
288             setInterpolator(context, resID);
289         }
290
291         setMax(a.getInt(R.styleable.ProgressBar_max, mMax));
292
293         setProgress(a.getInt(R.styleable.ProgressBar_progress, mProgress));
294
295         setSecondaryProgress(
296                 a.getInt(R.styleable.ProgressBar_secondaryProgress, mSecondaryProgress));
297
298         final Drawable indeterminateDrawable = a.getDrawable(
299                 R.styleable.ProgressBar_indeterminateDrawable);
300         if (indeterminateDrawable != null) {
301             setIndeterminateDrawableTiled(indeterminateDrawable);
302         }
303
304         mOnlyIndeterminate = a.getBoolean(
305                 R.styleable.ProgressBar_indeterminateOnly, mOnlyIndeterminate);
306
307         mNoInvalidate = false;
308
309         setIndeterminate(mOnlyIndeterminate || a.getBoolean(
310                 R.styleable.ProgressBar_indeterminate, mIndeterminate));
311
312         mMirrorForRtl = a.getBoolean(R.styleable.ProgressBar_mirrorForRtl, mMirrorForRtl);
313
314         if (a.hasValue(R.styleable.ProgressBar_progressTintMode)) {
315             if (mProgressTintInfo == null) {
316                 mProgressTintInfo = new ProgressTintInfo();
317             }
318             mProgressTintInfo.mProgressTintMode = Drawable.parseTintMode(a.getInt(
319                     R.styleable.ProgressBar_progressBackgroundTintMode, -1), null);
320             mProgressTintInfo.mHasProgressTintMode = true;
321         }
322
323         if (a.hasValue(R.styleable.ProgressBar_progressTint)) {
324             if (mProgressTintInfo == null) {
325                 mProgressTintInfo = new ProgressTintInfo();
326             }
327             mProgressTintInfo.mProgressTintList = a.getColorStateList(
328                     R.styleable.ProgressBar_progressTint);
329             mProgressTintInfo.mHasProgressTint = true;
330         }
331
332         if (a.hasValue(R.styleable.ProgressBar_progressBackgroundTintMode)) {
333             if (mProgressTintInfo == null) {
334                 mProgressTintInfo = new ProgressTintInfo();
335             }
336             mProgressTintInfo.mProgressBackgroundTintMode = Drawable.parseTintMode(a.getInt(
337                     R.styleable.ProgressBar_progressTintMode, -1), null);
338             mProgressTintInfo.mHasProgressBackgroundTintMode = true;
339         }
340
341         if (a.hasValue(R.styleable.ProgressBar_progressBackgroundTint)) {
342             if (mProgressTintInfo == null) {
343                 mProgressTintInfo = new ProgressTintInfo();
344             }
345             mProgressTintInfo.mProgressBackgroundTintList = a.getColorStateList(
346                     R.styleable.ProgressBar_progressBackgroundTint);
347             mProgressTintInfo.mHasProgressBackgroundTint = true;
348         }
349
350         if (a.hasValue(R.styleable.ProgressBar_secondaryProgressTintMode)) {
351             if (mProgressTintInfo == null) {
352                 mProgressTintInfo = new ProgressTintInfo();
353             }
354             mProgressTintInfo.mSecondaryProgressTintMode = Drawable.parseTintMode(
355                     a.getInt(R.styleable.ProgressBar_secondaryProgressTintMode, -1), null);
356             mProgressTintInfo.mHasSecondaryProgressTintMode = true;
357         }
358
359         if (a.hasValue(R.styleable.ProgressBar_secondaryProgressTint)) {
360             if (mProgressTintInfo == null) {
361                 mProgressTintInfo = new ProgressTintInfo();
362             }
363             mProgressTintInfo.mSecondaryProgressTintList = a.getColorStateList(
364                     R.styleable.ProgressBar_secondaryProgressTint);
365             mProgressTintInfo.mHasSecondaryProgressTint = true;
366         }
367
368         if (a.hasValue(R.styleable.ProgressBar_indeterminateTint)) {
369             if (mProgressTintInfo == null) {
370                 mProgressTintInfo = new ProgressTintInfo();
371             }
372             mProgressTintInfo.mIndeterminateTintMode = Drawable.parseTintMode(a.getInt(
373                     R.styleable.ProgressBar_indeterminateTintMode, -1), null);
374             mProgressTintInfo.mHasIndeterminateTintMode = true;
375         }
376
377         if (a.hasValue(R.styleable.ProgressBar_indeterminateTint)) {
378             if (mProgressTintInfo == null) {
379                 mProgressTintInfo = new ProgressTintInfo();
380             }
381             mProgressTintInfo.mIndeterminateTintList = a.getColorStateList(
382                     R.styleable.ProgressBar_indeterminateTint);
383             mProgressTintInfo.mHasIndeterminateTint = true;
384         }
385
386         a.recycle();
387
388         applyProgressTints();
389         applyIndeterminateTint();
390
391         // If not explicitly specified this view is important for accessibility.
392         if (getImportantForAccessibility() == View.IMPORTANT_FOR_ACCESSIBILITY_AUTO) {
393             setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_YES);
394         }
395     }
396
397     /**
398      * Converts a drawable to a tiled version of itself. It will recursively
399      * traverse layer and state list drawables.
400      */
401     private Drawable tileify(Drawable drawable, boolean clip) {
402         // TODO: This is a terrible idea that potentially destroys any drawable
403         // that extends any of these classes. We *really* need to remove this.
404
405         if (drawable instanceof LayerDrawable) {
406             final LayerDrawable orig = (LayerDrawable) drawable;
407             final int N = orig.getNumberOfLayers();
408             final Drawable[] outDrawables = new Drawable[N];
409
410             for (int i = 0; i < N; i++) {
411                 final int id = orig.getId(i);
412                 outDrawables[i] = tileify(orig.getDrawable(i),
413                         (id == R.id.progress || id == R.id.secondaryProgress));
414             }
415
416             final LayerDrawable clone = new LayerDrawable(outDrawables);
417             for (int i = 0; i < N; i++) {
418                 clone.setId(i, orig.getId(i));
419                 clone.setLayerGravity(i, orig.getLayerGravity(i));
420                 clone.setLayerWidth(i, orig.getLayerWidth(i));
421                 clone.setLayerHeight(i, orig.getLayerHeight(i));
422                 clone.setLayerInsetLeft(i, orig.getLayerInsetLeft(i));
423                 clone.setLayerInsetRight(i, orig.getLayerInsetRight(i));
424                 clone.setLayerInsetTop(i, orig.getLayerInsetTop(i));
425                 clone.setLayerInsetBottom(i, orig.getLayerInsetBottom(i));
426                 clone.setLayerInsetStart(i, orig.getLayerInsetStart(i));
427                 clone.setLayerInsetEnd(i, orig.getLayerInsetEnd(i));
428             }
429
430             return clone;
431         }
432
433         if (drawable instanceof StateListDrawable) {
434             final StateListDrawable in = (StateListDrawable) drawable;
435             final StateListDrawable out = new StateListDrawable();
436             final int N = in.getStateCount();
437             for (int i = 0; i < N; i++) {
438                 out.addState(in.getStateSet(i), tileify(in.getStateDrawable(i), clip));
439             }
440
441             return out;
442         }
443
444         if (drawable instanceof BitmapDrawable) {
445             final BitmapDrawable bitmap = (BitmapDrawable) drawable;
446             final Bitmap tileBitmap = bitmap.getBitmap();
447             if (mSampleTile == null) {
448                 mSampleTile = tileBitmap;
449             }
450
451             final ShapeDrawable shapeDrawable = new ShapeDrawable(getDrawableShape());
452             final BitmapShader bitmapShader = new BitmapShader(tileBitmap,
453                     Shader.TileMode.REPEAT, Shader.TileMode.CLAMP);
454             shapeDrawable.getPaint().setShader(bitmapShader);
455
456             // Ensure the tint and filter are propagated in the correct order.
457             shapeDrawable.setTintList(bitmap.getTint());
458             shapeDrawable.setTintMode(bitmap.getTintMode());
459             shapeDrawable.setColorFilter(bitmap.getColorFilter());
460
461             return clip ? new ClipDrawable(
462                     shapeDrawable, Gravity.LEFT, ClipDrawable.HORIZONTAL) : shapeDrawable;
463         }
464
465         return drawable;
466     }
467
468     Shape getDrawableShape() {
469         final float[] roundedCorners = new float[] { 5, 5, 5, 5, 5, 5, 5, 5 };
470         return new RoundRectShape(roundedCorners, null, null);
471     }
472
473     /**
474      * Convert a AnimationDrawable for use as a barberpole animation.
475      * Each frame of the animation is wrapped in a ClipDrawable and
476      * given a tiling BitmapShader.
477      */
478     private Drawable tileifyIndeterminate(Drawable drawable) {
479         if (drawable instanceof AnimationDrawable) {
480             AnimationDrawable background = (AnimationDrawable) drawable;
481             final int N = background.getNumberOfFrames();
482             AnimationDrawable newBg = new AnimationDrawable();
483             newBg.setOneShot(background.isOneShot());
484
485             for (int i = 0; i < N; i++) {
486                 Drawable frame = tileify(background.getFrame(i), true);
487                 frame.setLevel(10000);
488                 newBg.addFrame(frame, background.getDuration(i));
489             }
490             newBg.setLevel(10000);
491             drawable = newBg;
492         }
493         return drawable;
494     }
495
496     /**
497      * <p>
498      * Initialize the progress bar's default values:
499      * </p>
500      * <ul>
501      * <li>progress = 0</li>
502      * <li>max = 100</li>
503      * <li>animation duration = 4000 ms</li>
504      * <li>indeterminate = false</li>
505      * <li>behavior = repeat</li>
506      * </ul>
507      */
508     private void initProgressBar() {
509         mMax = 100;
510         mProgress = 0;
511         mSecondaryProgress = 0;
512         mIndeterminate = false;
513         mOnlyIndeterminate = false;
514         mDuration = 4000;
515         mBehavior = AlphaAnimation.RESTART;
516         mMinWidth = 24;
517         mMaxWidth = 48;
518         mMinHeight = 24;
519         mMaxHeight = 48;
520     }
521
522     /**
523      * <p>Indicate whether this progress bar is in indeterminate mode.</p>
524      *
525      * @return true if the progress bar is in indeterminate mode
526      */
527     @ViewDebug.ExportedProperty(category = "progress")
528     public synchronized boolean isIndeterminate() {
529         return mIndeterminate;
530     }
531
532     /**
533      * <p>Change the indeterminate mode for this progress bar. In indeterminate
534      * mode, the progress is ignored and the progress bar shows an infinite
535      * animation instead.</p>
536      *
537      * If this progress bar's style only supports indeterminate mode (such as the circular
538      * progress bars), then this will be ignored.
539      *
540      * @param indeterminate true to enable the indeterminate mode
541      */
542     @android.view.RemotableViewMethod
543     public synchronized void setIndeterminate(boolean indeterminate) {
544         if ((!mOnlyIndeterminate || !mIndeterminate) && indeterminate != mIndeterminate) {
545             mIndeterminate = indeterminate;
546
547             if (indeterminate) {
548                 // swap between indeterminate and regular backgrounds
549                 mCurrentDrawable = mIndeterminateDrawable;
550                 startAnimation();
551             } else {
552                 mCurrentDrawable = mProgressDrawable;
553                 stopAnimation();
554             }
555         }
556     }
557
558     /**
559      * <p>Get the drawable used to draw the progress bar in
560      * indeterminate mode.</p>
561      *
562      * @return a {@link android.graphics.drawable.Drawable} instance
563      *
564      * @see #setIndeterminateDrawable(android.graphics.drawable.Drawable)
565      * @see #setIndeterminate(boolean)
566      */
567     public Drawable getIndeterminateDrawable() {
568         return mIndeterminateDrawable;
569     }
570
571     /**
572      * Define the drawable used to draw the progress bar in indeterminate mode.
573      *
574      * @param d the new drawable
575      * @see #getIndeterminateDrawable()
576      * @see #setIndeterminate(boolean)
577      */
578     public void setIndeterminateDrawable(Drawable d) {
579         if (mIndeterminateDrawable != d) {
580             if (mIndeterminateDrawable != null) {
581                 mIndeterminateDrawable.setCallback(null);
582                 unscheduleDrawable(mIndeterminateDrawable);
583             }
584
585             mIndeterminateDrawable = d;
586
587             if (d != null) {
588                 d.setCallback(this);
589                 d.setLayoutDirection(getLayoutDirection());
590                 if (d.isStateful()) {
591                     d.setState(getDrawableState());
592                 }
593                 applyIndeterminateTint();
594             }
595
596             if (mIndeterminate) {
597                 mCurrentDrawable = d;
598                 postInvalidate();
599             }
600         }
601     }
602
603     /**
604      * Applies a tint to the indeterminate drawable. Does not modify the
605      * current tint mode, which is {@link PorterDuff.Mode#SRC_IN} by default.
606      * <p>
607      * Subsequent calls to {@link #setIndeterminateDrawable(Drawable)} will
608      * automatically mutate the drawable and apply the specified tint and
609      * tint mode using
610      * {@link Drawable#setTintList(ColorStateList)}.
611      *
612      * @param tint the tint to apply, may be {@code null} to clear tint
613      *
614      * @attr ref android.R.styleable#ProgressBar_indeterminateTint
615      * @see #getIndeterminateTintList()
616      * @see Drawable#setTintList(ColorStateList)
617      */
618     @RemotableViewMethod
619     public void setIndeterminateTintList(@Nullable ColorStateList tint) {
620         if (mProgressTintInfo == null) {
621             mProgressTintInfo = new ProgressTintInfo();
622         }
623         mProgressTintInfo.mIndeterminateTintList = tint;
624         mProgressTintInfo.mHasIndeterminateTint = true;
625
626         applyIndeterminateTint();
627     }
628
629     /**
630      * @return the tint applied to the indeterminate drawable
631      * @attr ref android.R.styleable#ProgressBar_indeterminateTint
632      * @see #setIndeterminateTintList(ColorStateList)
633      */
634     @Nullable
635     public ColorStateList getIndeterminateTintList() {
636         return mProgressTintInfo != null ? mProgressTintInfo.mIndeterminateTintList : null;
637     }
638
639     /**
640      * Specifies the blending mode used to apply the tint specified by
641      * {@link #setIndeterminateTintList(ColorStateList)} to the indeterminate
642      * drawable. The default mode is {@link PorterDuff.Mode#SRC_IN}.
643      *
644      * @param tintMode the blending mode used to apply the tint, may be
645      *                 {@code null} to clear tint
646      * @attr ref android.R.styleable#ProgressBar_indeterminateTintMode
647      * @see #setIndeterminateTintList(ColorStateList)
648      * @see Drawable#setTintMode(PorterDuff.Mode)
649      */
650     public void setIndeterminateTintMode(@Nullable PorterDuff.Mode tintMode) {
651         if (mProgressTintInfo == null) {
652             mProgressTintInfo = new ProgressTintInfo();
653         }
654         mProgressTintInfo.mIndeterminateTintMode = tintMode;
655         mProgressTintInfo.mHasIndeterminateTintMode = true;
656
657         applyIndeterminateTint();
658     }
659
660     /**
661      * Returns the blending mode used to apply the tint to the indeterminate
662      * drawable, if specified.
663      *
664      * @return the blending mode used to apply the tint to the indeterminate
665      *         drawable
666      * @attr ref android.R.styleable#ProgressBar_indeterminateTintMode
667      * @see #setIndeterminateTintMode(PorterDuff.Mode)
668      */
669     @Nullable
670     public PorterDuff.Mode getIndeterminateTintMode() {
671         return mProgressTintInfo != null ? mProgressTintInfo.mIndeterminateTintMode : null;
672     }
673
674     private void applyIndeterminateTint() {
675         if (mIndeterminateDrawable != null && mProgressTintInfo != null) {
676             final ProgressTintInfo tintInfo = mProgressTintInfo;
677             if (tintInfo.mHasIndeterminateTint || tintInfo.mHasIndeterminateTintMode) {
678                 mIndeterminateDrawable = mIndeterminateDrawable.mutate();
679
680                 if (tintInfo.mHasIndeterminateTint) {
681                     mIndeterminateDrawable.setTintList(tintInfo.mIndeterminateTintList);
682                 }
683
684                 if (tintInfo.mHasIndeterminateTintMode) {
685                     mIndeterminateDrawable.setTintMode(tintInfo.mIndeterminateTintMode);
686                 }
687
688                 // The drawable (or one of its children) may not have been
689                 // stateful before applying the tint, so let's try again.
690                 if (mIndeterminateDrawable.isStateful()) {
691                     mIndeterminateDrawable.setState(getDrawableState());
692                 }
693             }
694         }
695     }
696
697     /**
698      * Define the tileable drawable used to draw the progress bar in
699      * indeterminate mode.
700      * <p>
701      * If the drawable is a BitmapDrawable or contains BitmapDrawables, a
702      * tiled copy will be generated for display as a progress bar.
703      *
704      * @param d the new drawable
705      * @see #getIndeterminateDrawable()
706      * @see #setIndeterminate(boolean)
707      */
708     public void setIndeterminateDrawableTiled(Drawable d) {
709         if (d != null) {
710             d = tileifyIndeterminate(d);
711         }
712
713         setIndeterminateDrawable(d);
714     }
715
716     /**
717      * <p>Get the drawable used to draw the progress bar in
718      * progress mode.</p>
719      *
720      * @return a {@link android.graphics.drawable.Drawable} instance
721      *
722      * @see #setProgressDrawable(android.graphics.drawable.Drawable)
723      * @see #setIndeterminate(boolean)
724      */
725     public Drawable getProgressDrawable() {
726         return mProgressDrawable;
727     }
728
729     /**
730      * Define the drawable used to draw the progress bar in progress mode.
731      *
732      * @param d the new drawable
733      * @see #getProgressDrawable()
734      * @see #setIndeterminate(boolean)
735      */
736     public void setProgressDrawable(Drawable d) {
737         if (mProgressDrawable != d) {
738             if (mProgressDrawable != null) {
739                 mProgressDrawable.setCallback(null);
740                 unscheduleDrawable(mProgressDrawable);
741             }
742
743             mProgressDrawable = d;
744
745             if (d != null) {
746                 d.setCallback(this);
747                 d.setLayoutDirection(getLayoutDirection());
748                 if (d.isStateful()) {
749                     d.setState(getDrawableState());
750                 }
751
752                 // Make sure the ProgressBar is always tall enough
753                 int drawableHeight = d.getMinimumHeight();
754                 if (mMaxHeight < drawableHeight) {
755                     mMaxHeight = drawableHeight;
756                     requestLayout();
757                 }
758
759                 applyProgressTints();
760             }
761
762             if (!mIndeterminate) {
763                 mCurrentDrawable = d;
764                 postInvalidate();
765             }
766
767             updateDrawableBounds(getWidth(), getHeight());
768             updateDrawableState();
769
770             doRefreshProgress(R.id.progress, mProgress, false, false);
771             doRefreshProgress(R.id.secondaryProgress, mSecondaryProgress, false, false);
772         }
773     }
774
775     /**
776      * Applies the progress tints in order of increasing specificity.
777      */
778     private void applyProgressTints() {
779         if (mProgressDrawable != null && mProgressTintInfo != null) {
780             applyPrimaryProgressTint();
781             applyProgressBackgroundTint();
782             applySecondaryProgressTint();
783         }
784     }
785
786     /**
787      * Should only be called if we've already verified that mProgressDrawable
788      * and mProgressTintInfo are non-null.
789      */
790     private void applyPrimaryProgressTint() {
791         if (mProgressTintInfo.mHasProgressTint
792                 || mProgressTintInfo.mHasProgressTintMode) {
793             final Drawable target = getTintTarget(R.id.progress, true);
794             if (target != null) {
795                 if (mProgressTintInfo.mHasProgressTint) {
796                     target.setTintList(mProgressTintInfo.mProgressTintList);
797                 }
798                 if (mProgressTintInfo.mHasProgressTintMode) {
799                     target.setTintMode(mProgressTintInfo.mProgressTintMode);
800                 }
801
802                 // The drawable (or one of its children) may not have been
803                 // stateful before applying the tint, so let's try again.
804                 if (target.isStateful()) {
805                     target.setState(getDrawableState());
806                 }
807             }
808         }
809     }
810
811     /**
812      * Should only be called if we've already verified that mProgressDrawable
813      * and mProgressTintInfo are non-null.
814      */
815     private void applyProgressBackgroundTint() {
816         if (mProgressTintInfo.mHasProgressBackgroundTint
817                 || mProgressTintInfo.mHasProgressBackgroundTintMode) {
818             final Drawable target = getTintTarget(R.id.background, false);
819             if (target != null) {
820                 if (mProgressTintInfo.mHasProgressBackgroundTint) {
821                     target.setTintList(mProgressTintInfo.mProgressBackgroundTintList);
822                 }
823                 if (mProgressTintInfo.mHasProgressBackgroundTintMode) {
824                     target.setTintMode(mProgressTintInfo.mProgressBackgroundTintMode);
825                 }
826
827                 // The drawable (or one of its children) may not have been
828                 // stateful before applying the tint, so let's try again.
829                 if (target.isStateful()) {
830                     target.setState(getDrawableState());
831                 }
832             }
833         }
834     }
835
836     /**
837      * Should only be called if we've already verified that mProgressDrawable
838      * and mProgressTintInfo are non-null.
839      */
840     private void applySecondaryProgressTint() {
841         if (mProgressTintInfo.mHasSecondaryProgressTint
842                 || mProgressTintInfo.mHasSecondaryProgressTintMode) {
843             final Drawable target = getTintTarget(R.id.secondaryProgress, false);
844             if (target != null) {
845                 if (mProgressTintInfo.mHasSecondaryProgressTint) {
846                     target.setTintList(mProgressTintInfo.mSecondaryProgressTintList);
847                 }
848                 if (mProgressTintInfo.mHasSecondaryProgressTintMode) {
849                     target.setTintMode(mProgressTintInfo.mSecondaryProgressTintMode);
850                 }
851
852                 // The drawable (or one of its children) may not have been
853                 // stateful before applying the tint, so let's try again.
854                 if (target.isStateful()) {
855                     target.setState(getDrawableState());
856                 }
857             }
858         }
859     }
860
861     /**
862      * Applies a tint to the progress indicator, if one exists, or to the
863      * entire progress drawable otherwise. Does not modify the current tint
864      * mode, which is {@link PorterDuff.Mode#SRC_IN} by default.
865      * <p>
866      * The progress indicator should be specified as a layer with
867      * id {@link android.R.id#progress} in a {@link LayerDrawable}
868      * used as the progress drawable.
869      * <p>
870      * Subsequent calls to {@link #setProgressDrawable(Drawable)} will
871      * automatically mutate the drawable and apply the specified tint and
872      * tint mode using
873      * {@link Drawable#setTintList(ColorStateList)}.
874      *
875      * @param tint the tint to apply, may be {@code null} to clear tint
876      *
877      * @attr ref android.R.styleable#ProgressBar_progressTint
878      * @see #getProgressTintList()
879      * @see Drawable#setTintList(ColorStateList)
880      */
881     @RemotableViewMethod
882     public void setProgressTintList(@Nullable ColorStateList tint) {
883         if (mProgressTintInfo == null) {
884             mProgressTintInfo = new ProgressTintInfo();
885         }
886         mProgressTintInfo.mProgressTintList = tint;
887         mProgressTintInfo.mHasProgressTint = true;
888
889         if (mProgressDrawable != null) {
890             applyPrimaryProgressTint();
891         }
892     }
893
894     /**
895      * Returns the tint applied to the progress drawable, if specified.
896      *
897      * @return the tint applied to the progress drawable
898      * @attr ref android.R.styleable#ProgressBar_progressTint
899      * @see #setProgressTintList(ColorStateList)
900      */
901     @Nullable
902     public ColorStateList getProgressTintList() {
903         return mProgressTintInfo != null ? mProgressTintInfo.mProgressTintList : null;
904     }
905
906     /**
907      * Specifies the blending mode used to apply the tint specified by
908      * {@link #setProgressTintList(ColorStateList)}} to the progress
909      * indicator. The default mode is {@link PorterDuff.Mode#SRC_IN}.
910      *
911      * @param tintMode the blending mode used to apply the tint, may be
912      *                 {@code null} to clear tint
913      * @attr ref android.R.styleable#ProgressBar_progressTintMode
914      * @see #getProgressTintMode()
915      * @see Drawable#setTintMode(PorterDuff.Mode)
916      */
917     public void setProgressTintMode(@Nullable PorterDuff.Mode tintMode) {
918         if (mProgressTintInfo == null) {
919             mProgressTintInfo = new ProgressTintInfo();
920         }
921         mProgressTintInfo.mProgressTintMode = tintMode;
922         mProgressTintInfo.mHasProgressTintMode = true;
923
924         if (mProgressDrawable != null) {
925             applyPrimaryProgressTint();
926         }
927     }
928
929     /**
930      * Returns the blending mode used to apply the tint to the progress
931      * drawable, if specified.
932      *
933      * @return the blending mode used to apply the tint to the progress
934      *         drawable
935      * @attr ref android.R.styleable#ProgressBar_progressTintMode
936      * @see #setProgressTintMode(PorterDuff.Mode)
937      */
938     @Nullable
939     public PorterDuff.Mode getProgressTintMode() {
940         return mProgressTintInfo != null ? mProgressTintInfo.mProgressTintMode : null;
941     }
942
943     /**
944      * Applies a tint to the progress background, if one exists. Does not
945      * modify the current tint mode, which is
946      * {@link PorterDuff.Mode#SRC_ATOP} by default.
947      * <p>
948      * The progress background must be specified as a layer with
949      * id {@link android.R.id#background} in a {@link LayerDrawable}
950      * used as the progress drawable.
951      * <p>
952      * Subsequent calls to {@link #setProgressDrawable(Drawable)} where the
953      * drawable contains a progress background will automatically mutate the
954      * drawable and apply the specified tint and tint mode using
955      * {@link Drawable#setTintList(ColorStateList)}.
956      *
957      * @param tint the tint to apply, may be {@code null} to clear tint
958      *
959      * @attr ref android.R.styleable#ProgressBar_progressBackgroundTint
960      * @see #getProgressBackgroundTintList()
961      * @see Drawable#setTintList(ColorStateList)
962      */
963     @RemotableViewMethod
964     public void setProgressBackgroundTintList(@Nullable ColorStateList tint) {
965         if (mProgressTintInfo == null) {
966             mProgressTintInfo = new ProgressTintInfo();
967         }
968         mProgressTintInfo.mProgressBackgroundTintList = tint;
969         mProgressTintInfo.mHasProgressBackgroundTint = true;
970
971         if (mProgressDrawable != null) {
972             applyProgressBackgroundTint();
973         }
974     }
975
976     /**
977      * Returns the tint applied to the progress background, if specified.
978      *
979      * @return the tint applied to the progress background
980      * @attr ref android.R.styleable#ProgressBar_progressBackgroundTint
981      * @see #setProgressBackgroundTintList(ColorStateList)
982      */
983     @Nullable
984     public ColorStateList getProgressBackgroundTintList() {
985         return mProgressTintInfo != null ? mProgressTintInfo.mProgressBackgroundTintList : null;
986     }
987
988     /**
989      * Specifies the blending mode used to apply the tint specified by
990      * {@link #setProgressBackgroundTintList(ColorStateList)}} to the progress
991      * background. The default mode is {@link PorterDuff.Mode#SRC_IN}.
992      *
993      * @param tintMode the blending mode used to apply the tint, may be
994      *                 {@code null} to clear tint
995      * @attr ref android.R.styleable#ProgressBar_progressBackgroundTintMode
996      * @see #setProgressBackgroundTintList(ColorStateList)
997      * @see Drawable#setTintMode(PorterDuff.Mode)
998      */
999     public void setProgressBackgroundTintMode(@Nullable PorterDuff.Mode tintMode) {
1000         if (mProgressTintInfo == null) {
1001             mProgressTintInfo = new ProgressTintInfo();
1002         }
1003         mProgressTintInfo.mProgressBackgroundTintMode = tintMode;
1004         mProgressTintInfo.mHasProgressBackgroundTintMode = true;
1005
1006         if (mProgressDrawable != null) {
1007             applyProgressBackgroundTint();
1008         }
1009     }
1010
1011     /**
1012      * @return the blending mode used to apply the tint to the progress
1013      *         background
1014      * @attr ref android.R.styleable#ProgressBar_progressBackgroundTintMode
1015      * @see #setProgressBackgroundTintMode(PorterDuff.Mode)
1016      */
1017     @Nullable
1018     public PorterDuff.Mode getProgressBackgroundTintMode() {
1019         return mProgressTintInfo != null ? mProgressTintInfo.mProgressBackgroundTintMode : null;
1020     }
1021
1022     /**
1023      * Applies a tint to the secondary progress indicator, if one exists.
1024      * Does not modify the current tint mode, which is
1025      * {@link PorterDuff.Mode#SRC_ATOP} by default.
1026      * <p>
1027      * The secondary progress indicator must be specified as a layer with
1028      * id {@link android.R.id#secondaryProgress} in a {@link LayerDrawable}
1029      * used as the progress drawable.
1030      * <p>
1031      * Subsequent calls to {@link #setProgressDrawable(Drawable)} where the
1032      * drawable contains a secondary progress indicator will automatically
1033      * mutate the drawable and apply the specified tint and tint mode using
1034      * {@link Drawable#setTintList(ColorStateList)}.
1035      *
1036      * @param tint the tint to apply, may be {@code null} to clear tint
1037      *
1038      * @attr ref android.R.styleable#ProgressBar_secondaryProgressTint
1039      * @see #getSecondaryProgressTintList()
1040      * @see Drawable#setTintList(ColorStateList)
1041      */
1042     public void setSecondaryProgressTintList(@Nullable ColorStateList tint) {
1043         if (mProgressTintInfo == null) {
1044             mProgressTintInfo = new ProgressTintInfo();
1045         }
1046         mProgressTintInfo.mSecondaryProgressTintList = tint;
1047         mProgressTintInfo.mHasSecondaryProgressTint = true;
1048
1049         if (mProgressDrawable != null) {
1050             applySecondaryProgressTint();
1051         }
1052     }
1053
1054     /**
1055      * Returns the tint applied to the secondary progress drawable, if
1056      * specified.
1057      *
1058      * @return the tint applied to the secondary progress drawable
1059      * @attr ref android.R.styleable#ProgressBar_secondaryProgressTint
1060      * @see #setSecondaryProgressTintList(ColorStateList)
1061      */
1062     @Nullable
1063     public ColorStateList getSecondaryProgressTintList() {
1064         return mProgressTintInfo != null ? mProgressTintInfo.mSecondaryProgressTintList : null;
1065     }
1066
1067     /**
1068      * Specifies the blending mode used to apply the tint specified by
1069      * {@link #setSecondaryProgressTintList(ColorStateList)}} to the secondary
1070      * progress indicator. The default mode is
1071      * {@link PorterDuff.Mode#SRC_ATOP}.
1072      *
1073      * @param tintMode the blending mode used to apply the tint, may be
1074      *                 {@code null} to clear tint
1075      * @attr ref android.R.styleable#ProgressBar_secondaryProgressTintMode
1076      * @see #setSecondaryProgressTintList(ColorStateList)
1077      * @see Drawable#setTintMode(PorterDuff.Mode)
1078      */
1079     public void setSecondaryProgressTintMode(@Nullable PorterDuff.Mode tintMode) {
1080         if (mProgressTintInfo == null) {
1081             mProgressTintInfo = new ProgressTintInfo();
1082         }
1083         mProgressTintInfo.mSecondaryProgressTintMode = tintMode;
1084         mProgressTintInfo.mHasSecondaryProgressTintMode = true;
1085
1086         if (mProgressDrawable != null) {
1087             applySecondaryProgressTint();
1088         }
1089     }
1090
1091     /**
1092      * Returns the blending mode used to apply the tint to the secondary
1093      * progress drawable, if specified.
1094      *
1095      * @return the blending mode used to apply the tint to the secondary
1096      *         progress drawable
1097      * @attr ref android.R.styleable#ProgressBar_secondaryProgressTintMode
1098      * @see #setSecondaryProgressTintMode(PorterDuff.Mode)
1099      */
1100     @Nullable
1101     public PorterDuff.Mode getSecondaryProgressTintMode() {
1102         return mProgressTintInfo != null ? mProgressTintInfo.mSecondaryProgressTintMode : null;
1103     }
1104
1105     /**
1106      * Returns the drawable to which a tint or tint mode should be applied.
1107      *
1108      * @param layerId id of the layer to modify
1109      * @param shouldFallback whether the base drawable should be returned
1110      *                       if the id does not exist
1111      * @return the drawable to modify
1112      */
1113     @Nullable
1114     private Drawable getTintTarget(int layerId, boolean shouldFallback) {
1115         Drawable layer = null;
1116
1117         final Drawable d = mProgressDrawable;
1118         if (d != null) {
1119             mProgressDrawable = d.mutate();
1120
1121             if (d instanceof LayerDrawable) {
1122                 layer = ((LayerDrawable) d).findDrawableByLayerId(layerId);
1123             }
1124
1125             if (shouldFallback && layer == null) {
1126                 layer = d;
1127             }
1128         }
1129
1130         return layer;
1131     }
1132
1133     /**
1134      * Define the tileable drawable used to draw the progress bar in
1135      * progress mode.
1136      * <p>
1137      * If the drawable is a BitmapDrawable or contains BitmapDrawables, a
1138      * tiled copy will be generated for display as a progress bar.
1139      *
1140      * @param d the new drawable
1141      * @see #getProgressDrawable()
1142      * @see #setIndeterminate(boolean)
1143      */
1144     public void setProgressDrawableTiled(Drawable d) {
1145         if (d != null) {
1146             d = tileify(d, false);
1147         }
1148
1149         setProgressDrawable(d);
1150     }
1151
1152     /**
1153      * @return The drawable currently used to draw the progress bar
1154      */
1155     Drawable getCurrentDrawable() {
1156         return mCurrentDrawable;
1157     }
1158
1159     @Override
1160     protected boolean verifyDrawable(Drawable who) {
1161         return who == mProgressDrawable || who == mIndeterminateDrawable
1162                 || super.verifyDrawable(who);
1163     }
1164
1165     @Override
1166     public void jumpDrawablesToCurrentState() {
1167         super.jumpDrawablesToCurrentState();
1168         if (mProgressDrawable != null) mProgressDrawable.jumpToCurrentState();
1169         if (mIndeterminateDrawable != null) mIndeterminateDrawable.jumpToCurrentState();
1170     }
1171
1172     /**
1173      * @hide
1174      */
1175     @Override
1176     public void onResolveDrawables(int layoutDirection) {
1177         final Drawable d = mCurrentDrawable;
1178         if (d != null) {
1179             d.setLayoutDirection(layoutDirection);
1180         }
1181         if (mIndeterminateDrawable != null) {
1182             mIndeterminateDrawable.setLayoutDirection(layoutDirection);
1183         }
1184         if (mProgressDrawable != null) {
1185             mProgressDrawable.setLayoutDirection(layoutDirection);
1186         }
1187     }
1188
1189     @Override
1190     public void postInvalidate() {
1191         if (!mNoInvalidate) {
1192             super.postInvalidate();
1193         }
1194     }
1195
1196     private class RefreshProgressRunnable implements Runnable {
1197         public void run() {
1198             synchronized (ProgressBar.this) {
1199                 final int count = mRefreshData.size();
1200                 for (int i = 0; i < count; i++) {
1201                     final RefreshData rd = mRefreshData.get(i);
1202                     doRefreshProgress(rd.id, rd.progress, rd.fromUser, true);
1203                     rd.recycle();
1204                 }
1205                 mRefreshData.clear();
1206                 mRefreshIsPosted = false;
1207             }
1208         }
1209     }
1210
1211     private static class RefreshData {
1212         private static final int POOL_MAX = 24;
1213         private static final SynchronizedPool<RefreshData> sPool =
1214                 new SynchronizedPool<RefreshData>(POOL_MAX);
1215
1216         public int id;
1217         public int progress;
1218         public boolean fromUser;
1219
1220         public static RefreshData obtain(int id, int progress, boolean fromUser) {
1221             RefreshData rd = sPool.acquire();
1222             if (rd == null) {
1223                 rd = new RefreshData();
1224             }
1225             rd.id = id;
1226             rd.progress = progress;
1227             rd.fromUser = fromUser;
1228             return rd;
1229         }
1230
1231         public void recycle() {
1232             sPool.release(this);
1233         }
1234     }
1235
1236     private synchronized void doRefreshProgress(int id, int progress, boolean fromUser,
1237             boolean callBackToApp) {
1238         float scale = mMax > 0 ? (float) progress / (float) mMax : 0;
1239         final Drawable d = mCurrentDrawable;
1240         if (d != null) {
1241             Drawable progressDrawable = null;
1242
1243             if (d instanceof LayerDrawable) {
1244                 progressDrawable = ((LayerDrawable) d).findDrawableByLayerId(id);
1245                 if (progressDrawable != null && canResolveLayoutDirection()) {
1246                     progressDrawable.setLayoutDirection(getLayoutDirection());
1247                 }
1248             }
1249
1250             final int level = (int) (scale * MAX_LEVEL);
1251             (progressDrawable != null ? progressDrawable : d).setLevel(level);
1252         } else {
1253             invalidate();
1254         }
1255
1256         if (callBackToApp && id == R.id.progress) {
1257             onProgressRefresh(scale, fromUser, progress);
1258         }
1259     }
1260
1261     void onProgressRefresh(float scale, boolean fromUser, int progress) {
1262         if (AccessibilityManager.getInstance(mContext).isEnabled()) {
1263             scheduleAccessibilityEventSender();
1264         }
1265     }
1266
1267     private synchronized void refreshProgress(int id, int progress, boolean fromUser) {
1268         if (mUiThreadId == Thread.currentThread().getId()) {
1269             doRefreshProgress(id, progress, fromUser, true);
1270         } else {
1271             if (mRefreshProgressRunnable == null) {
1272                 mRefreshProgressRunnable = new RefreshProgressRunnable();
1273             }
1274
1275             final RefreshData rd = RefreshData.obtain(id, progress, fromUser);
1276             mRefreshData.add(rd);
1277             if (mAttached && !mRefreshIsPosted) {
1278                 post(mRefreshProgressRunnable);
1279                 mRefreshIsPosted = true;
1280             }
1281         }
1282     }
1283
1284     /**
1285      * <p>Set the current progress to the specified value. Does not do anything
1286      * if the progress bar is in indeterminate mode.</p>
1287      *
1288      * @param progress the new progress, between 0 and {@link #getMax()}
1289      *
1290      * @see #setIndeterminate(boolean)
1291      * @see #isIndeterminate()
1292      * @see #getProgress()
1293      * @see #incrementProgressBy(int)
1294      */
1295     @android.view.RemotableViewMethod
1296     public synchronized void setProgress(int progress) {
1297         setProgress(progress, false);
1298     }
1299
1300     @android.view.RemotableViewMethod
1301     synchronized void setProgress(int progress, boolean fromUser) {
1302         if (mIndeterminate) {
1303             return;
1304         }
1305
1306         if (progress < 0) {
1307             progress = 0;
1308         }
1309
1310         if (progress > mMax) {
1311             progress = mMax;
1312         }
1313
1314         if (progress != mProgress) {
1315             mProgress = progress;
1316             refreshProgress(R.id.progress, mProgress, fromUser);
1317         }
1318     }
1319
1320     /**
1321      * <p>
1322      * Set the current secondary progress to the specified value. Does not do
1323      * anything if the progress bar is in indeterminate mode.
1324      * </p>
1325      *
1326      * @param secondaryProgress the new secondary progress, between 0 and {@link #getMax()}
1327      * @see #setIndeterminate(boolean)
1328      * @see #isIndeterminate()
1329      * @see #getSecondaryProgress()
1330      * @see #incrementSecondaryProgressBy(int)
1331      */
1332     @android.view.RemotableViewMethod
1333     public synchronized void setSecondaryProgress(int secondaryProgress) {
1334         if (mIndeterminate) {
1335             return;
1336         }
1337
1338         if (secondaryProgress < 0) {
1339             secondaryProgress = 0;
1340         }
1341
1342         if (secondaryProgress > mMax) {
1343             secondaryProgress = mMax;
1344         }
1345
1346         if (secondaryProgress != mSecondaryProgress) {
1347             mSecondaryProgress = secondaryProgress;
1348             refreshProgress(R.id.secondaryProgress, mSecondaryProgress, false);
1349         }
1350     }
1351
1352     /**
1353      * <p>Get the progress bar's current level of progress. Return 0 when the
1354      * progress bar is in indeterminate mode.</p>
1355      *
1356      * @return the current progress, between 0 and {@link #getMax()}
1357      *
1358      * @see #setIndeterminate(boolean)
1359      * @see #isIndeterminate()
1360      * @see #setProgress(int)
1361      * @see #setMax(int)
1362      * @see #getMax()
1363      */
1364     @ViewDebug.ExportedProperty(category = "progress")
1365     public synchronized int getProgress() {
1366         return mIndeterminate ? 0 : mProgress;
1367     }
1368
1369     /**
1370      * <p>Get the progress bar's current level of secondary progress. Return 0 when the
1371      * progress bar is in indeterminate mode.</p>
1372      *
1373      * @return the current secondary progress, between 0 and {@link #getMax()}
1374      *
1375      * @see #setIndeterminate(boolean)
1376      * @see #isIndeterminate()
1377      * @see #setSecondaryProgress(int)
1378      * @see #setMax(int)
1379      * @see #getMax()
1380      */
1381     @ViewDebug.ExportedProperty(category = "progress")
1382     public synchronized int getSecondaryProgress() {
1383         return mIndeterminate ? 0 : mSecondaryProgress;
1384     }
1385
1386     /**
1387      * <p>Return the upper limit of this progress bar's range.</p>
1388      *
1389      * @return a positive integer
1390      *
1391      * @see #setMax(int)
1392      * @see #getProgress()
1393      * @see #getSecondaryProgress()
1394      */
1395     @ViewDebug.ExportedProperty(category = "progress")
1396     public synchronized int getMax() {
1397         return mMax;
1398     }
1399
1400     /**
1401      * <p>Set the range of the progress bar to 0...<tt>max</tt>.</p>
1402      *
1403      * @param max the upper range of this progress bar
1404      *
1405      * @see #getMax()
1406      * @see #setProgress(int)
1407      * @see #setSecondaryProgress(int)
1408      */
1409     @android.view.RemotableViewMethod
1410     public synchronized void setMax(int max) {
1411         if (max < 0) {
1412             max = 0;
1413         }
1414         if (max != mMax) {
1415             mMax = max;
1416             postInvalidate();
1417
1418             if (mProgress > max) {
1419                 mProgress = max;
1420             }
1421             refreshProgress(R.id.progress, mProgress, false);
1422         }
1423     }
1424
1425     /**
1426      * <p>Increase the progress bar's progress by the specified amount.</p>
1427      *
1428      * @param diff the amount by which the progress must be increased
1429      *
1430      * @see #setProgress(int)
1431      */
1432     public synchronized final void incrementProgressBy(int diff) {
1433         setProgress(mProgress + diff);
1434     }
1435
1436     /**
1437      * <p>Increase the progress bar's secondary progress by the specified amount.</p>
1438      *
1439      * @param diff the amount by which the secondary progress must be increased
1440      *
1441      * @see #setSecondaryProgress(int)
1442      */
1443     public synchronized final void incrementSecondaryProgressBy(int diff) {
1444         setSecondaryProgress(mSecondaryProgress + diff);
1445     }
1446
1447     /**
1448      * <p>Start the indeterminate progress animation.</p>
1449      */
1450     void startAnimation() {
1451         if (getVisibility() != VISIBLE) {
1452             return;
1453         }
1454
1455         if (mIndeterminateDrawable instanceof Animatable) {
1456             mShouldStartAnimationDrawable = true;
1457             mHasAnimation = false;
1458         } else {
1459             mHasAnimation = true;
1460
1461             if (mInterpolator == null) {
1462                 mInterpolator = new LinearInterpolator();
1463             }
1464
1465             if (mTransformation == null) {
1466                 mTransformation = new Transformation();
1467             } else {
1468                 mTransformation.clear();
1469             }
1470
1471             if (mAnimation == null) {
1472                 mAnimation = new AlphaAnimation(0.0f, 1.0f);
1473             } else {
1474                 mAnimation.reset();
1475             }
1476
1477             mAnimation.setRepeatMode(mBehavior);
1478             mAnimation.setRepeatCount(Animation.INFINITE);
1479             mAnimation.setDuration(mDuration);
1480             mAnimation.setInterpolator(mInterpolator);
1481             mAnimation.setStartTime(Animation.START_ON_FIRST_FRAME);
1482         }
1483         postInvalidate();
1484     }
1485
1486     /**
1487      * <p>Stop the indeterminate progress animation.</p>
1488      */
1489     void stopAnimation() {
1490         mHasAnimation = false;
1491         if (mIndeterminateDrawable instanceof Animatable) {
1492             ((Animatable) mIndeterminateDrawable).stop();
1493             mShouldStartAnimationDrawable = false;
1494         }
1495         postInvalidate();
1496     }
1497
1498     /**
1499      * Sets the acceleration curve for the indeterminate animation.
1500      * The interpolator is loaded as a resource from the specified context.
1501      *
1502      * @param context The application environment
1503      * @param resID The resource identifier of the interpolator to load
1504      */
1505     public void setInterpolator(Context context, @InterpolatorRes int resID) {
1506         setInterpolator(AnimationUtils.loadInterpolator(context, resID));
1507     }
1508
1509     /**
1510      * Sets the acceleration curve for the indeterminate animation.
1511      * Defaults to a linear interpolation.
1512      *
1513      * @param interpolator The interpolator which defines the acceleration curve
1514      */
1515     public void setInterpolator(Interpolator interpolator) {
1516         mInterpolator = interpolator;
1517     }
1518
1519     /**
1520      * Gets the acceleration curve type for the indeterminate animation.
1521      *
1522      * @return the {@link Interpolator} associated to this animation
1523      */
1524     public Interpolator getInterpolator() {
1525         return mInterpolator;
1526     }
1527
1528     @Override
1529     @RemotableViewMethod
1530     public void setVisibility(int v) {
1531         if (getVisibility() != v) {
1532             super.setVisibility(v);
1533
1534             if (mIndeterminate) {
1535                 // let's be nice with the UI thread
1536                 if (v == GONE || v == INVISIBLE) {
1537                     stopAnimation();
1538                 } else {
1539                     startAnimation();
1540                 }
1541             }
1542         }
1543     }
1544
1545     @Override
1546     protected void onVisibilityChanged(View changedView, int visibility) {
1547         super.onVisibilityChanged(changedView, visibility);
1548
1549         if (mIndeterminate) {
1550             // let's be nice with the UI thread
1551             if (visibility == GONE || visibility == INVISIBLE) {
1552                 stopAnimation();
1553             } else {
1554                 startAnimation();
1555             }
1556         }
1557     }
1558
1559     @Override
1560     public void invalidateDrawable(Drawable dr) {
1561         if (!mInDrawing) {
1562             if (verifyDrawable(dr)) {
1563                 final Rect dirty = dr.getBounds();
1564                 final int scrollX = mScrollX + mPaddingLeft;
1565                 final int scrollY = mScrollY + mPaddingTop;
1566
1567                 invalidate(dirty.left + scrollX, dirty.top + scrollY,
1568                         dirty.right + scrollX, dirty.bottom + scrollY);
1569             } else {
1570                 super.invalidateDrawable(dr);
1571             }
1572         }
1573     }
1574
1575     @Override
1576     protected void onSizeChanged(int w, int h, int oldw, int oldh) {
1577         updateDrawableBounds(w, h);
1578     }
1579
1580     private void updateDrawableBounds(int w, int h) {
1581         // onDraw will translate the canvas so we draw starting at 0,0.
1582         // Subtract out padding for the purposes of the calculations below.
1583         w -= mPaddingRight + mPaddingLeft;
1584         h -= mPaddingTop + mPaddingBottom;
1585
1586         int right = w;
1587         int bottom = h;
1588         int top = 0;
1589         int left = 0;
1590
1591         if (mIndeterminateDrawable != null) {
1592             // Aspect ratio logic does not apply to AnimationDrawables
1593             if (mOnlyIndeterminate && !(mIndeterminateDrawable instanceof AnimationDrawable)) {
1594                 // Maintain aspect ratio. Certain kinds of animated drawables
1595                 // get very confused otherwise.
1596                 final int intrinsicWidth = mIndeterminateDrawable.getIntrinsicWidth();
1597                 final int intrinsicHeight = mIndeterminateDrawable.getIntrinsicHeight();
1598                 final float intrinsicAspect = (float) intrinsicWidth / intrinsicHeight;
1599                 final float boundAspect = (float) w / h;
1600                 if (intrinsicAspect != boundAspect) {
1601                     if (boundAspect > intrinsicAspect) {
1602                         // New width is larger. Make it smaller to match height.
1603                         final int width = (int) (h * intrinsicAspect);
1604                         left = (w - width) / 2;
1605                         right = left + width;
1606                     } else {
1607                         // New height is larger. Make it smaller to match width.
1608                         final int height = (int) (w * (1 / intrinsicAspect));
1609                         top = (h - height) / 2;
1610                         bottom = top + height;
1611                     }
1612                 }
1613             }
1614             if (isLayoutRtl() && mMirrorForRtl) {
1615                 int tempLeft = left;
1616                 left = w - right;
1617                 right = w - tempLeft;
1618             }
1619             mIndeterminateDrawable.setBounds(left, top, right, bottom);
1620         }
1621
1622         if (mProgressDrawable != null) {
1623             mProgressDrawable.setBounds(0, 0, right, bottom);
1624         }
1625     }
1626
1627     @Override
1628     protected synchronized void onDraw(Canvas canvas) {
1629         super.onDraw(canvas);
1630
1631         drawTrack(canvas);
1632     }
1633
1634     /**
1635      * Draws the progress bar track.
1636      */
1637     void drawTrack(Canvas canvas) {
1638         final Drawable d = mCurrentDrawable;
1639         if (d != null) {
1640             // Translate canvas so a indeterminate circular progress bar with padding
1641             // rotates properly in its animation
1642             final int saveCount = canvas.save();
1643
1644             if (isLayoutRtl() && mMirrorForRtl) {
1645                 canvas.translate(getWidth() - mPaddingRight, mPaddingTop);
1646                 canvas.scale(-1.0f, 1.0f);
1647             } else {
1648                 canvas.translate(mPaddingLeft, mPaddingTop);
1649             }
1650
1651             final long time = getDrawingTime();
1652             if (mHasAnimation) {
1653                 mAnimation.getTransformation(time, mTransformation);
1654                 final float scale = mTransformation.getAlpha();
1655                 try {
1656                     mInDrawing = true;
1657                     d.setLevel((int) (scale * MAX_LEVEL));
1658                 } finally {
1659                     mInDrawing = false;
1660                 }
1661                 postInvalidateOnAnimation();
1662             }
1663
1664             d.draw(canvas);
1665             canvas.restoreToCount(saveCount);
1666
1667             if (mShouldStartAnimationDrawable && d instanceof Animatable) {
1668                 ((Animatable) d).start();
1669                 mShouldStartAnimationDrawable = false;
1670             }
1671         }
1672     }
1673
1674     @Override
1675     protected synchronized void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
1676         int dw = 0;
1677         int dh = 0;
1678
1679         final Drawable d = mCurrentDrawable;
1680         if (d != null) {
1681             dw = Math.max(mMinWidth, Math.min(mMaxWidth, d.getIntrinsicWidth()));
1682             dh = Math.max(mMinHeight, Math.min(mMaxHeight, d.getIntrinsicHeight()));
1683         }
1684
1685         updateDrawableState();
1686
1687         dw += mPaddingLeft + mPaddingRight;
1688         dh += mPaddingTop + mPaddingBottom;
1689
1690         final int measuredWidth = resolveSizeAndState(dw, widthMeasureSpec, 0);
1691         final int measuredHeight = resolveSizeAndState(dh, heightMeasureSpec, 0);
1692         setMeasuredDimension(measuredWidth, measuredHeight);
1693     }
1694
1695     @Override
1696     protected void drawableStateChanged() {
1697         super.drawableStateChanged();
1698         updateDrawableState();
1699     }
1700
1701     private void updateDrawableState() {
1702         final int[] state = getDrawableState();
1703
1704         if (mProgressDrawable != null && mProgressDrawable.isStateful()) {
1705             mProgressDrawable.setState(state);
1706         }
1707
1708         if (mIndeterminateDrawable != null && mIndeterminateDrawable.isStateful()) {
1709             mIndeterminateDrawable.setState(state);
1710         }
1711     }
1712
1713     @Override
1714     public void drawableHotspotChanged(float x, float y) {
1715         super.drawableHotspotChanged(x, y);
1716
1717         if (mProgressDrawable != null) {
1718             mProgressDrawable.setHotspot(x, y);
1719         }
1720
1721         if (mIndeterminateDrawable != null) {
1722             mIndeterminateDrawable.setHotspot(x, y);
1723         }
1724     }
1725
1726     static class SavedState extends BaseSavedState {
1727         int progress;
1728         int secondaryProgress;
1729
1730         /**
1731          * Constructor called from {@link ProgressBar#onSaveInstanceState()}
1732          */
1733         SavedState(Parcelable superState) {
1734             super(superState);
1735         }
1736
1737         /**
1738          * Constructor called from {@link #CREATOR}
1739          */
1740         private SavedState(Parcel in) {
1741             super(in);
1742             progress = in.readInt();
1743             secondaryProgress = in.readInt();
1744         }
1745
1746         @Override
1747         public void writeToParcel(Parcel out, int flags) {
1748             super.writeToParcel(out, flags);
1749             out.writeInt(progress);
1750             out.writeInt(secondaryProgress);
1751         }
1752
1753         public static final Parcelable.Creator<SavedState> CREATOR
1754                 = new Parcelable.Creator<SavedState>() {
1755             public SavedState createFromParcel(Parcel in) {
1756                 return new SavedState(in);
1757             }
1758
1759             public SavedState[] newArray(int size) {
1760                 return new SavedState[size];
1761             }
1762         };
1763     }
1764
1765     @Override
1766     public Parcelable onSaveInstanceState() {
1767         // Force our ancestor class to save its state
1768         Parcelable superState = super.onSaveInstanceState();
1769         SavedState ss = new SavedState(superState);
1770
1771         ss.progress = mProgress;
1772         ss.secondaryProgress = mSecondaryProgress;
1773
1774         return ss;
1775     }
1776
1777     @Override
1778     public void onRestoreInstanceState(Parcelable state) {
1779         SavedState ss = (SavedState) state;
1780         super.onRestoreInstanceState(ss.getSuperState());
1781
1782         setProgress(ss.progress);
1783         setSecondaryProgress(ss.secondaryProgress);
1784     }
1785
1786     @Override
1787     protected void onAttachedToWindow() {
1788         super.onAttachedToWindow();
1789         if (mIndeterminate) {
1790             startAnimation();
1791         }
1792         if (mRefreshData != null) {
1793             synchronized (this) {
1794                 final int count = mRefreshData.size();
1795                 for (int i = 0; i < count; i++) {
1796                     final RefreshData rd = mRefreshData.get(i);
1797                     doRefreshProgress(rd.id, rd.progress, rd.fromUser, true);
1798                     rd.recycle();
1799                 }
1800                 mRefreshData.clear();
1801             }
1802         }
1803         mAttached = true;
1804     }
1805
1806     @Override
1807     protected void onDetachedFromWindow() {
1808         if (mIndeterminate) {
1809             stopAnimation();
1810         }
1811         if (mRefreshProgressRunnable != null) {
1812             removeCallbacks(mRefreshProgressRunnable);
1813         }
1814         if (mRefreshProgressRunnable != null && mRefreshIsPosted) {
1815             removeCallbacks(mRefreshProgressRunnable);
1816         }
1817         if (mAccessibilityEventSender != null) {
1818             removeCallbacks(mAccessibilityEventSender);
1819         }
1820         // This should come after stopAnimation(), otherwise an invalidate message remains in the
1821         // queue, which can prevent the entire view hierarchy from being GC'ed during a rotation
1822         super.onDetachedFromWindow();
1823         mAttached = false;
1824     }
1825
1826     @Override
1827     public CharSequence getAccessibilityClassName() {
1828         return ProgressBar.class.getName();
1829     }
1830
1831     /** @hide */
1832     @Override
1833     public void onInitializeAccessibilityEventInternal(AccessibilityEvent event) {
1834         super.onInitializeAccessibilityEventInternal(event);
1835         event.setItemCount(mMax);
1836         event.setCurrentItemIndex(mProgress);
1837     }
1838
1839     /**
1840      * Schedule a command for sending an accessibility event.
1841      * </br>
1842      * Note: A command is used to ensure that accessibility events
1843      *       are sent at most one in a given time frame to save
1844      *       system resources while the progress changes quickly.
1845      */
1846     private void scheduleAccessibilityEventSender() {
1847         if (mAccessibilityEventSender == null) {
1848             mAccessibilityEventSender = new AccessibilityEventSender();
1849         } else {
1850             removeCallbacks(mAccessibilityEventSender);
1851         }
1852         postDelayed(mAccessibilityEventSender, TIMEOUT_SEND_ACCESSIBILITY_EVENT);
1853     }
1854
1855     /**
1856      * Command for sending an accessibility event.
1857      */
1858     private class AccessibilityEventSender implements Runnable {
1859         public void run() {
1860             sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_SELECTED);
1861         }
1862     }
1863
1864     private static class ProgressTintInfo {
1865         ColorStateList mIndeterminateTintList;
1866         PorterDuff.Mode mIndeterminateTintMode;
1867         boolean mHasIndeterminateTint;
1868         boolean mHasIndeterminateTintMode;
1869
1870         ColorStateList mProgressTintList;
1871         PorterDuff.Mode mProgressTintMode;
1872         boolean mHasProgressTint;
1873         boolean mHasProgressTintMode;
1874
1875         ColorStateList mProgressBackgroundTintList;
1876         PorterDuff.Mode mProgressBackgroundTintMode;
1877         boolean mHasProgressBackgroundTint;
1878         boolean mHasProgressBackgroundTintMode;
1879
1880         ColorStateList mSecondaryProgressTintList;
1881         PorterDuff.Mode mSecondaryProgressTintMode;
1882         boolean mHasSecondaryProgressTint;
1883         boolean mHasSecondaryProgressTintMode;
1884     }
1885 }