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