ProgressBar.java revision d5133792391443521dc15f7da7de5d280e6703dd
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                // The drawable (or one of its children) may not have been
678                // stateful before applying the tint, so let's try again.
679                if (mIndeterminateDrawable.isStateful()) {
680                    mIndeterminateDrawable.setState(getDrawableState());
681                }
682            }
683        }
684    }
685
686    /**
687     * Define the tileable drawable used to draw the progress bar in
688     * indeterminate mode.
689     * <p>
690     * If the drawable is a BitmapDrawable or contains BitmapDrawables, a
691     * tiled copy will be generated for display as a progress bar.
692     *
693     * @param d the new drawable
694     * @see #getIndeterminateDrawable()
695     * @see #setIndeterminate(boolean)
696     */
697    public void setIndeterminateDrawableTiled(Drawable d) {
698        if (d != null) {
699            d = tileifyIndeterminate(d);
700        }
701
702        setIndeterminateDrawable(d);
703    }
704
705    /**
706     * <p>Get the drawable used to draw the progress bar in
707     * progress mode.</p>
708     *
709     * @return a {@link android.graphics.drawable.Drawable} instance
710     *
711     * @see #setProgressDrawable(android.graphics.drawable.Drawable)
712     * @see #setIndeterminate(boolean)
713     */
714    public Drawable getProgressDrawable() {
715        return mProgressDrawable;
716    }
717
718    /**
719     * Define the drawable used to draw the progress bar in progress mode.
720     *
721     * @param d the new drawable
722     * @see #getProgressDrawable()
723     * @see #setIndeterminate(boolean)
724     */
725    public void setProgressDrawable(Drawable d) {
726        if (mProgressDrawable != d) {
727            if (mProgressDrawable != null) {
728                mProgressDrawable.setCallback(null);
729                unscheduleDrawable(mProgressDrawable);
730            }
731
732            mProgressDrawable = d;
733
734            if (d != null) {
735                d.setCallback(this);
736                d.setLayoutDirection(getLayoutDirection());
737                if (d.isStateful()) {
738                    d.setState(getDrawableState());
739                }
740
741                // Make sure the ProgressBar is always tall enough
742                int drawableHeight = d.getMinimumHeight();
743                if (mMaxHeight < drawableHeight) {
744                    mMaxHeight = drawableHeight;
745                    requestLayout();
746                }
747
748                applyProgressTints();
749            }
750
751            if (!mIndeterminate) {
752                mCurrentDrawable = d;
753                postInvalidate();
754            }
755
756            updateDrawableBounds(getWidth(), getHeight());
757            updateDrawableState();
758
759            doRefreshProgress(R.id.progress, mProgress, false, false);
760            doRefreshProgress(R.id.secondaryProgress, mSecondaryProgress, false, false);
761        }
762    }
763
764    /**
765     * Applies the progress tints in order of increasing specificity.
766     */
767    private void applyProgressTints() {
768        if (mProgressDrawable != null && mProgressTintInfo != null) {
769            applyPrimaryProgressTint();
770            applyProgressBackgroundTint();
771            applySecondaryProgressTint();
772        }
773    }
774
775    /**
776     * Should only be called if we've already verified that mProgressDrawable
777     * and mProgressTintInfo are non-null.
778     */
779    private void applyPrimaryProgressTint() {
780        if (mProgressTintInfo.mHasProgressTint
781                || mProgressTintInfo.mHasProgressTintMode) {
782            final Drawable target = getTintTarget(R.id.progress, true);
783            if (target != null) {
784                if (mProgressTintInfo.mHasProgressTint) {
785                    target.setTintList(mProgressTintInfo.mProgressTintList);
786                }
787                if (mProgressTintInfo.mHasProgressTintMode) {
788                    target.setTintMode(mProgressTintInfo.mProgressTintMode);
789                }
790
791                // The drawable (or one of its children) may not have been
792                // stateful before applying the tint, so let's try again.
793                if (target.isStateful()) {
794                    target.setState(getDrawableState());
795                }
796            }
797        }
798    }
799
800    /**
801     * Should only be called if we've already verified that mProgressDrawable
802     * and mProgressTintInfo are non-null.
803     */
804    private void applyProgressBackgroundTint() {
805        if (mProgressTintInfo.mHasProgressBackgroundTint
806                || mProgressTintInfo.mHasProgressBackgroundTintMode) {
807            final Drawable target = getTintTarget(R.id.background, false);
808            if (target != null) {
809                if (mProgressTintInfo.mHasProgressBackgroundTint) {
810                    target.setTintList(mProgressTintInfo.mProgressBackgroundTintList);
811                }
812                if (mProgressTintInfo.mHasProgressBackgroundTintMode) {
813                    target.setTintMode(mProgressTintInfo.mProgressBackgroundTintMode);
814                }
815
816                // The drawable (or one of its children) may not have been
817                // stateful before applying the tint, so let's try again.
818                if (target.isStateful()) {
819                    target.setState(getDrawableState());
820                }
821            }
822        }
823    }
824
825    /**
826     * Should only be called if we've already verified that mProgressDrawable
827     * and mProgressTintInfo are non-null.
828     */
829    private void applySecondaryProgressTint() {
830        if (mProgressTintInfo.mHasSecondaryProgressTint
831                || mProgressTintInfo.mHasSecondaryProgressTintMode) {
832            final Drawable target = getTintTarget(R.id.secondaryProgress, false);
833            if (target != null) {
834                if (mProgressTintInfo.mHasSecondaryProgressTint) {
835                    target.setTintList(mProgressTintInfo.mSecondaryProgressTintList);
836                }
837                if (mProgressTintInfo.mHasSecondaryProgressTintMode) {
838                    target.setTintMode(mProgressTintInfo.mSecondaryProgressTintMode);
839                }
840
841                // The drawable (or one of its children) may not have been
842                // stateful before applying the tint, so let's try again.
843                if (target.isStateful()) {
844                    target.setState(getDrawableState());
845                }
846            }
847        }
848    }
849
850    /**
851     * Applies a tint to the progress indicator, if one exists, or to the
852     * entire progress drawable otherwise. Does not modify the current tint
853     * mode, which is {@link PorterDuff.Mode#SRC_IN} by default.
854     * <p>
855     * The progress indicator should be specified as a layer with
856     * id {@link android.R.id#progress} in a {@link LayerDrawable}
857     * used as the progress drawable.
858     * <p>
859     * Subsequent calls to {@link #setProgressDrawable(Drawable)} will
860     * automatically mutate the drawable and apply the specified tint and
861     * tint mode using
862     * {@link Drawable#setTintList(ColorStateList)}.
863     *
864     * @param tint the tint to apply, may be {@code null} to clear tint
865     *
866     * @attr ref android.R.styleable#ProgressBar_progressTint
867     * @see #getProgressTintList()
868     * @see Drawable#setTintList(ColorStateList)
869     */
870    @RemotableViewMethod
871    public void setProgressTintList(@Nullable ColorStateList tint) {
872        if (mProgressTintInfo == null) {
873            mProgressTintInfo = new ProgressTintInfo();
874        }
875        mProgressTintInfo.mProgressTintList = tint;
876        mProgressTintInfo.mHasProgressTint = true;
877
878        if (mProgressDrawable != null) {
879            applyPrimaryProgressTint();
880        }
881    }
882
883    /**
884     * Returns the tint applied to the progress drawable, if specified.
885     *
886     * @return the tint applied to the progress drawable
887     * @attr ref android.R.styleable#ProgressBar_progressTint
888     * @see #setProgressTintList(ColorStateList)
889     */
890    @Nullable
891    public ColorStateList getProgressTintList() {
892        return mProgressTintInfo != null ? mProgressTintInfo.mProgressTintList : null;
893    }
894
895    /**
896     * Specifies the blending mode used to apply the tint specified by
897     * {@link #setProgressTintList(ColorStateList)}} to the progress
898     * indicator. The default mode is {@link PorterDuff.Mode#SRC_IN}.
899     *
900     * @param tintMode the blending mode used to apply the tint, may be
901     *                 {@code null} to clear tint
902     * @attr ref android.R.styleable#ProgressBar_progressTintMode
903     * @see #getProgressTintMode()
904     * @see Drawable#setTintMode(PorterDuff.Mode)
905     */
906    public void setProgressTintMode(@Nullable PorterDuff.Mode tintMode) {
907        if (mProgressTintInfo == null) {
908            mProgressTintInfo = new ProgressTintInfo();
909        }
910        mProgressTintInfo.mProgressTintMode = tintMode;
911        mProgressTintInfo.mHasProgressTintMode = true;
912
913        if (mProgressDrawable != null) {
914            applyPrimaryProgressTint();
915        }
916    }
917
918    /**
919     * Returns the blending mode used to apply the tint to the progress
920     * drawable, if specified.
921     *
922     * @return the blending mode used to apply the tint to the progress
923     *         drawable
924     * @attr ref android.R.styleable#ProgressBar_progressTintMode
925     * @see #setProgressTintMode(PorterDuff.Mode)
926     */
927    @Nullable
928    public PorterDuff.Mode getProgressTintMode() {
929        return mProgressTintInfo != null ? mProgressTintInfo.mProgressTintMode : null;
930    }
931
932    /**
933     * Applies a tint to the progress background, if one exists. Does not
934     * modify the current tint mode, which is
935     * {@link PorterDuff.Mode#SRC_ATOP} by default.
936     * <p>
937     * The progress background must be specified as a layer with
938     * id {@link android.R.id#background} in a {@link LayerDrawable}
939     * used as the progress drawable.
940     * <p>
941     * Subsequent calls to {@link #setProgressDrawable(Drawable)} where the
942     * drawable contains a progress background will automatically mutate the
943     * drawable and apply the specified tint and tint mode using
944     * {@link Drawable#setTintList(ColorStateList)}.
945     *
946     * @param tint the tint to apply, may be {@code null} to clear tint
947     *
948     * @attr ref android.R.styleable#ProgressBar_progressBackgroundTint
949     * @see #getProgressBackgroundTintList()
950     * @see Drawable#setTintList(ColorStateList)
951     */
952    @RemotableViewMethod
953    public void setProgressBackgroundTintList(@Nullable ColorStateList tint) {
954        if (mProgressTintInfo == null) {
955            mProgressTintInfo = new ProgressTintInfo();
956        }
957        mProgressTintInfo.mProgressBackgroundTintList = tint;
958        mProgressTintInfo.mHasProgressBackgroundTint = true;
959
960        if (mProgressDrawable != null) {
961            applyProgressBackgroundTint();
962        }
963    }
964
965    /**
966     * Returns the tint applied to the progress background, if specified.
967     *
968     * @return the tint applied to the progress background
969     * @attr ref android.R.styleable#ProgressBar_progressBackgroundTint
970     * @see #setProgressBackgroundTintList(ColorStateList)
971     */
972    @Nullable
973    public ColorStateList getProgressBackgroundTintList() {
974        return mProgressTintInfo != null ? mProgressTintInfo.mProgressBackgroundTintList : null;
975    }
976
977    /**
978     * Specifies the blending mode used to apply the tint specified by
979     * {@link #setProgressBackgroundTintList(ColorStateList)}} to the progress
980     * background. The default mode is {@link PorterDuff.Mode#SRC_IN}.
981     *
982     * @param tintMode the blending mode used to apply the tint, may be
983     *                 {@code null} to clear tint
984     * @attr ref android.R.styleable#ProgressBar_progressBackgroundTintMode
985     * @see #setProgressBackgroundTintList(ColorStateList)
986     * @see Drawable#setTintMode(PorterDuff.Mode)
987     */
988    public void setProgressBackgroundTintMode(@Nullable PorterDuff.Mode tintMode) {
989        if (mProgressTintInfo == null) {
990            mProgressTintInfo = new ProgressTintInfo();
991        }
992        mProgressTintInfo.mProgressBackgroundTintMode = tintMode;
993        mProgressTintInfo.mHasProgressBackgroundTintMode = true;
994
995        if (mProgressDrawable != null) {
996            applyProgressBackgroundTint();
997        }
998    }
999
1000    /**
1001     * @return the blending mode used to apply the tint to the progress
1002     *         background
1003     * @attr ref android.R.styleable#ProgressBar_progressBackgroundTintMode
1004     * @see #setProgressBackgroundTintMode(PorterDuff.Mode)
1005     */
1006    @Nullable
1007    public PorterDuff.Mode getProgressBackgroundTintMode() {
1008        return mProgressTintInfo != null ? mProgressTintInfo.mProgressBackgroundTintMode : null;
1009    }
1010
1011    /**
1012     * Applies a tint to the secondary progress indicator, if one exists.
1013     * Does not modify the current tint mode, which is
1014     * {@link PorterDuff.Mode#SRC_ATOP} by default.
1015     * <p>
1016     * The secondary progress indicator must be specified as a layer with
1017     * id {@link android.R.id#secondaryProgress} in a {@link LayerDrawable}
1018     * used as the progress drawable.
1019     * <p>
1020     * Subsequent calls to {@link #setProgressDrawable(Drawable)} where the
1021     * drawable contains a secondary progress indicator will automatically
1022     * mutate the drawable and apply the specified tint and tint mode using
1023     * {@link Drawable#setTintList(ColorStateList)}.
1024     *
1025     * @param tint the tint to apply, may be {@code null} to clear tint
1026     *
1027     * @attr ref android.R.styleable#ProgressBar_secondaryProgressTint
1028     * @see #getSecondaryProgressTintList()
1029     * @see Drawable#setTintList(ColorStateList)
1030     */
1031    public void setSecondaryProgressTintList(@Nullable ColorStateList tint) {
1032        if (mProgressTintInfo == null) {
1033            mProgressTintInfo = new ProgressTintInfo();
1034        }
1035        mProgressTintInfo.mSecondaryProgressTintList = tint;
1036        mProgressTintInfo.mHasSecondaryProgressTint = true;
1037
1038        if (mProgressDrawable != null) {
1039            applySecondaryProgressTint();
1040        }
1041    }
1042
1043    /**
1044     * Returns the tint applied to the secondary progress drawable, if
1045     * specified.
1046     *
1047     * @return the tint applied to the secondary progress drawable
1048     * @attr ref android.R.styleable#ProgressBar_secondaryProgressTint
1049     * @see #setSecondaryProgressTintList(ColorStateList)
1050     */
1051    @Nullable
1052    public ColorStateList getSecondaryProgressTintList() {
1053        return mProgressTintInfo != null ? mProgressTintInfo.mSecondaryProgressTintList : null;
1054    }
1055
1056    /**
1057     * Specifies the blending mode used to apply the tint specified by
1058     * {@link #setSecondaryProgressTintList(ColorStateList)}} to the secondary
1059     * progress indicator. The default mode is
1060     * {@link PorterDuff.Mode#SRC_ATOP}.
1061     *
1062     * @param tintMode the blending mode used to apply the tint, may be
1063     *                 {@code null} to clear tint
1064     * @attr ref android.R.styleable#ProgressBar_secondaryProgressTintMode
1065     * @see #setSecondaryProgressTintList(ColorStateList)
1066     * @see Drawable#setTintMode(PorterDuff.Mode)
1067     */
1068    public void setSecondaryProgressTintMode(@Nullable PorterDuff.Mode tintMode) {
1069        if (mProgressTintInfo == null) {
1070            mProgressTintInfo = new ProgressTintInfo();
1071        }
1072        mProgressTintInfo.mSecondaryProgressTintMode = tintMode;
1073        mProgressTintInfo.mHasSecondaryProgressTintMode = true;
1074
1075        if (mProgressDrawable != null) {
1076            applySecondaryProgressTint();
1077        }
1078    }
1079
1080    /**
1081     * Returns the blending mode used to apply the tint to the secondary
1082     * progress drawable, if specified.
1083     *
1084     * @return the blending mode used to apply the tint to the secondary
1085     *         progress drawable
1086     * @attr ref android.R.styleable#ProgressBar_secondaryProgressTintMode
1087     * @see #setSecondaryProgressTintMode(PorterDuff.Mode)
1088     */
1089    @Nullable
1090    public PorterDuff.Mode getSecondaryProgressTintMode() {
1091        return mProgressTintInfo != null ? mProgressTintInfo.mSecondaryProgressTintMode : null;
1092    }
1093
1094    /**
1095     * Returns the drawable to which a tint or tint mode should be applied.
1096     *
1097     * @param layerId id of the layer to modify
1098     * @param shouldFallback whether the base drawable should be returned
1099     *                       if the id does not exist
1100     * @return the drawable to modify
1101     */
1102    @Nullable
1103    private Drawable getTintTarget(int layerId, boolean shouldFallback) {
1104        Drawable layer = null;
1105
1106        final Drawable d = mProgressDrawable;
1107        if (d != null) {
1108            mProgressDrawable = d.mutate();
1109
1110            if (d instanceof LayerDrawable) {
1111                layer = ((LayerDrawable) d).findDrawableByLayerId(layerId);
1112            }
1113
1114            if (shouldFallback && layer == null) {
1115                layer = d;
1116            }
1117        }
1118
1119        return layer;
1120    }
1121
1122    /**
1123     * Define the tileable drawable used to draw the progress bar in
1124     * progress mode.
1125     * <p>
1126     * If the drawable is a BitmapDrawable or contains BitmapDrawables, a
1127     * tiled copy will be generated for display as a progress bar.
1128     *
1129     * @param d the new drawable
1130     * @see #getProgressDrawable()
1131     * @see #setIndeterminate(boolean)
1132     */
1133    public void setProgressDrawableTiled(Drawable d) {
1134        if (d != null) {
1135            d = tileify(d, false);
1136        }
1137
1138        setProgressDrawable(d);
1139    }
1140
1141    /**
1142     * @return The drawable currently used to draw the progress bar
1143     */
1144    Drawable getCurrentDrawable() {
1145        return mCurrentDrawable;
1146    }
1147
1148    @Override
1149    protected boolean verifyDrawable(Drawable who) {
1150        return who == mProgressDrawable || who == mIndeterminateDrawable
1151                || super.verifyDrawable(who);
1152    }
1153
1154    @Override
1155    public void jumpDrawablesToCurrentState() {
1156        super.jumpDrawablesToCurrentState();
1157        if (mProgressDrawable != null) mProgressDrawable.jumpToCurrentState();
1158        if (mIndeterminateDrawable != null) mIndeterminateDrawable.jumpToCurrentState();
1159    }
1160
1161    /**
1162     * @hide
1163     */
1164    @Override
1165    public void onResolveDrawables(int layoutDirection) {
1166        final Drawable d = mCurrentDrawable;
1167        if (d != null) {
1168            d.setLayoutDirection(layoutDirection);
1169        }
1170        if (mIndeterminateDrawable != null) {
1171            mIndeterminateDrawable.setLayoutDirection(layoutDirection);
1172        }
1173        if (mProgressDrawable != null) {
1174            mProgressDrawable.setLayoutDirection(layoutDirection);
1175        }
1176    }
1177
1178    @Override
1179    public void postInvalidate() {
1180        if (!mNoInvalidate) {
1181            super.postInvalidate();
1182        }
1183    }
1184
1185    private class RefreshProgressRunnable implements Runnable {
1186        public void run() {
1187            synchronized (ProgressBar.this) {
1188                final int count = mRefreshData.size();
1189                for (int i = 0; i < count; i++) {
1190                    final RefreshData rd = mRefreshData.get(i);
1191                    doRefreshProgress(rd.id, rd.progress, rd.fromUser, true, rd.animate);
1192                    rd.recycle();
1193                }
1194                mRefreshData.clear();
1195                mRefreshIsPosted = false;
1196            }
1197        }
1198    }
1199
1200    private static class RefreshData {
1201        private static final int POOL_MAX = 24;
1202        private static final SynchronizedPool<RefreshData> sPool =
1203                new SynchronizedPool<RefreshData>(POOL_MAX);
1204
1205        public int id;
1206        public float progress;
1207        public boolean fromUser;
1208        public boolean animate;
1209
1210        public static RefreshData obtain(int id, float progress, boolean fromUser,
1211                boolean animate) {
1212            RefreshData rd = sPool.acquire();
1213            if (rd == null) {
1214                rd = new RefreshData();
1215            }
1216            rd.id = id;
1217            rd.progress = progress;
1218            rd.fromUser = fromUser;
1219            rd.animate = animate;
1220            return rd;
1221        }
1222
1223        public void recycle() {
1224            sPool.release(this);
1225        }
1226    }
1227
1228    private void setDrawableTint(int id, ColorStateList tint, Mode tintMode, boolean fallback) {
1229        Drawable layer = null;
1230
1231        // We expect a layer drawable, so try to find the target ID.
1232        final Drawable d = mCurrentDrawable;
1233        if (d instanceof LayerDrawable) {
1234            layer = ((LayerDrawable) d).findDrawableByLayerId(id);
1235        }
1236
1237        if (fallback && layer == null) {
1238            layer = d;
1239        }
1240
1241        layer.mutate();
1242        layer.setTintList(tint);
1243        layer.setTintMode(tintMode);
1244    }
1245
1246    private float getScale(float progress) {
1247        return mMax > 0 ? progress / (float) mMax : 0;
1248    }
1249
1250    private synchronized void doRefreshProgress(int id, float progress, boolean fromUser,
1251            boolean callBackToApp) {
1252        doRefreshProgress(id, progress, fromUser, callBackToApp, false);
1253    }
1254
1255    private synchronized void doRefreshProgress(int id, float progress, boolean fromUser,
1256            boolean callBackToApp, boolean animate) {
1257        float scale = getScale(progress);
1258
1259        final Drawable d = mCurrentDrawable;
1260        if (d != null) {
1261            Drawable progressDrawable = null;
1262
1263            if (d instanceof LayerDrawable) {
1264                progressDrawable = ((LayerDrawable) d).findDrawableByLayerId(id);
1265                if (progressDrawable != null && canResolveLayoutDirection()) {
1266                    progressDrawable.setLayoutDirection(getLayoutDirection());
1267                }
1268            }
1269
1270            final int level = (int) (scale * MAX_LEVEL);
1271            (progressDrawable != null ? progressDrawable : d).setLevel(level);
1272        } else {
1273            invalidate();
1274        }
1275
1276        if (id == R.id.progress) {
1277            if (animate) {
1278                onAnimatePosition(scale, fromUser);
1279            } else if (callBackToApp) {
1280                onProgressRefresh(scale, fromUser);
1281            }
1282        }
1283    }
1284
1285    /**
1286     * Called when a ProgressBar is animating its position.
1287     *
1288     * @param scale Current position/progress between 0 and 1.
1289     * @param fromUser True if the progress change was initiated by the user.
1290     */
1291    void onAnimatePosition(float scale, boolean fromUser) {
1292    }
1293
1294    /**
1295     * Sets the progress value without going through the entire refresh process.
1296     *
1297     * @see #setProgress(int, boolean)
1298     * @param progress The new progress, between 0 and {@link #getMax()}
1299     */
1300    void setProgressValueOnly(int progress) {
1301        mProgress = progress;
1302        onProgressRefresh(getScale(progress), true);
1303    }
1304
1305    void setAnimationPosition(float position) {
1306        mAnimationPosition = position;
1307        refreshProgress(R.id.progress, position, true, true);
1308    }
1309
1310    float getAnimationPosition() {
1311        return mAnimationPosition;
1312    }
1313
1314    void onProgressRefresh(float scale, boolean fromUser) {
1315        if (AccessibilityManager.getInstance(mContext).isEnabled()) {
1316            scheduleAccessibilityEventSender();
1317        }
1318    }
1319
1320    private synchronized void refreshProgress(int id, float progress, boolean fromUser) {
1321        refreshProgress(id, progress, fromUser, false);
1322    }
1323
1324    private synchronized void refreshProgress(int id, float progress, boolean fromUser,
1325            boolean animate) {
1326        if (mUiThreadId == Thread.currentThread().getId()) {
1327            doRefreshProgress(id, progress, fromUser, true, animate);
1328        } else {
1329            if (mRefreshProgressRunnable == null) {
1330                mRefreshProgressRunnable = new RefreshProgressRunnable();
1331            }
1332
1333            final RefreshData rd = RefreshData.obtain(id, progress, fromUser, animate);
1334            mRefreshData.add(rd);
1335            if (mAttached && !mRefreshIsPosted) {
1336                post(mRefreshProgressRunnable);
1337                mRefreshIsPosted = true;
1338            }
1339        }
1340    }
1341
1342    /**
1343     * <p>Set the current progress to the specified value. Does not do anything
1344     * if the progress bar is in indeterminate mode.</p>
1345     *
1346     * @param progress the new progress, between 0 and {@link #getMax()}
1347     *
1348     * @see #setIndeterminate(boolean)
1349     * @see #isIndeterminate()
1350     * @see #getProgress()
1351     * @see #incrementProgressBy(int)
1352     */
1353    @android.view.RemotableViewMethod
1354    public synchronized void setProgress(int progress) {
1355        setProgress(progress, false);
1356    }
1357
1358    @android.view.RemotableViewMethod
1359    synchronized void setProgress(int progress, boolean fromUser) {
1360        if (mIndeterminate) {
1361            return;
1362        }
1363
1364        if (progress < 0) {
1365            progress = 0;
1366        }
1367
1368        if (progress > mMax) {
1369            progress = mMax;
1370        }
1371
1372        if (progress != mProgress) {
1373            mProgress = progress;
1374            refreshProgress(R.id.progress, mProgress, fromUser);
1375        }
1376    }
1377
1378    /**
1379     * <p>
1380     * Set the current secondary progress to the specified value. Does not do
1381     * anything if the progress bar is in indeterminate mode.
1382     * </p>
1383     *
1384     * @param secondaryProgress the new secondary progress, between 0 and {@link #getMax()}
1385     * @see #setIndeterminate(boolean)
1386     * @see #isIndeterminate()
1387     * @see #getSecondaryProgress()
1388     * @see #incrementSecondaryProgressBy(int)
1389     */
1390    @android.view.RemotableViewMethod
1391    public synchronized void setSecondaryProgress(int secondaryProgress) {
1392        if (mIndeterminate) {
1393            return;
1394        }
1395
1396        if (secondaryProgress < 0) {
1397            secondaryProgress = 0;
1398        }
1399
1400        if (secondaryProgress > mMax) {
1401            secondaryProgress = mMax;
1402        }
1403
1404        if (secondaryProgress != mSecondaryProgress) {
1405            mSecondaryProgress = secondaryProgress;
1406            refreshProgress(R.id.secondaryProgress, mSecondaryProgress, false);
1407        }
1408    }
1409
1410    /**
1411     * <p>Get the progress bar's current level of progress. Return 0 when the
1412     * progress bar is in indeterminate mode.</p>
1413     *
1414     * @return the current progress, between 0 and {@link #getMax()}
1415     *
1416     * @see #setIndeterminate(boolean)
1417     * @see #isIndeterminate()
1418     * @see #setProgress(int)
1419     * @see #setMax(int)
1420     * @see #getMax()
1421     */
1422    @ViewDebug.ExportedProperty(category = "progress")
1423    public synchronized int getProgress() {
1424        return mIndeterminate ? 0 : mProgress;
1425    }
1426
1427    /**
1428     * <p>Get the progress bar's current level of secondary progress. Return 0 when the
1429     * progress bar is in indeterminate mode.</p>
1430     *
1431     * @return the current secondary progress, between 0 and {@link #getMax()}
1432     *
1433     * @see #setIndeterminate(boolean)
1434     * @see #isIndeterminate()
1435     * @see #setSecondaryProgress(int)
1436     * @see #setMax(int)
1437     * @see #getMax()
1438     */
1439    @ViewDebug.ExportedProperty(category = "progress")
1440    public synchronized int getSecondaryProgress() {
1441        return mIndeterminate ? 0 : mSecondaryProgress;
1442    }
1443
1444    /**
1445     * <p>Return the upper limit of this progress bar's range.</p>
1446     *
1447     * @return a positive integer
1448     *
1449     * @see #setMax(int)
1450     * @see #getProgress()
1451     * @see #getSecondaryProgress()
1452     */
1453    @ViewDebug.ExportedProperty(category = "progress")
1454    public synchronized int getMax() {
1455        return mMax;
1456    }
1457
1458    /**
1459     * <p>Set the range of the progress bar to 0...<tt>max</tt>.</p>
1460     *
1461     * @param max the upper range of this progress bar
1462     *
1463     * @see #getMax()
1464     * @see #setProgress(int)
1465     * @see #setSecondaryProgress(int)
1466     */
1467    @android.view.RemotableViewMethod
1468    public synchronized void setMax(int max) {
1469        if (max < 0) {
1470            max = 0;
1471        }
1472        if (max != mMax) {
1473            mMax = max;
1474            postInvalidate();
1475
1476            if (mProgress > max) {
1477                mProgress = max;
1478            }
1479            refreshProgress(R.id.progress, mProgress, false);
1480        }
1481    }
1482
1483    /**
1484     * <p>Increase the progress bar's progress by the specified amount.</p>
1485     *
1486     * @param diff the amount by which the progress must be increased
1487     *
1488     * @see #setProgress(int)
1489     */
1490    public synchronized final void incrementProgressBy(int diff) {
1491        setProgress(mProgress + diff);
1492    }
1493
1494    /**
1495     * <p>Increase the progress bar's secondary progress by the specified amount.</p>
1496     *
1497     * @param diff the amount by which the secondary progress must be increased
1498     *
1499     * @see #setSecondaryProgress(int)
1500     */
1501    public synchronized final void incrementSecondaryProgressBy(int diff) {
1502        setSecondaryProgress(mSecondaryProgress + diff);
1503    }
1504
1505    /**
1506     * <p>Start the indeterminate progress animation.</p>
1507     */
1508    void startAnimation() {
1509        if (getVisibility() != VISIBLE) {
1510            return;
1511        }
1512
1513        if (mIndeterminateDrawable instanceof Animatable) {
1514            mShouldStartAnimationDrawable = true;
1515            mHasAnimation = false;
1516        } else {
1517            mHasAnimation = true;
1518
1519            if (mInterpolator == null) {
1520                mInterpolator = new LinearInterpolator();
1521            }
1522
1523            if (mTransformation == null) {
1524                mTransformation = new Transformation();
1525            } else {
1526                mTransformation.clear();
1527            }
1528
1529            if (mAnimation == null) {
1530                mAnimation = new AlphaAnimation(0.0f, 1.0f);
1531            } else {
1532                mAnimation.reset();
1533            }
1534
1535            mAnimation.setRepeatMode(mBehavior);
1536            mAnimation.setRepeatCount(Animation.INFINITE);
1537            mAnimation.setDuration(mDuration);
1538            mAnimation.setInterpolator(mInterpolator);
1539            mAnimation.setStartTime(Animation.START_ON_FIRST_FRAME);
1540        }
1541        postInvalidate();
1542    }
1543
1544    /**
1545     * <p>Stop the indeterminate progress animation.</p>
1546     */
1547    void stopAnimation() {
1548        mHasAnimation = false;
1549        if (mIndeterminateDrawable instanceof Animatable) {
1550            ((Animatable) mIndeterminateDrawable).stop();
1551            mShouldStartAnimationDrawable = false;
1552        }
1553        postInvalidate();
1554    }
1555
1556    /**
1557     * Sets the acceleration curve for the indeterminate animation.
1558     * The interpolator is loaded as a resource from the specified context.
1559     *
1560     * @param context The application environment
1561     * @param resID The resource identifier of the interpolator to load
1562     */
1563    public void setInterpolator(Context context, int resID) {
1564        setInterpolator(AnimationUtils.loadInterpolator(context, resID));
1565    }
1566
1567    /**
1568     * Sets the acceleration curve for the indeterminate animation.
1569     * Defaults to a linear interpolation.
1570     *
1571     * @param interpolator The interpolator which defines the acceleration curve
1572     */
1573    public void setInterpolator(Interpolator interpolator) {
1574        mInterpolator = interpolator;
1575    }
1576
1577    /**
1578     * Gets the acceleration curve type for the indeterminate animation.
1579     *
1580     * @return the {@link Interpolator} associated to this animation
1581     */
1582    public Interpolator getInterpolator() {
1583        return mInterpolator;
1584    }
1585
1586    @Override
1587    @RemotableViewMethod
1588    public void setVisibility(int v) {
1589        if (getVisibility() != v) {
1590            super.setVisibility(v);
1591
1592            if (mIndeterminate) {
1593                // let's be nice with the UI thread
1594                if (v == GONE || v == INVISIBLE) {
1595                    stopAnimation();
1596                } else {
1597                    startAnimation();
1598                }
1599            }
1600        }
1601    }
1602
1603    @Override
1604    protected void onVisibilityChanged(View changedView, int visibility) {
1605        super.onVisibilityChanged(changedView, visibility);
1606
1607        if (mIndeterminate) {
1608            // let's be nice with the UI thread
1609            if (visibility == GONE || visibility == INVISIBLE) {
1610                stopAnimation();
1611            } else {
1612                startAnimation();
1613            }
1614        }
1615    }
1616
1617    @Override
1618    public void invalidateDrawable(Drawable dr) {
1619        if (!mInDrawing) {
1620            if (verifyDrawable(dr)) {
1621                final Rect dirty = dr.getBounds();
1622                final int scrollX = mScrollX + mPaddingLeft;
1623                final int scrollY = mScrollY + mPaddingTop;
1624
1625                invalidate(dirty.left + scrollX, dirty.top + scrollY,
1626                        dirty.right + scrollX, dirty.bottom + scrollY);
1627            } else {
1628                super.invalidateDrawable(dr);
1629            }
1630        }
1631    }
1632
1633    @Override
1634    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
1635        updateDrawableBounds(w, h);
1636    }
1637
1638    private void updateDrawableBounds(int w, int h) {
1639        // onDraw will translate the canvas so we draw starting at 0,0.
1640        // Subtract out padding for the purposes of the calculations below.
1641        w -= mPaddingRight + mPaddingLeft;
1642        h -= mPaddingTop + mPaddingBottom;
1643
1644        int right = w;
1645        int bottom = h;
1646        int top = 0;
1647        int left = 0;
1648
1649        if (mIndeterminateDrawable != null) {
1650            // Aspect ratio logic does not apply to AnimationDrawables
1651            if (mOnlyIndeterminate && !(mIndeterminateDrawable instanceof AnimationDrawable)) {
1652                // Maintain aspect ratio. Certain kinds of animated drawables
1653                // get very confused otherwise.
1654                final int intrinsicWidth = mIndeterminateDrawable.getIntrinsicWidth();
1655                final int intrinsicHeight = mIndeterminateDrawable.getIntrinsicHeight();
1656                final float intrinsicAspect = (float) intrinsicWidth / intrinsicHeight;
1657                final float boundAspect = (float) w / h;
1658                if (intrinsicAspect != boundAspect) {
1659                    if (boundAspect > intrinsicAspect) {
1660                        // New width is larger. Make it smaller to match height.
1661                        final int width = (int) (h * intrinsicAspect);
1662                        left = (w - width) / 2;
1663                        right = left + width;
1664                    } else {
1665                        // New height is larger. Make it smaller to match width.
1666                        final int height = (int) (w * (1 / intrinsicAspect));
1667                        top = (h - height) / 2;
1668                        bottom = top + height;
1669                    }
1670                }
1671            }
1672            if (isLayoutRtl() && mMirrorForRtl) {
1673                int tempLeft = left;
1674                left = w - right;
1675                right = w - tempLeft;
1676            }
1677            mIndeterminateDrawable.setBounds(left, top, right, bottom);
1678        }
1679
1680        if (mProgressDrawable != null) {
1681            mProgressDrawable.setBounds(0, 0, right, bottom);
1682        }
1683    }
1684
1685    @Override
1686    protected synchronized void onDraw(Canvas canvas) {
1687        super.onDraw(canvas);
1688
1689        drawTrack(canvas);
1690    }
1691
1692    /**
1693     * Draws the progress bar track.
1694     */
1695    void drawTrack(Canvas canvas) {
1696        final Drawable d = mCurrentDrawable;
1697        if (d != null) {
1698            // Translate canvas so a indeterminate circular progress bar with padding
1699            // rotates properly in its animation
1700            final int saveCount = canvas.save();
1701
1702            if(isLayoutRtl() && mMirrorForRtl) {
1703                canvas.translate(getWidth() - mPaddingRight, mPaddingTop);
1704                canvas.scale(-1.0f, 1.0f);
1705            } else {
1706                canvas.translate(mPaddingLeft, mPaddingTop);
1707            }
1708
1709            final long time = getDrawingTime();
1710            if (mHasAnimation) {
1711                mAnimation.getTransformation(time, mTransformation);
1712                final float scale = mTransformation.getAlpha();
1713                try {
1714                    mInDrawing = true;
1715                    d.setLevel((int) (scale * MAX_LEVEL));
1716                } finally {
1717                    mInDrawing = false;
1718                }
1719                postInvalidateOnAnimation();
1720            }
1721
1722            d.draw(canvas);
1723            canvas.restoreToCount(saveCount);
1724
1725            if (mShouldStartAnimationDrawable && d instanceof Animatable) {
1726                ((Animatable) d).start();
1727                mShouldStartAnimationDrawable = false;
1728            }
1729        }
1730    }
1731
1732    @Override
1733    protected synchronized void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
1734        Drawable d = mCurrentDrawable;
1735
1736        int dw = 0;
1737        int dh = 0;
1738        if (d != null) {
1739            dw = Math.max(mMinWidth, Math.min(mMaxWidth, d.getIntrinsicWidth()));
1740            dh = Math.max(mMinHeight, Math.min(mMaxHeight, d.getIntrinsicHeight()));
1741        }
1742        updateDrawableState();
1743        dw += mPaddingLeft + mPaddingRight;
1744        dh += mPaddingTop + mPaddingBottom;
1745
1746        setMeasuredDimension(resolveSizeAndState(dw, widthMeasureSpec, 0),
1747                resolveSizeAndState(dh, heightMeasureSpec, 0));
1748    }
1749
1750    @Override
1751    protected void drawableStateChanged() {
1752        super.drawableStateChanged();
1753        updateDrawableState();
1754    }
1755
1756    private void updateDrawableState() {
1757        int[] state = getDrawableState();
1758
1759        if (mProgressDrawable != null && mProgressDrawable.isStateful()) {
1760            mProgressDrawable.setState(state);
1761        }
1762
1763        if (mIndeterminateDrawable != null && mIndeterminateDrawable.isStateful()) {
1764            mIndeterminateDrawable.setState(state);
1765        }
1766    }
1767
1768    @Override
1769    public void drawableHotspotChanged(float x, float y) {
1770        super.drawableHotspotChanged(x, y);
1771
1772        if (mProgressDrawable != null) {
1773            mProgressDrawable.setHotspot(x, y);
1774        }
1775
1776        if (mIndeterminateDrawable != null) {
1777            mIndeterminateDrawable.setHotspot(x, y);
1778        }
1779    }
1780
1781    static class SavedState extends BaseSavedState {
1782        int progress;
1783        int secondaryProgress;
1784
1785        /**
1786         * Constructor called from {@link ProgressBar#onSaveInstanceState()}
1787         */
1788        SavedState(Parcelable superState) {
1789            super(superState);
1790        }
1791
1792        /**
1793         * Constructor called from {@link #CREATOR}
1794         */
1795        private SavedState(Parcel in) {
1796            super(in);
1797            progress = in.readInt();
1798            secondaryProgress = in.readInt();
1799        }
1800
1801        @Override
1802        public void writeToParcel(Parcel out, int flags) {
1803            super.writeToParcel(out, flags);
1804            out.writeInt(progress);
1805            out.writeInt(secondaryProgress);
1806        }
1807
1808        public static final Parcelable.Creator<SavedState> CREATOR
1809                = new Parcelable.Creator<SavedState>() {
1810            public SavedState createFromParcel(Parcel in) {
1811                return new SavedState(in);
1812            }
1813
1814            public SavedState[] newArray(int size) {
1815                return new SavedState[size];
1816            }
1817        };
1818    }
1819
1820    @Override
1821    public Parcelable onSaveInstanceState() {
1822        // Force our ancestor class to save its state
1823        Parcelable superState = super.onSaveInstanceState();
1824        SavedState ss = new SavedState(superState);
1825
1826        ss.progress = mProgress;
1827        ss.secondaryProgress = mSecondaryProgress;
1828
1829        return ss;
1830    }
1831
1832    @Override
1833    public void onRestoreInstanceState(Parcelable state) {
1834        SavedState ss = (SavedState) state;
1835        super.onRestoreInstanceState(ss.getSuperState());
1836
1837        setProgress(ss.progress);
1838        setSecondaryProgress(ss.secondaryProgress);
1839    }
1840
1841    @Override
1842    protected void onAttachedToWindow() {
1843        super.onAttachedToWindow();
1844        if (mIndeterminate) {
1845            startAnimation();
1846        }
1847        if (mRefreshData != null) {
1848            synchronized (this) {
1849                final int count = mRefreshData.size();
1850                for (int i = 0; i < count; i++) {
1851                    final RefreshData rd = mRefreshData.get(i);
1852                    doRefreshProgress(rd.id, rd.progress, rd.fromUser, rd.animate);
1853                    rd.recycle();
1854                }
1855                mRefreshData.clear();
1856            }
1857        }
1858        mAttached = true;
1859    }
1860
1861    @Override
1862    protected void onDetachedFromWindow() {
1863        if (mIndeterminate) {
1864            stopAnimation();
1865        }
1866        if (mRefreshProgressRunnable != null) {
1867            removeCallbacks(mRefreshProgressRunnable);
1868        }
1869        if (mRefreshProgressRunnable != null && mRefreshIsPosted) {
1870            removeCallbacks(mRefreshProgressRunnable);
1871        }
1872        if (mAccessibilityEventSender != null) {
1873            removeCallbacks(mAccessibilityEventSender);
1874        }
1875        // This should come after stopAnimation(), otherwise an invalidate message remains in the
1876        // queue, which can prevent the entire view hierarchy from being GC'ed during a rotation
1877        super.onDetachedFromWindow();
1878        mAttached = false;
1879    }
1880
1881    @Override
1882    public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
1883        super.onInitializeAccessibilityEvent(event);
1884        event.setClassName(ProgressBar.class.getName());
1885        event.setItemCount(mMax);
1886        event.setCurrentItemIndex(mProgress);
1887    }
1888
1889    @Override
1890    public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
1891        super.onInitializeAccessibilityNodeInfo(info);
1892        info.setClassName(ProgressBar.class.getName());
1893    }
1894
1895    /**
1896     * Schedule a command for sending an accessibility event.
1897     * </br>
1898     * Note: A command is used to ensure that accessibility events
1899     *       are sent at most one in a given time frame to save
1900     *       system resources while the progress changes quickly.
1901     */
1902    private void scheduleAccessibilityEventSender() {
1903        if (mAccessibilityEventSender == null) {
1904            mAccessibilityEventSender = new AccessibilityEventSender();
1905        } else {
1906            removeCallbacks(mAccessibilityEventSender);
1907        }
1908        postDelayed(mAccessibilityEventSender, TIMEOUT_SEND_ACCESSIBILITY_EVENT);
1909    }
1910
1911    /**
1912     * Command for sending an accessibility event.
1913     */
1914    private class AccessibilityEventSender implements Runnable {
1915        public void run() {
1916            sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_SELECTED);
1917        }
1918    }
1919
1920    private static class ProgressTintInfo {
1921        ColorStateList mIndeterminateTintList;
1922        PorterDuff.Mode mIndeterminateTintMode;
1923        boolean mHasIndeterminateTint;
1924        boolean mHasIndeterminateTintMode;
1925
1926        ColorStateList mProgressTintList;
1927        PorterDuff.Mode mProgressTintMode;
1928        boolean mHasProgressTint;
1929        boolean mHasProgressTintMode;
1930
1931        ColorStateList mProgressBackgroundTintList;
1932        PorterDuff.Mode mProgressBackgroundTintMode;
1933        boolean mHasProgressBackgroundTint;
1934        boolean mHasProgressBackgroundTintMode;
1935
1936        ColorStateList mSecondaryProgressTintList;
1937        PorterDuff.Mode mSecondaryProgressTintMode;
1938        boolean mHasSecondaryProgressTint;
1939        boolean mHasSecondaryProgressTintMode;
1940    }
1941}
1942