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