ProgressBar.java revision ae12ee34f36226fce4de2314dbd69541d1a8863a
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
17package android.widget;
18
19import android.annotation.Nullable;
20import android.graphics.PorterDuff;
21import com.android.internal.R;
22
23import android.content.Context;
24import android.content.res.ColorStateList;
25import android.content.res.TypedArray;
26import android.graphics.Bitmap;
27import android.graphics.BitmapShader;
28import android.graphics.Canvas;
29import android.graphics.PorterDuff.Mode;
30import android.graphics.Rect;
31import android.graphics.Shader;
32import android.graphics.drawable.Animatable;
33import android.graphics.drawable.AnimationDrawable;
34import android.graphics.drawable.BitmapDrawable;
35import android.graphics.drawable.ClipDrawable;
36import android.graphics.drawable.Drawable;
37import android.graphics.drawable.LayerDrawable;
38import android.graphics.drawable.ShapeDrawable;
39import android.graphics.drawable.StateListDrawable;
40import android.graphics.drawable.shapes.RoundRectShape;
41import android.graphics.drawable.shapes.Shape;
42import android.os.Parcel;
43import android.os.Parcelable;
44import android.util.AttributeSet;
45import android.util.Pools.SynchronizedPool;
46import android.view.Gravity;
47import android.view.RemotableViewMethod;
48import android.view.View;
49import android.view.ViewDebug;
50import android.view.accessibility.AccessibilityEvent;
51import android.view.accessibility.AccessibilityManager;
52import android.view.accessibility.AccessibilityNodeInfo;
53import android.view.animation.AlphaAnimation;
54import android.view.animation.Animation;
55import android.view.animation.AnimationUtils;
56import android.view.animation.Interpolator;
57import android.view.animation.LinearInterpolator;
58import android.view.animation.Transformation;
59import android.widget.RemoteViews.RemoteView;
60
61import 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
197public 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
403        if (drawable instanceof LayerDrawable) {
404            LayerDrawable background = (LayerDrawable) drawable;
405            final int N = background.getNumberOfLayers();
406            Drawable[] outDrawables = new Drawable[N];
407
408            for (int i = 0; i < N; i++) {
409                int id = background.getId(i);
410                outDrawables[i] = tileify(background.getDrawable(i),
411                        (id == R.id.progress || id == R.id.secondaryProgress));
412            }
413
414            LayerDrawable newBg = new LayerDrawable(outDrawables);
415
416            for (int i = 0; i < N; i++) {
417                newBg.setId(i, background.getId(i));
418            }
419
420            return newBg;
421
422        } else if (drawable instanceof StateListDrawable) {
423            StateListDrawable in = (StateListDrawable) drawable;
424            StateListDrawable out = new StateListDrawable();
425            int numStates = in.getStateCount();
426            for (int i = 0; i < numStates; i++) {
427                out.addState(in.getStateSet(i), tileify(in.getStateDrawable(i), clip));
428            }
429            return out;
430
431        } else if (drawable instanceof BitmapDrawable) {
432            final BitmapDrawable bitmap = (BitmapDrawable) drawable;
433            final Bitmap tileBitmap = bitmap.getBitmap();
434            if (mSampleTile == null) {
435                mSampleTile = tileBitmap;
436            }
437
438            final ShapeDrawable shapeDrawable = new ShapeDrawable(getDrawableShape());
439            final BitmapShader bitmapShader = new BitmapShader(tileBitmap,
440                    Shader.TileMode.REPEAT, Shader.TileMode.CLAMP);
441            shapeDrawable.getPaint().setShader(bitmapShader);
442
443            // Ensure the tint and filter are propagated in the correct order.
444            shapeDrawable.setTintList(bitmap.getTint());
445            shapeDrawable.setTintMode(bitmap.getTintMode());
446            shapeDrawable.setColorFilter(bitmap.getColorFilter());
447
448            return clip ? new ClipDrawable(
449                    shapeDrawable, Gravity.LEFT, ClipDrawable.HORIZONTAL) : shapeDrawable;
450        }
451
452        return drawable;
453    }
454
455    Shape getDrawableShape() {
456        final float[] roundedCorners = new float[] { 5, 5, 5, 5, 5, 5, 5, 5 };
457        return new RoundRectShape(roundedCorners, null, null);
458    }
459
460    /**
461     * Convert a AnimationDrawable for use as a barberpole animation.
462     * Each frame of the animation is wrapped in a ClipDrawable and
463     * given a tiling BitmapShader.
464     */
465    private Drawable tileifyIndeterminate(Drawable drawable) {
466        if (drawable instanceof AnimationDrawable) {
467            AnimationDrawable background = (AnimationDrawable) drawable;
468            final int N = background.getNumberOfFrames();
469            AnimationDrawable newBg = new AnimationDrawable();
470            newBg.setOneShot(background.isOneShot());
471
472            for (int i = 0; i < N; i++) {
473                Drawable frame = tileify(background.getFrame(i), true);
474                frame.setLevel(10000);
475                newBg.addFrame(frame, background.getDuration(i));
476            }
477            newBg.setLevel(10000);
478            drawable = newBg;
479        }
480        return drawable;
481    }
482
483    /**
484     * <p>
485     * Initialize the progress bar's default values:
486     * </p>
487     * <ul>
488     * <li>progress = 0</li>
489     * <li>max = 100</li>
490     * <li>animation duration = 4000 ms</li>
491     * <li>indeterminate = false</li>
492     * <li>behavior = repeat</li>
493     * </ul>
494     */
495    private void initProgressBar() {
496        mMax = 100;
497        mProgress = 0;
498        mSecondaryProgress = 0;
499        mIndeterminate = false;
500        mOnlyIndeterminate = false;
501        mDuration = 4000;
502        mBehavior = AlphaAnimation.RESTART;
503        mMinWidth = 24;
504        mMaxWidth = 48;
505        mMinHeight = 24;
506        mMaxHeight = 48;
507    }
508
509    /**
510     * <p>Indicate whether this progress bar is in indeterminate mode.</p>
511     *
512     * @return true if the progress bar is in indeterminate mode
513     */
514    @ViewDebug.ExportedProperty(category = "progress")
515    public synchronized boolean isIndeterminate() {
516        return mIndeterminate;
517    }
518
519    /**
520     * <p>Change the indeterminate mode for this progress bar. In indeterminate
521     * mode, the progress is ignored and the progress bar shows an infinite
522     * animation instead.</p>
523     *
524     * If this progress bar's style only supports indeterminate mode (such as the circular
525     * progress bars), then this will be ignored.
526     *
527     * @param indeterminate true to enable the indeterminate mode
528     */
529    @android.view.RemotableViewMethod
530    public synchronized void setIndeterminate(boolean indeterminate) {
531        if ((!mOnlyIndeterminate || !mIndeterminate) && indeterminate != mIndeterminate) {
532            mIndeterminate = indeterminate;
533
534            if (indeterminate) {
535                // swap between indeterminate and regular backgrounds
536                mCurrentDrawable = mIndeterminateDrawable;
537                startAnimation();
538            } else {
539                mCurrentDrawable = mProgressDrawable;
540                stopAnimation();
541            }
542        }
543    }
544
545    /**
546     * <p>Get the drawable used to draw the progress bar in
547     * indeterminate mode.</p>
548     *
549     * @return a {@link android.graphics.drawable.Drawable} instance
550     *
551     * @see #setIndeterminateDrawable(android.graphics.drawable.Drawable)
552     * @see #setIndeterminate(boolean)
553     */
554    public Drawable getIndeterminateDrawable() {
555        return mIndeterminateDrawable;
556    }
557
558    /**
559     * Define the drawable used to draw the progress bar in indeterminate mode.
560     *
561     * @param d the new drawable
562     * @see #getIndeterminateDrawable()
563     * @see #setIndeterminate(boolean)
564     */
565    public void setIndeterminateDrawable(Drawable d) {
566        if (mIndeterminateDrawable != d) {
567            if (mIndeterminateDrawable != null) {
568                mIndeterminateDrawable.setCallback(null);
569                unscheduleDrawable(mIndeterminateDrawable);
570            }
571
572            mIndeterminateDrawable = d;
573
574            if (d != null) {
575                d.setCallback(this);
576                d.setLayoutDirection(getLayoutDirection());
577                if (d.isStateful()) {
578                    d.setState(getDrawableState());
579                }
580                applyIndeterminateTint();
581            }
582
583            if (mIndeterminate) {
584                mCurrentDrawable = d;
585                postInvalidate();
586            }
587        }
588    }
589
590    /**
591     * Applies a tint to the indeterminate drawable. Does not modify the
592     * current tint mode, which is {@link PorterDuff.Mode#SRC_IN} by default.
593     * <p>
594     * Subsequent calls to {@link #setIndeterminateDrawable(Drawable)} will
595     * automatically mutate the drawable and apply the specified tint and
596     * tint mode using
597     * {@link Drawable#setTintList(ColorStateList)}.
598     *
599     * @param tint the tint to apply, may be {@code null} to clear tint
600     *
601     * @attr ref android.R.styleable#ProgressBar_indeterminateTint
602     * @see #getIndeterminateTintList()
603     * @see Drawable#setTintList(ColorStateList)
604     */
605    @RemotableViewMethod
606    public void setIndeterminateTintList(@Nullable ColorStateList tint) {
607        if (mProgressTintInfo == null) {
608            mProgressTintInfo = new ProgressTintInfo();
609        }
610        mProgressTintInfo.mIndeterminateTintList = tint;
611        mProgressTintInfo.mHasIndeterminateTint = true;
612
613        applyIndeterminateTint();
614    }
615
616    /**
617     * @return the tint applied to the indeterminate drawable
618     * @attr ref android.R.styleable#ProgressBar_indeterminateTint
619     * @see #setIndeterminateTintList(ColorStateList)
620     */
621    @Nullable
622    public ColorStateList getIndeterminateTintList() {
623        return mProgressTintInfo != null ? mProgressTintInfo.mIndeterminateTintList : null;
624    }
625
626    /**
627     * Specifies the blending mode used to apply the tint specified by
628     * {@link #setIndeterminateTintList(ColorStateList)} to the indeterminate
629     * drawable. The default mode is {@link PorterDuff.Mode#SRC_IN}.
630     *
631     * @param tintMode the blending mode used to apply the tint, may be
632     *                 {@code null} to clear tint
633     * @attr ref android.R.styleable#ProgressBar_indeterminateTintMode
634     * @see #setIndeterminateTintList(ColorStateList)
635     * @see Drawable#setTintMode(PorterDuff.Mode)
636     */
637    public void setIndeterminateTintMode(@Nullable PorterDuff.Mode tintMode) {
638        if (mProgressTintInfo == null) {
639            mProgressTintInfo = new ProgressTintInfo();
640        }
641        mProgressTintInfo.mIndeterminateTintMode = tintMode;
642        mProgressTintInfo.mHasIndeterminateTintMode = true;
643
644        applyIndeterminateTint();
645    }
646
647    /**
648     * Returns the blending mode used to apply the tint to the indeterminate
649     * drawable, if specified.
650     *
651     * @return the blending mode used to apply the tint to the indeterminate
652     *         drawable
653     * @attr ref android.R.styleable#ProgressBar_indeterminateTintMode
654     * @see #setIndeterminateTintMode(PorterDuff.Mode)
655     */
656    @Nullable
657    public PorterDuff.Mode getIndeterminateTintMode() {
658        return mProgressTintInfo != null ? mProgressTintInfo.mIndeterminateTintMode : null;
659    }
660
661    private void applyIndeterminateTint() {
662        if (mIndeterminateDrawable != null && mProgressTintInfo != null) {
663            final ProgressTintInfo tintInfo = mProgressTintInfo;
664            if (tintInfo.mHasIndeterminateTint || tintInfo.mHasIndeterminateTintMode) {
665                mIndeterminateDrawable = mIndeterminateDrawable.mutate();
666
667                if (tintInfo.mHasIndeterminateTint) {
668                    mIndeterminateDrawable.setTintList(tintInfo.mIndeterminateTintList);
669                }
670
671                if (tintInfo.mHasIndeterminateTintMode) {
672                    mIndeterminateDrawable.setTintMode(tintInfo.mIndeterminateTintMode);
673                }
674
675                // The drawable (or one of its children) may not have been
676                // stateful before applying the tint, so let's try again.
677                if (mIndeterminateDrawable.isStateful()) {
678                    mIndeterminateDrawable.setState(getDrawableState());
679                }
680            }
681        }
682    }
683
684    /**
685     * Define the tileable drawable used to draw the progress bar in
686     * indeterminate mode.
687     * <p>
688     * If the drawable is a BitmapDrawable or contains BitmapDrawables, a
689     * tiled copy will be generated for display as a progress bar.
690     *
691     * @param d the new drawable
692     * @see #getIndeterminateDrawable()
693     * @see #setIndeterminate(boolean)
694     */
695    public void setIndeterminateDrawableTiled(Drawable d) {
696        if (d != null) {
697            d = tileifyIndeterminate(d);
698        }
699
700        setIndeterminateDrawable(d);
701    }
702
703    /**
704     * <p>Get the drawable used to draw the progress bar in
705     * progress mode.</p>
706     *
707     * @return a {@link android.graphics.drawable.Drawable} instance
708     *
709     * @see #setProgressDrawable(android.graphics.drawable.Drawable)
710     * @see #setIndeterminate(boolean)
711     */
712    public Drawable getProgressDrawable() {
713        return mProgressDrawable;
714    }
715
716    /**
717     * Define the drawable used to draw the progress bar in progress mode.
718     *
719     * @param d the new drawable
720     * @see #getProgressDrawable()
721     * @see #setIndeterminate(boolean)
722     */
723    public void setProgressDrawable(Drawable d) {
724        if (mProgressDrawable != d) {
725            if (mProgressDrawable != null) {
726                mProgressDrawable.setCallback(null);
727                unscheduleDrawable(mProgressDrawable);
728            }
729
730            mProgressDrawable = d;
731
732            if (d != null) {
733                d.setCallback(this);
734                d.setLayoutDirection(getLayoutDirection());
735                if (d.isStateful()) {
736                    d.setState(getDrawableState());
737                }
738
739                // Make sure the ProgressBar is always tall enough
740                int drawableHeight = d.getMinimumHeight();
741                if (mMaxHeight < drawableHeight) {
742                    mMaxHeight = drawableHeight;
743                    requestLayout();
744                }
745
746                applyProgressTints();
747            }
748
749            if (!mIndeterminate) {
750                mCurrentDrawable = d;
751                postInvalidate();
752            }
753
754            updateDrawableBounds(getWidth(), getHeight());
755            updateDrawableState();
756
757            doRefreshProgress(R.id.progress, mProgress, false, false);
758            doRefreshProgress(R.id.secondaryProgress, mSecondaryProgress, false, false);
759        }
760    }
761
762    /**
763     * Applies the progress tints in order of increasing specificity.
764     */
765    private void applyProgressTints() {
766        if (mProgressDrawable != null && mProgressTintInfo != null) {
767            applyPrimaryProgressTint();
768            applyProgressBackgroundTint();
769            applySecondaryProgressTint();
770        }
771    }
772
773    /**
774     * Should only be called if we've already verified that mProgressDrawable
775     * and mProgressTintInfo are non-null.
776     */
777    private void applyPrimaryProgressTint() {
778        if (mProgressTintInfo.mHasProgressTint
779                || mProgressTintInfo.mHasProgressTintMode) {
780            final Drawable target = getTintTarget(R.id.progress, true);
781            if (target != null) {
782                if (mProgressTintInfo.mHasProgressTint) {
783                    target.setTintList(mProgressTintInfo.mProgressTintList);
784                }
785                if (mProgressTintInfo.mHasProgressTintMode) {
786                    target.setTintMode(mProgressTintInfo.mProgressTintMode);
787                }
788
789                // The drawable (or one of its children) may not have been
790                // stateful before applying the tint, so let's try again.
791                if (target.isStateful()) {
792                    target.setState(getDrawableState());
793                }
794            }
795        }
796    }
797
798    /**
799     * Should only be called if we've already verified that mProgressDrawable
800     * and mProgressTintInfo are non-null.
801     */
802    private void applyProgressBackgroundTint() {
803        if (mProgressTintInfo.mHasProgressBackgroundTint
804                || mProgressTintInfo.mHasProgressBackgroundTintMode) {
805            final Drawable target = getTintTarget(R.id.background, false);
806            if (target != null) {
807                if (mProgressTintInfo.mHasProgressBackgroundTint) {
808                    target.setTintList(mProgressTintInfo.mProgressBackgroundTintList);
809                }
810                if (mProgressTintInfo.mHasProgressBackgroundTintMode) {
811                    target.setTintMode(mProgressTintInfo.mProgressBackgroundTintMode);
812                }
813
814                // The drawable (or one of its children) may not have been
815                // stateful before applying the tint, so let's try again.
816                if (target.isStateful()) {
817                    target.setState(getDrawableState());
818                }
819            }
820        }
821    }
822
823    /**
824     * Should only be called if we've already verified that mProgressDrawable
825     * and mProgressTintInfo are non-null.
826     */
827    private void applySecondaryProgressTint() {
828        if (mProgressTintInfo.mHasSecondaryProgressTint
829                || mProgressTintInfo.mHasSecondaryProgressTintMode) {
830            final Drawable target = getTintTarget(R.id.secondaryProgress, false);
831            if (target != null) {
832                if (mProgressTintInfo.mHasSecondaryProgressTint) {
833                    target.setTintList(mProgressTintInfo.mSecondaryProgressTintList);
834                }
835                if (mProgressTintInfo.mHasSecondaryProgressTintMode) {
836                    target.setTintMode(mProgressTintInfo.mSecondaryProgressTintMode);
837                }
838
839                // The drawable (or one of its children) may not have been
840                // stateful before applying the tint, so let's try again.
841                if (target.isStateful()) {
842                    target.setState(getDrawableState());
843                }
844            }
845        }
846    }
847
848    /**
849     * Applies a tint to the progress indicator, if one exists, or to the
850     * entire progress drawable otherwise. Does not modify the current tint
851     * mode, which is {@link PorterDuff.Mode#SRC_IN} by default.
852     * <p>
853     * The progress indicator should be specified as a layer with
854     * id {@link android.R.id#progress} in a {@link LayerDrawable}
855     * used as the progress drawable.
856     * <p>
857     * Subsequent calls to {@link #setProgressDrawable(Drawable)} will
858     * automatically mutate the drawable and apply the specified tint and
859     * tint mode using
860     * {@link Drawable#setTintList(ColorStateList)}.
861     *
862     * @param tint the tint to apply, may be {@code null} to clear tint
863     *
864     * @attr ref android.R.styleable#ProgressBar_progressTint
865     * @see #getProgressTintList()
866     * @see Drawable#setTintList(ColorStateList)
867     */
868    @RemotableViewMethod
869    public void setProgressTintList(@Nullable ColorStateList tint) {
870        if (mProgressTintInfo == null) {
871            mProgressTintInfo = new ProgressTintInfo();
872        }
873        mProgressTintInfo.mProgressTintList = tint;
874        mProgressTintInfo.mHasProgressTint = true;
875
876        if (mProgressDrawable != null) {
877            applyPrimaryProgressTint();
878        }
879    }
880
881    /**
882     * Returns the tint applied to the progress drawable, if specified.
883     *
884     * @return the tint applied to the progress drawable
885     * @attr ref android.R.styleable#ProgressBar_progressTint
886     * @see #setProgressTintList(ColorStateList)
887     */
888    @Nullable
889    public ColorStateList getProgressTintList() {
890        return mProgressTintInfo != null ? mProgressTintInfo.mProgressTintList : null;
891    }
892
893    /**
894     * Specifies the blending mode used to apply the tint specified by
895     * {@link #setProgressTintList(ColorStateList)}} to the progress
896     * indicator. The default mode is {@link PorterDuff.Mode#SRC_IN}.
897     *
898     * @param tintMode the blending mode used to apply the tint, may be
899     *                 {@code null} to clear tint
900     * @attr ref android.R.styleable#ProgressBar_progressTintMode
901     * @see #getProgressTintMode()
902     * @see Drawable#setTintMode(PorterDuff.Mode)
903     */
904    public void setProgressTintMode(@Nullable PorterDuff.Mode tintMode) {
905        if (mProgressTintInfo == null) {
906            mProgressTintInfo = new ProgressTintInfo();
907        }
908        mProgressTintInfo.mProgressTintMode = tintMode;
909        mProgressTintInfo.mHasProgressTintMode = true;
910
911        if (mProgressDrawable != null) {
912            applyPrimaryProgressTint();
913        }
914    }
915
916    /**
917     * Returns the blending mode used to apply the tint to the progress
918     * drawable, if specified.
919     *
920     * @return the blending mode used to apply the tint to the progress
921     *         drawable
922     * @attr ref android.R.styleable#ProgressBar_progressTintMode
923     * @see #setProgressTintMode(PorterDuff.Mode)
924     */
925    @Nullable
926    public PorterDuff.Mode getProgressTintMode() {
927        return mProgressTintInfo != null ? mProgressTintInfo.mProgressTintMode : null;
928    }
929
930    /**
931     * Applies a tint to the progress background, if one exists. Does not
932     * modify the current tint mode, which is
933     * {@link PorterDuff.Mode#SRC_ATOP} by default.
934     * <p>
935     * The progress background must be specified as a layer with
936     * id {@link android.R.id#background} in a {@link LayerDrawable}
937     * used as the progress drawable.
938     * <p>
939     * Subsequent calls to {@link #setProgressDrawable(Drawable)} where the
940     * drawable contains a progress background will automatically mutate the
941     * drawable and apply the specified tint and tint mode using
942     * {@link Drawable#setTintList(ColorStateList)}.
943     *
944     * @param tint the tint to apply, may be {@code null} to clear tint
945     *
946     * @attr ref android.R.styleable#ProgressBar_progressBackgroundTint
947     * @see #getProgressBackgroundTintList()
948     * @see Drawable#setTintList(ColorStateList)
949     */
950    @RemotableViewMethod
951    public void setProgressBackgroundTintList(@Nullable ColorStateList tint) {
952        if (mProgressTintInfo == null) {
953            mProgressTintInfo = new ProgressTintInfo();
954        }
955        mProgressTintInfo.mProgressBackgroundTintList = tint;
956        mProgressTintInfo.mHasProgressBackgroundTint = true;
957
958        if (mProgressDrawable != null) {
959            applyProgressBackgroundTint();
960        }
961    }
962
963    /**
964     * Returns the tint applied to the progress background, if specified.
965     *
966     * @return the tint applied to the progress background
967     * @attr ref android.R.styleable#ProgressBar_progressBackgroundTint
968     * @see #setProgressBackgroundTintList(ColorStateList)
969     */
970    @Nullable
971    public ColorStateList getProgressBackgroundTintList() {
972        return mProgressTintInfo != null ? mProgressTintInfo.mProgressBackgroundTintList : null;
973    }
974
975    /**
976     * Specifies the blending mode used to apply the tint specified by
977     * {@link #setProgressBackgroundTintList(ColorStateList)}} to the progress
978     * background. The default mode is {@link PorterDuff.Mode#SRC_IN}.
979     *
980     * @param tintMode the blending mode used to apply the tint, may be
981     *                 {@code null} to clear tint
982     * @attr ref android.R.styleable#ProgressBar_progressBackgroundTintMode
983     * @see #setProgressBackgroundTintList(ColorStateList)
984     * @see Drawable#setTintMode(PorterDuff.Mode)
985     */
986    public void setProgressBackgroundTintMode(@Nullable PorterDuff.Mode tintMode) {
987        if (mProgressTintInfo == null) {
988            mProgressTintInfo = new ProgressTintInfo();
989        }
990        mProgressTintInfo.mProgressBackgroundTintMode = tintMode;
991        mProgressTintInfo.mHasProgressBackgroundTintMode = true;
992
993        if (mProgressDrawable != null) {
994            applyProgressBackgroundTint();
995        }
996    }
997
998    /**
999     * @return the blending mode used to apply the tint to the progress
1000     *         background
1001     * @attr ref android.R.styleable#ProgressBar_progressBackgroundTintMode
1002     * @see #setProgressBackgroundTintMode(PorterDuff.Mode)
1003     */
1004    @Nullable
1005    public PorterDuff.Mode getProgressBackgroundTintMode() {
1006        return mProgressTintInfo != null ? mProgressTintInfo.mProgressBackgroundTintMode : null;
1007    }
1008
1009    /**
1010     * Applies a tint to the secondary progress indicator, if one exists.
1011     * Does not modify the current tint mode, which is
1012     * {@link PorterDuff.Mode#SRC_ATOP} by default.
1013     * <p>
1014     * The secondary progress indicator must be specified as a layer with
1015     * id {@link android.R.id#secondaryProgress} in a {@link LayerDrawable}
1016     * used as the progress drawable.
1017     * <p>
1018     * Subsequent calls to {@link #setProgressDrawable(Drawable)} where the
1019     * drawable contains a secondary progress indicator will automatically
1020     * mutate the drawable and apply the specified tint and tint mode using
1021     * {@link Drawable#setTintList(ColorStateList)}.
1022     *
1023     * @param tint the tint to apply, may be {@code null} to clear tint
1024     *
1025     * @attr ref android.R.styleable#ProgressBar_secondaryProgressTint
1026     * @see #getSecondaryProgressTintList()
1027     * @see Drawable#setTintList(ColorStateList)
1028     */
1029    public void setSecondaryProgressTintList(@Nullable ColorStateList tint) {
1030        if (mProgressTintInfo == null) {
1031            mProgressTintInfo = new ProgressTintInfo();
1032        }
1033        mProgressTintInfo.mSecondaryProgressTintList = tint;
1034        mProgressTintInfo.mHasSecondaryProgressTint = true;
1035
1036        if (mProgressDrawable != null) {
1037            applySecondaryProgressTint();
1038        }
1039    }
1040
1041    /**
1042     * Returns the tint applied to the secondary progress drawable, if
1043     * specified.
1044     *
1045     * @return the tint applied to the secondary progress drawable
1046     * @attr ref android.R.styleable#ProgressBar_secondaryProgressTint
1047     * @see #setSecondaryProgressTintList(ColorStateList)
1048     */
1049    @Nullable
1050    public ColorStateList getSecondaryProgressTintList() {
1051        return mProgressTintInfo != null ? mProgressTintInfo.mSecondaryProgressTintList : null;
1052    }
1053
1054    /**
1055     * Specifies the blending mode used to apply the tint specified by
1056     * {@link #setSecondaryProgressTintList(ColorStateList)}} to the secondary
1057     * progress indicator. The default mode is
1058     * {@link PorterDuff.Mode#SRC_ATOP}.
1059     *
1060     * @param tintMode the blending mode used to apply the tint, may be
1061     *                 {@code null} to clear tint
1062     * @attr ref android.R.styleable#ProgressBar_secondaryProgressTintMode
1063     * @see #setSecondaryProgressTintList(ColorStateList)
1064     * @see Drawable#setTintMode(PorterDuff.Mode)
1065     */
1066    public void setSecondaryProgressTintMode(@Nullable PorterDuff.Mode tintMode) {
1067        if (mProgressTintInfo == null) {
1068            mProgressTintInfo = new ProgressTintInfo();
1069        }
1070        mProgressTintInfo.mSecondaryProgressTintMode = tintMode;
1071        mProgressTintInfo.mHasSecondaryProgressTintMode = true;
1072
1073        if (mProgressDrawable != null) {
1074            applySecondaryProgressTint();
1075        }
1076    }
1077
1078    /**
1079     * Returns the blending mode used to apply the tint to the secondary
1080     * progress drawable, if specified.
1081     *
1082     * @return the blending mode used to apply the tint to the secondary
1083     *         progress drawable
1084     * @attr ref android.R.styleable#ProgressBar_secondaryProgressTintMode
1085     * @see #setSecondaryProgressTintMode(PorterDuff.Mode)
1086     */
1087    @Nullable
1088    public PorterDuff.Mode getSecondaryProgressTintMode() {
1089        return mProgressTintInfo != null ? mProgressTintInfo.mSecondaryProgressTintMode : null;
1090    }
1091
1092    /**
1093     * Returns the drawable to which a tint or tint mode should be applied.
1094     *
1095     * @param layerId id of the layer to modify
1096     * @param shouldFallback whether the base drawable should be returned
1097     *                       if the id does not exist
1098     * @return the drawable to modify
1099     */
1100    @Nullable
1101    private Drawable getTintTarget(int layerId, boolean shouldFallback) {
1102        Drawable layer = null;
1103
1104        final Drawable d = mProgressDrawable;
1105        if (d != null) {
1106            mProgressDrawable = d.mutate();
1107
1108            if (d instanceof LayerDrawable) {
1109                layer = ((LayerDrawable) d).findDrawableByLayerId(layerId);
1110            }
1111
1112            if (shouldFallback && layer == null) {
1113                layer = d;
1114            }
1115        }
1116
1117        return layer;
1118    }
1119
1120    /**
1121     * Define the tileable drawable used to draw the progress bar in
1122     * progress mode.
1123     * <p>
1124     * If the drawable is a BitmapDrawable or contains BitmapDrawables, a
1125     * tiled copy will be generated for display as a progress bar.
1126     *
1127     * @param d the new drawable
1128     * @see #getProgressDrawable()
1129     * @see #setIndeterminate(boolean)
1130     */
1131    public void setProgressDrawableTiled(Drawable d) {
1132        if (d != null) {
1133            d = tileify(d, false);
1134        }
1135
1136        setProgressDrawable(d);
1137    }
1138
1139    /**
1140     * @return The drawable currently used to draw the progress bar
1141     */
1142    Drawable getCurrentDrawable() {
1143        return mCurrentDrawable;
1144    }
1145
1146    @Override
1147    protected boolean verifyDrawable(Drawable who) {
1148        return who == mProgressDrawable || who == mIndeterminateDrawable
1149                || super.verifyDrawable(who);
1150    }
1151
1152    @Override
1153    public void jumpDrawablesToCurrentState() {
1154        super.jumpDrawablesToCurrentState();
1155        if (mProgressDrawable != null) mProgressDrawable.jumpToCurrentState();
1156        if (mIndeterminateDrawable != null) mIndeterminateDrawable.jumpToCurrentState();
1157    }
1158
1159    /**
1160     * @hide
1161     */
1162    @Override
1163    public void onResolveDrawables(int layoutDirection) {
1164        final Drawable d = mCurrentDrawable;
1165        if (d != null) {
1166            d.setLayoutDirection(layoutDirection);
1167        }
1168        if (mIndeterminateDrawable != null) {
1169            mIndeterminateDrawable.setLayoutDirection(layoutDirection);
1170        }
1171        if (mProgressDrawable != null) {
1172            mProgressDrawable.setLayoutDirection(layoutDirection);
1173        }
1174    }
1175
1176    @Override
1177    public void postInvalidate() {
1178        if (!mNoInvalidate) {
1179            super.postInvalidate();
1180        }
1181    }
1182
1183    private class RefreshProgressRunnable implements Runnable {
1184        public void run() {
1185            synchronized (ProgressBar.this) {
1186                final int count = mRefreshData.size();
1187                for (int i = 0; i < count; i++) {
1188                    final RefreshData rd = mRefreshData.get(i);
1189                    doRefreshProgress(rd.id, rd.progress, rd.fromUser, true);
1190                    rd.recycle();
1191                }
1192                mRefreshData.clear();
1193                mRefreshIsPosted = false;
1194            }
1195        }
1196    }
1197
1198    private static class RefreshData {
1199        private static final int POOL_MAX = 24;
1200        private static final SynchronizedPool<RefreshData> sPool =
1201                new SynchronizedPool<RefreshData>(POOL_MAX);
1202
1203        public int id;
1204        public int progress;
1205        public boolean fromUser;
1206
1207        public static RefreshData obtain(int id, int progress, boolean fromUser) {
1208            RefreshData rd = sPool.acquire();
1209            if (rd == null) {
1210                rd = new RefreshData();
1211            }
1212            rd.id = id;
1213            rd.progress = progress;
1214            rd.fromUser = fromUser;
1215            return rd;
1216        }
1217
1218        public void recycle() {
1219            sPool.release(this);
1220        }
1221    }
1222
1223    private void setDrawableTint(int id, ColorStateList tint, Mode tintMode, boolean fallback) {
1224        Drawable layer = null;
1225
1226        // We expect a layer drawable, so try to find the target ID.
1227        final Drawable d = mCurrentDrawable;
1228        if (d instanceof LayerDrawable) {
1229            layer = ((LayerDrawable) d).findDrawableByLayerId(id);
1230        }
1231
1232        if (fallback && layer == null) {
1233            layer = d;
1234        }
1235
1236        layer.mutate();
1237        layer.setTintList(tint);
1238        layer.setTintMode(tintMode);
1239    }
1240
1241    private synchronized void doRefreshProgress(int id, int progress, boolean fromUser,
1242            boolean callBackToApp) {
1243        float scale = mMax > 0 ? (float) progress / (float) mMax : 0;
1244        final Drawable d = mCurrentDrawable.getCurrent();
1245        if (d != null) {
1246            Drawable progressDrawable = null;
1247
1248            if (d instanceof LayerDrawable) {
1249                progressDrawable = ((LayerDrawable) d).findDrawableByLayerId(id);
1250                if (progressDrawable != null && canResolveLayoutDirection()) {
1251                    progressDrawable.setLayoutDirection(getLayoutDirection());
1252                }
1253            }
1254
1255            final int level = (int) (scale * MAX_LEVEL);
1256            (progressDrawable != null ? progressDrawable : d).setLevel(level);
1257        } else {
1258            invalidate();
1259        }
1260
1261        if (callBackToApp && id == R.id.progress) {
1262            onProgressRefresh(scale, fromUser);
1263        }
1264    }
1265
1266    void onProgressRefresh(float scale, boolean fromUser) {
1267        if (AccessibilityManager.getInstance(mContext).isEnabled()) {
1268            scheduleAccessibilityEventSender();
1269        }
1270    }
1271
1272    private synchronized void refreshProgress(int id, int progress, boolean fromUser) {
1273        if (mUiThreadId == Thread.currentThread().getId()) {
1274            doRefreshProgress(id, progress, fromUser, true);
1275        } else {
1276            if (mRefreshProgressRunnable == null) {
1277                mRefreshProgressRunnable = new RefreshProgressRunnable();
1278            }
1279
1280            final RefreshData rd = RefreshData.obtain(id, progress, fromUser);
1281            mRefreshData.add(rd);
1282            if (mAttached && !mRefreshIsPosted) {
1283                post(mRefreshProgressRunnable);
1284                mRefreshIsPosted = true;
1285            }
1286        }
1287    }
1288
1289    /**
1290     * <p>Set the current progress to the specified value. Does not do anything
1291     * if the progress bar is in indeterminate mode.</p>
1292     *
1293     * @param progress the new progress, between 0 and {@link #getMax()}
1294     *
1295     * @see #setIndeterminate(boolean)
1296     * @see #isIndeterminate()
1297     * @see #getProgress()
1298     * @see #incrementProgressBy(int)
1299     */
1300    @android.view.RemotableViewMethod
1301    public synchronized void setProgress(int progress) {
1302        setProgress(progress, false);
1303    }
1304
1305    @android.view.RemotableViewMethod
1306    synchronized void setProgress(int progress, boolean fromUser) {
1307        if (mIndeterminate) {
1308            return;
1309        }
1310
1311        if (progress < 0) {
1312            progress = 0;
1313        }
1314
1315        if (progress > mMax) {
1316            progress = mMax;
1317        }
1318
1319        if (progress != mProgress) {
1320            mProgress = progress;
1321            refreshProgress(R.id.progress, mProgress, fromUser);
1322        }
1323    }
1324
1325    /**
1326     * <p>
1327     * Set the current secondary progress to the specified value. Does not do
1328     * anything if the progress bar is in indeterminate mode.
1329     * </p>
1330     *
1331     * @param secondaryProgress the new secondary progress, between 0 and {@link #getMax()}
1332     * @see #setIndeterminate(boolean)
1333     * @see #isIndeterminate()
1334     * @see #getSecondaryProgress()
1335     * @see #incrementSecondaryProgressBy(int)
1336     */
1337    @android.view.RemotableViewMethod
1338    public synchronized void setSecondaryProgress(int secondaryProgress) {
1339        if (mIndeterminate) {
1340            return;
1341        }
1342
1343        if (secondaryProgress < 0) {
1344            secondaryProgress = 0;
1345        }
1346
1347        if (secondaryProgress > mMax) {
1348            secondaryProgress = mMax;
1349        }
1350
1351        if (secondaryProgress != mSecondaryProgress) {
1352            mSecondaryProgress = secondaryProgress;
1353            refreshProgress(R.id.secondaryProgress, mSecondaryProgress, false);
1354        }
1355    }
1356
1357    /**
1358     * <p>Get the progress bar's current level of progress. Return 0 when the
1359     * progress bar is in indeterminate mode.</p>
1360     *
1361     * @return the current progress, between 0 and {@link #getMax()}
1362     *
1363     * @see #setIndeterminate(boolean)
1364     * @see #isIndeterminate()
1365     * @see #setProgress(int)
1366     * @see #setMax(int)
1367     * @see #getMax()
1368     */
1369    @ViewDebug.ExportedProperty(category = "progress")
1370    public synchronized int getProgress() {
1371        return mIndeterminate ? 0 : mProgress;
1372    }
1373
1374    /**
1375     * <p>Get the progress bar's current level of secondary progress. Return 0 when the
1376     * progress bar is in indeterminate mode.</p>
1377     *
1378     * @return the current secondary progress, between 0 and {@link #getMax()}
1379     *
1380     * @see #setIndeterminate(boolean)
1381     * @see #isIndeterminate()
1382     * @see #setSecondaryProgress(int)
1383     * @see #setMax(int)
1384     * @see #getMax()
1385     */
1386    @ViewDebug.ExportedProperty(category = "progress")
1387    public synchronized int getSecondaryProgress() {
1388        return mIndeterminate ? 0 : mSecondaryProgress;
1389    }
1390
1391    /**
1392     * <p>Return the upper limit of this progress bar's range.</p>
1393     *
1394     * @return a positive integer
1395     *
1396     * @see #setMax(int)
1397     * @see #getProgress()
1398     * @see #getSecondaryProgress()
1399     */
1400    @ViewDebug.ExportedProperty(category = "progress")
1401    public synchronized int getMax() {
1402        return mMax;
1403    }
1404
1405    /**
1406     * <p>Set the range of the progress bar to 0...<tt>max</tt>.</p>
1407     *
1408     * @param max the upper range of this progress bar
1409     *
1410     * @see #getMax()
1411     * @see #setProgress(int)
1412     * @see #setSecondaryProgress(int)
1413     */
1414    @android.view.RemotableViewMethod
1415    public synchronized void setMax(int max) {
1416        if (max < 0) {
1417            max = 0;
1418        }
1419        if (max != mMax) {
1420            mMax = max;
1421            postInvalidate();
1422
1423            if (mProgress > max) {
1424                mProgress = max;
1425            }
1426            refreshProgress(R.id.progress, mProgress, false);
1427        }
1428    }
1429
1430    /**
1431     * <p>Increase the progress bar's progress by the specified amount.</p>
1432     *
1433     * @param diff the amount by which the progress must be increased
1434     *
1435     * @see #setProgress(int)
1436     */
1437    public synchronized final void incrementProgressBy(int diff) {
1438        setProgress(mProgress + diff);
1439    }
1440
1441    /**
1442     * <p>Increase the progress bar's secondary progress by the specified amount.</p>
1443     *
1444     * @param diff the amount by which the secondary progress must be increased
1445     *
1446     * @see #setSecondaryProgress(int)
1447     */
1448    public synchronized final void incrementSecondaryProgressBy(int diff) {
1449        setSecondaryProgress(mSecondaryProgress + diff);
1450    }
1451
1452    /**
1453     * <p>Start the indeterminate progress animation.</p>
1454     */
1455    void startAnimation() {
1456        if (getVisibility() != VISIBLE) {
1457            return;
1458        }
1459
1460        if (mIndeterminateDrawable instanceof Animatable) {
1461            mShouldStartAnimationDrawable = true;
1462            mHasAnimation = false;
1463        } else {
1464            mHasAnimation = true;
1465
1466            if (mInterpolator == null) {
1467                mInterpolator = new LinearInterpolator();
1468            }
1469
1470            if (mTransformation == null) {
1471                mTransformation = new Transformation();
1472            } else {
1473                mTransformation.clear();
1474            }
1475
1476            if (mAnimation == null) {
1477                mAnimation = new AlphaAnimation(0.0f, 1.0f);
1478            } else {
1479                mAnimation.reset();
1480            }
1481
1482            mAnimation.setRepeatMode(mBehavior);
1483            mAnimation.setRepeatCount(Animation.INFINITE);
1484            mAnimation.setDuration(mDuration);
1485            mAnimation.setInterpolator(mInterpolator);
1486            mAnimation.setStartTime(Animation.START_ON_FIRST_FRAME);
1487        }
1488        postInvalidate();
1489    }
1490
1491    /**
1492     * <p>Stop the indeterminate progress animation.</p>
1493     */
1494    void stopAnimation() {
1495        mHasAnimation = false;
1496        if (mIndeterminateDrawable instanceof Animatable) {
1497            ((Animatable) mIndeterminateDrawable).stop();
1498            mShouldStartAnimationDrawable = false;
1499        }
1500        postInvalidate();
1501    }
1502
1503    /**
1504     * Sets the acceleration curve for the indeterminate animation.
1505     * The interpolator is loaded as a resource from the specified context.
1506     *
1507     * @param context The application environment
1508     * @param resID The resource identifier of the interpolator to load
1509     */
1510    public void setInterpolator(Context context, int resID) {
1511        setInterpolator(AnimationUtils.loadInterpolator(context, resID));
1512    }
1513
1514    /**
1515     * Sets the acceleration curve for the indeterminate animation.
1516     * Defaults to a linear interpolation.
1517     *
1518     * @param interpolator The interpolator which defines the acceleration curve
1519     */
1520    public void setInterpolator(Interpolator interpolator) {
1521        mInterpolator = interpolator;
1522    }
1523
1524    /**
1525     * Gets the acceleration curve type for the indeterminate animation.
1526     *
1527     * @return the {@link Interpolator} associated to this animation
1528     */
1529    public Interpolator getInterpolator() {
1530        return mInterpolator;
1531    }
1532
1533    @Override
1534    @RemotableViewMethod
1535    public void setVisibility(int v) {
1536        if (getVisibility() != v) {
1537            super.setVisibility(v);
1538
1539            if (mIndeterminate) {
1540                // let's be nice with the UI thread
1541                if (v == GONE || v == INVISIBLE) {
1542                    stopAnimation();
1543                } else {
1544                    startAnimation();
1545                }
1546            }
1547        }
1548    }
1549
1550    @Override
1551    protected void onVisibilityChanged(View changedView, int visibility) {
1552        super.onVisibilityChanged(changedView, visibility);
1553
1554        if (mIndeterminate) {
1555            // let's be nice with the UI thread
1556            if (visibility == GONE || visibility == INVISIBLE) {
1557                stopAnimation();
1558            } else {
1559                startAnimation();
1560            }
1561        }
1562    }
1563
1564    @Override
1565    public void invalidateDrawable(Drawable dr) {
1566        if (!mInDrawing) {
1567            if (verifyDrawable(dr)) {
1568                final Rect dirty = dr.getBounds();
1569                final int scrollX = mScrollX + mPaddingLeft;
1570                final int scrollY = mScrollY + mPaddingTop;
1571
1572                invalidate(dirty.left + scrollX, dirty.top + scrollY,
1573                        dirty.right + scrollX, dirty.bottom + scrollY);
1574            } else {
1575                super.invalidateDrawable(dr);
1576            }
1577        }
1578    }
1579
1580    @Override
1581    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
1582        updateDrawableBounds(w, h);
1583    }
1584
1585    private void updateDrawableBounds(int w, int h) {
1586        // onDraw will translate the canvas so we draw starting at 0,0.
1587        // Subtract out padding for the purposes of the calculations below.
1588        w -= mPaddingRight + mPaddingLeft;
1589        h -= mPaddingTop + mPaddingBottom;
1590
1591        int right = w;
1592        int bottom = h;
1593        int top = 0;
1594        int left = 0;
1595
1596        if (mIndeterminateDrawable != null) {
1597            // Aspect ratio logic does not apply to AnimationDrawables
1598            if (mOnlyIndeterminate && !(mIndeterminateDrawable instanceof AnimationDrawable)) {
1599                // Maintain aspect ratio. Certain kinds of animated drawables
1600                // get very confused otherwise.
1601                final int intrinsicWidth = mIndeterminateDrawable.getIntrinsicWidth();
1602                final int intrinsicHeight = mIndeterminateDrawable.getIntrinsicHeight();
1603                final float intrinsicAspect = (float) intrinsicWidth / intrinsicHeight;
1604                final float boundAspect = (float) w / h;
1605                if (intrinsicAspect != boundAspect) {
1606                    if (boundAspect > intrinsicAspect) {
1607                        // New width is larger. Make it smaller to match height.
1608                        final int width = (int) (h * intrinsicAspect);
1609                        left = (w - width) / 2;
1610                        right = left + width;
1611                    } else {
1612                        // New height is larger. Make it smaller to match width.
1613                        final int height = (int) (w * (1 / intrinsicAspect));
1614                        top = (h - height) / 2;
1615                        bottom = top + height;
1616                    }
1617                }
1618            }
1619            if (isLayoutRtl() && mMirrorForRtl) {
1620                int tempLeft = left;
1621                left = w - right;
1622                right = w - tempLeft;
1623            }
1624            mIndeterminateDrawable.setBounds(left, top, right, bottom);
1625        }
1626
1627        if (mProgressDrawable != null) {
1628            mProgressDrawable.setBounds(0, 0, right, bottom);
1629        }
1630    }
1631
1632    @Override
1633    protected synchronized void onDraw(Canvas canvas) {
1634        super.onDraw(canvas);
1635
1636        drawTrack(canvas);
1637    }
1638
1639    /**
1640     * Draws the progress bar track.
1641     */
1642    void drawTrack(Canvas canvas) {
1643        final Drawable d = mCurrentDrawable;
1644        if (d != null) {
1645            // Translate canvas so a indeterminate circular progress bar with padding
1646            // rotates properly in its animation
1647            final int saveCount = canvas.save();
1648
1649            if(isLayoutRtl() && mMirrorForRtl) {
1650                canvas.translate(getWidth() - mPaddingRight, mPaddingTop);
1651                canvas.scale(-1.0f, 1.0f);
1652            } else {
1653                canvas.translate(mPaddingLeft, mPaddingTop);
1654            }
1655
1656            final long time = getDrawingTime();
1657            if (mHasAnimation) {
1658                mAnimation.getTransformation(time, mTransformation);
1659                final float scale = mTransformation.getAlpha();
1660                try {
1661                    mInDrawing = true;
1662                    d.setLevel((int) (scale * MAX_LEVEL));
1663                } finally {
1664                    mInDrawing = false;
1665                }
1666                postInvalidateOnAnimation();
1667            }
1668
1669            d.draw(canvas);
1670            canvas.restoreToCount(saveCount);
1671
1672            if (mShouldStartAnimationDrawable && d instanceof Animatable) {
1673                ((Animatable) d).start();
1674                mShouldStartAnimationDrawable = false;
1675            }
1676        }
1677    }
1678
1679    @Override
1680    protected synchronized void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
1681        Drawable d = mCurrentDrawable;
1682
1683        int dw = 0;
1684        int dh = 0;
1685        if (d != null) {
1686            dw = Math.max(mMinWidth, Math.min(mMaxWidth, d.getIntrinsicWidth()));
1687            dh = Math.max(mMinHeight, Math.min(mMaxHeight, d.getIntrinsicHeight()));
1688        }
1689        updateDrawableState();
1690        dw += mPaddingLeft + mPaddingRight;
1691        dh += mPaddingTop + mPaddingBottom;
1692
1693        setMeasuredDimension(resolveSizeAndState(dw, widthMeasureSpec, 0),
1694                resolveSizeAndState(dh, heightMeasureSpec, 0));
1695    }
1696
1697    @Override
1698    protected void drawableStateChanged() {
1699        super.drawableStateChanged();
1700        updateDrawableState();
1701    }
1702
1703    private void updateDrawableState() {
1704        int[] state = getDrawableState();
1705
1706        if (mProgressDrawable != null && mProgressDrawable.isStateful()) {
1707            mProgressDrawable.setState(state);
1708        }
1709
1710        if (mIndeterminateDrawable != null && mIndeterminateDrawable.isStateful()) {
1711            mIndeterminateDrawable.setState(state);
1712        }
1713    }
1714
1715    @Override
1716    public void drawableHotspotChanged(float x, float y) {
1717        super.drawableHotspotChanged(x, y);
1718
1719        if (mProgressDrawable != null) {
1720            mProgressDrawable.setHotspot(x, y);
1721        }
1722
1723        if (mIndeterminateDrawable != null) {
1724            mIndeterminateDrawable.setHotspot(x, y);
1725        }
1726    }
1727
1728    static class SavedState extends BaseSavedState {
1729        int progress;
1730        int secondaryProgress;
1731
1732        /**
1733         * Constructor called from {@link ProgressBar#onSaveInstanceState()}
1734         */
1735        SavedState(Parcelable superState) {
1736            super(superState);
1737        }
1738
1739        /**
1740         * Constructor called from {@link #CREATOR}
1741         */
1742        private SavedState(Parcel in) {
1743            super(in);
1744            progress = in.readInt();
1745            secondaryProgress = in.readInt();
1746        }
1747
1748        @Override
1749        public void writeToParcel(Parcel out, int flags) {
1750            super.writeToParcel(out, flags);
1751            out.writeInt(progress);
1752            out.writeInt(secondaryProgress);
1753        }
1754
1755        public static final Parcelable.Creator<SavedState> CREATOR
1756                = new Parcelable.Creator<SavedState>() {
1757            public SavedState createFromParcel(Parcel in) {
1758                return new SavedState(in);
1759            }
1760
1761            public SavedState[] newArray(int size) {
1762                return new SavedState[size];
1763            }
1764        };
1765    }
1766
1767    @Override
1768    public Parcelable onSaveInstanceState() {
1769        // Force our ancestor class to save its state
1770        Parcelable superState = super.onSaveInstanceState();
1771        SavedState ss = new SavedState(superState);
1772
1773        ss.progress = mProgress;
1774        ss.secondaryProgress = mSecondaryProgress;
1775
1776        return ss;
1777    }
1778
1779    @Override
1780    public void onRestoreInstanceState(Parcelable state) {
1781        SavedState ss = (SavedState) state;
1782        super.onRestoreInstanceState(ss.getSuperState());
1783
1784        setProgress(ss.progress);
1785        setSecondaryProgress(ss.secondaryProgress);
1786    }
1787
1788    @Override
1789    protected void onAttachedToWindow() {
1790        super.onAttachedToWindow();
1791        if (mIndeterminate) {
1792            startAnimation();
1793        }
1794        if (mRefreshData != null) {
1795            synchronized (this) {
1796                final int count = mRefreshData.size();
1797                for (int i = 0; i < count; i++) {
1798                    final RefreshData rd = mRefreshData.get(i);
1799                    doRefreshProgress(rd.id, rd.progress, rd.fromUser, true);
1800                    rd.recycle();
1801                }
1802                mRefreshData.clear();
1803            }
1804        }
1805        mAttached = true;
1806    }
1807
1808    @Override
1809    protected void onDetachedFromWindow() {
1810        if (mIndeterminate) {
1811            stopAnimation();
1812        }
1813        if (mRefreshProgressRunnable != null) {
1814            removeCallbacks(mRefreshProgressRunnable);
1815        }
1816        if (mRefreshProgressRunnable != null && mRefreshIsPosted) {
1817            removeCallbacks(mRefreshProgressRunnable);
1818        }
1819        if (mAccessibilityEventSender != null) {
1820            removeCallbacks(mAccessibilityEventSender);
1821        }
1822        // This should come after stopAnimation(), otherwise an invalidate message remains in the
1823        // queue, which can prevent the entire view hierarchy from being GC'ed during a rotation
1824        super.onDetachedFromWindow();
1825        mAttached = false;
1826    }
1827
1828    @Override
1829    public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
1830        super.onInitializeAccessibilityEvent(event);
1831        event.setClassName(ProgressBar.class.getName());
1832        event.setItemCount(mMax);
1833        event.setCurrentItemIndex(mProgress);
1834    }
1835
1836    @Override
1837    public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
1838        super.onInitializeAccessibilityNodeInfo(info);
1839        info.setClassName(ProgressBar.class.getName());
1840    }
1841
1842    /**
1843     * Schedule a command for sending an accessibility event.
1844     * </br>
1845     * Note: A command is used to ensure that accessibility events
1846     *       are sent at most one in a given time frame to save
1847     *       system resources while the progress changes quickly.
1848     */
1849    private void scheduleAccessibilityEventSender() {
1850        if (mAccessibilityEventSender == null) {
1851            mAccessibilityEventSender = new AccessibilityEventSender();
1852        } else {
1853            removeCallbacks(mAccessibilityEventSender);
1854        }
1855        postDelayed(mAccessibilityEventSender, TIMEOUT_SEND_ACCESSIBILITY_EVENT);
1856    }
1857
1858    /**
1859     * Command for sending an accessibility event.
1860     */
1861    private class AccessibilityEventSender implements Runnable {
1862        public void run() {
1863            sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_SELECTED);
1864        }
1865    }
1866
1867    private static class ProgressTintInfo {
1868        ColorStateList mIndeterminateTintList;
1869        PorterDuff.Mode mIndeterminateTintMode;
1870        boolean mHasIndeterminateTint;
1871        boolean mHasIndeterminateTintMode;
1872
1873        ColorStateList mProgressTintList;
1874        PorterDuff.Mode mProgressTintMode;
1875        boolean mHasProgressTint;
1876        boolean mHasProgressTintMode;
1877
1878        ColorStateList mProgressBackgroundTintList;
1879        PorterDuff.Mode mProgressBackgroundTintMode;
1880        boolean mHasProgressBackgroundTint;
1881        boolean mHasProgressBackgroundTintMode;
1882
1883        ColorStateList mSecondaryProgressTintList;
1884        PorterDuff.Mode mSecondaryProgressTintMode;
1885        boolean mHasSecondaryProgressTint;
1886        boolean mHasSecondaryProgressTintMode;
1887    }
1888}
1889