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