ProgressBar.java revision abae2a1b891772d36d8f781adfcc8969e551691f
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 com.android.internal.R;
20
21import android.content.Context;
22import android.content.res.TypedArray;
23import android.graphics.Bitmap;
24import android.graphics.BitmapShader;
25import android.graphics.Canvas;
26import android.graphics.Rect;
27import android.graphics.Shader;
28import android.graphics.drawable.Animatable;
29import android.graphics.drawable.AnimationDrawable;
30import android.graphics.drawable.BitmapDrawable;
31import android.graphics.drawable.ClipDrawable;
32import android.graphics.drawable.Drawable;
33import android.graphics.drawable.LayerDrawable;
34import android.graphics.drawable.ShapeDrawable;
35import android.graphics.drawable.StateListDrawable;
36import android.graphics.drawable.shapes.RoundRectShape;
37import android.graphics.drawable.shapes.Shape;
38import android.os.Parcel;
39import android.os.Parcelable;
40import android.util.AttributeSet;
41import android.util.Pools.SynchronizedPool;
42import android.view.Gravity;
43import android.view.RemotableViewMethod;
44import android.view.View;
45import android.view.ViewDebug;
46import android.view.accessibility.AccessibilityEvent;
47import android.view.accessibility.AccessibilityManager;
48import android.view.accessibility.AccessibilityNodeInfo;
49import android.view.animation.AlphaAnimation;
50import android.view.animation.Animation;
51import android.view.animation.AnimationUtils;
52import android.view.animation.Interpolator;
53import android.view.animation.LinearInterpolator;
54import android.view.animation.Transformation;
55import android.widget.RemoteViews.RemoteView;
56
57import java.util.ArrayList;
58
59
60/**
61 * <p>
62 * Visual indicator of progress in some operation.  Displays a bar to the user
63 * representing how far the operation has progressed; the application can
64 * change the amount of progress (modifying the length of the bar) as it moves
65 * forward.  There is also a secondary progress displayable on a progress bar
66 * which is useful for displaying intermediate progress, such as the buffer
67 * level during a streaming playback progress bar.
68 * </p>
69 *
70 * <p>
71 * A progress bar can also be made indeterminate. In indeterminate mode, the
72 * progress bar shows a cyclic animation without an indication of progress. This mode is used by
73 * applications when the length of the task is unknown. The indeterminate progress bar can be either
74 * a spinning wheel or a horizontal bar.
75 * </p>
76 *
77 * <p>The following code example shows how a progress bar can be used from
78 * a worker thread to update the user interface to notify the user of progress:
79 * </p>
80 *
81 * <pre>
82 * public class MyActivity extends Activity {
83 *     private static final int PROGRESS = 0x1;
84 *
85 *     private ProgressBar mProgress;
86 *     private int mProgressStatus = 0;
87 *
88 *     private Handler mHandler = new Handler();
89 *
90 *     protected void onCreate(Bundle icicle) {
91 *         super.onCreate(icicle);
92 *
93 *         setContentView(R.layout.progressbar_activity);
94 *
95 *         mProgress = (ProgressBar) findViewById(R.id.progress_bar);
96 *
97 *         // Start lengthy operation in a background thread
98 *         new Thread(new Runnable() {
99 *             public void run() {
100 *                 while (mProgressStatus &lt; 100) {
101 *                     mProgressStatus = doWork();
102 *
103 *                     // Update the progress bar
104 *                     mHandler.post(new Runnable() {
105 *                         public void run() {
106 *                             mProgress.setProgress(mProgressStatus);
107 *                         }
108 *                     });
109 *                 }
110 *             }
111 *         }).start();
112 *     }
113 * }</pre>
114 *
115 * <p>To add a progress bar to a layout file, you can use the {@code &lt;ProgressBar&gt;} element.
116 * By default, the progress bar is a spinning wheel (an indeterminate indicator). To change to a
117 * horizontal progress bar, apply the {@link android.R.style#Widget_ProgressBar_Horizontal
118 * Widget.ProgressBar.Horizontal} style, like so:</p>
119 *
120 * <pre>
121 * &lt;ProgressBar
122 *     style="@android:style/Widget.ProgressBar.Horizontal"
123 *     ... /&gt;</pre>
124 *
125 * <p>If you will use the progress bar to show real progress, you must use the horizontal bar. You
126 * can then increment the  progress with {@link #incrementProgressBy incrementProgressBy()} or
127 * {@link #setProgress setProgress()}. By default, the progress bar is full when it reaches 100. If
128 * necessary, you can adjust the maximum value (the value for a full bar) using the {@link
129 * android.R.styleable#ProgressBar_max android:max} attribute. Other attributes available are listed
130 * below.</p>
131 *
132 * <p>Another common style to apply to the progress bar is {@link
133 * android.R.style#Widget_ProgressBar_Small Widget.ProgressBar.Small}, which shows a smaller
134 * version of the spinning wheel&mdash;useful when waiting for content to load.
135 * For example, you can insert this kind of progress bar into your default layout for
136 * a view that will be populated by some content fetched from the Internet&mdash;the spinning wheel
137 * appears immediately and when your application receives the content, it replaces the progress bar
138 * with the loaded content. For example:</p>
139 *
140 * <pre>
141 * &lt;LinearLayout
142 *     android:orientation="horizontal"
143 *     ... &gt;
144 *     &lt;ProgressBar
145 *         android:layout_width="wrap_content"
146 *         android:layout_height="wrap_content"
147 *         style="@android:style/Widget.ProgressBar.Small"
148 *         android:layout_marginRight="5dp" /&gt;
149 *     &lt;TextView
150 *         android:layout_width="wrap_content"
151 *         android:layout_height="wrap_content"
152 *         android:text="@string/loading" /&gt;
153 * &lt;/LinearLayout&gt;</pre>
154 *
155 * <p>Other progress bar styles provided by the system include:</p>
156 * <ul>
157 * <li>{@link android.R.style#Widget_ProgressBar_Horizontal Widget.ProgressBar.Horizontal}</li>
158 * <li>{@link android.R.style#Widget_ProgressBar_Small Widget.ProgressBar.Small}</li>
159 * <li>{@link android.R.style#Widget_ProgressBar_Large Widget.ProgressBar.Large}</li>
160 * <li>{@link android.R.style#Widget_ProgressBar_Inverse Widget.ProgressBar.Inverse}</li>
161 * <li>{@link android.R.style#Widget_ProgressBar_Small_Inverse
162 * Widget.ProgressBar.Small.Inverse}</li>
163 * <li>{@link android.R.style#Widget_ProgressBar_Large_Inverse
164 * Widget.ProgressBar.Large.Inverse}</li>
165 * </ul>
166 * <p>The "inverse" styles provide an inverse color scheme for the spinner, which may be necessary
167 * if your application uses a light colored theme (a white background).</p>
168 *
169 * <p><strong>XML attributes</b></strong>
170 * <p>
171 * See {@link android.R.styleable#ProgressBar ProgressBar Attributes},
172 * {@link android.R.styleable#View View Attributes}
173 * </p>
174 *
175 * @attr ref android.R.styleable#ProgressBar_animationResolution
176 * @attr ref android.R.styleable#ProgressBar_indeterminate
177 * @attr ref android.R.styleable#ProgressBar_indeterminateBehavior
178 * @attr ref android.R.styleable#ProgressBar_indeterminateDrawable
179 * @attr ref android.R.styleable#ProgressBar_indeterminateDuration
180 * @attr ref android.R.styleable#ProgressBar_indeterminateOnly
181 * @attr ref android.R.styleable#ProgressBar_interpolator
182 * @attr ref android.R.styleable#ProgressBar_max
183 * @attr ref android.R.styleable#ProgressBar_maxHeight
184 * @attr ref android.R.styleable#ProgressBar_maxWidth
185 * @attr ref android.R.styleable#ProgressBar_minHeight
186 * @attr ref android.R.styleable#ProgressBar_minWidth
187 * @attr ref android.R.styleable#ProgressBar_progress
188 * @attr ref android.R.styleable#ProgressBar_progressDrawable
189 * @attr ref android.R.styleable#ProgressBar_secondaryProgress
190 */
191@RemoteView
192public class ProgressBar extends View {
193    private static final int MAX_LEVEL = 10000;
194    private static final int TIMEOUT_SEND_ACCESSIBILITY_EVENT = 200;
195
196    int mMinWidth;
197    int mMaxWidth;
198    int mMinHeight;
199    int mMaxHeight;
200
201    private int mProgress;
202    private int mSecondaryProgress;
203    private int mMax;
204
205    private int mBehavior;
206    private int mDuration;
207    private boolean mIndeterminate;
208    private boolean mOnlyIndeterminate;
209    private Transformation mTransformation;
210    private AlphaAnimation mAnimation;
211    private boolean mHasAnimation;
212    private Drawable mIndeterminateDrawable;
213    private Drawable mProgressDrawable;
214    private Drawable mCurrentDrawable;
215    Bitmap mSampleTile;
216    private boolean mNoInvalidate;
217    private Interpolator mInterpolator;
218    private RefreshProgressRunnable mRefreshProgressRunnable;
219    private long mUiThreadId;
220    private boolean mShouldStartAnimationDrawable;
221
222    private boolean mInDrawing;
223    private boolean mAttached;
224    private boolean mRefreshIsPosted;
225
226    private final ArrayList<RefreshData> mRefreshData = new ArrayList<RefreshData>();
227
228    private AccessibilityEventSender mAccessibilityEventSender;
229
230    /**
231     * Create a new progress bar with range 0...100 and initial progress of 0.
232     * @param context the application environment
233     */
234    public ProgressBar(Context context) {
235        this(context, null);
236    }
237
238    public ProgressBar(Context context, AttributeSet attrs) {
239        this(context, attrs, com.android.internal.R.attr.progressBarStyle);
240    }
241
242    public ProgressBar(Context context, AttributeSet attrs, int defStyle) {
243        this(context, attrs, defStyle, 0);
244    }
245
246    /**
247     * @hide
248     */
249    public ProgressBar(Context context, AttributeSet attrs, int defStyle, int styleRes) {
250        super(context, attrs, defStyle);
251        mUiThreadId = Thread.currentThread().getId();
252        initProgressBar();
253
254        TypedArray a =
255            context.obtainStyledAttributes(attrs, R.styleable.ProgressBar, defStyle, styleRes);
256
257        mNoInvalidate = true;
258
259        Drawable drawable = a.getDrawable(R.styleable.ProgressBar_progressDrawable);
260        if (drawable != null) {
261            drawable = tileify(drawable, false);
262            // Calling this method can set mMaxHeight, make sure the corresponding
263            // XML attribute for mMaxHeight is read after calling this method
264            setProgressDrawable(drawable);
265        }
266
267
268        mDuration = a.getInt(R.styleable.ProgressBar_indeterminateDuration, mDuration);
269
270        mMinWidth = a.getDimensionPixelSize(R.styleable.ProgressBar_minWidth, mMinWidth);
271        mMaxWidth = a.getDimensionPixelSize(R.styleable.ProgressBar_maxWidth, mMaxWidth);
272        mMinHeight = a.getDimensionPixelSize(R.styleable.ProgressBar_minHeight, mMinHeight);
273        mMaxHeight = a.getDimensionPixelSize(R.styleable.ProgressBar_maxHeight, mMaxHeight);
274
275        mBehavior = a.getInt(R.styleable.ProgressBar_indeterminateBehavior, mBehavior);
276
277        final int resID = a.getResourceId(
278                com.android.internal.R.styleable.ProgressBar_interpolator,
279                android.R.anim.linear_interpolator); // default to linear interpolator
280        if (resID > 0) {
281            setInterpolator(context, resID);
282        }
283
284        setMax(a.getInt(R.styleable.ProgressBar_max, mMax));
285
286        setProgress(a.getInt(R.styleable.ProgressBar_progress, mProgress));
287
288        setSecondaryProgress(
289                a.getInt(R.styleable.ProgressBar_secondaryProgress, mSecondaryProgress));
290
291        drawable = a.getDrawable(R.styleable.ProgressBar_indeterminateDrawable);
292        if (drawable != null) {
293            drawable = tileifyIndeterminate(drawable);
294            setIndeterminateDrawable(drawable);
295        }
296
297        mOnlyIndeterminate = a.getBoolean(
298                R.styleable.ProgressBar_indeterminateOnly, mOnlyIndeterminate);
299
300        mNoInvalidate = false;
301
302        setIndeterminate(mOnlyIndeterminate || a.getBoolean(
303                R.styleable.ProgressBar_indeterminate, mIndeterminate));
304
305        a.recycle();
306    }
307
308    /**
309     * Converts a drawable to a tiled version of itself. It will recursively
310     * traverse layer and state list drawables.
311     */
312    private Drawable tileify(Drawable drawable, boolean clip) {
313
314        if (drawable instanceof LayerDrawable) {
315            LayerDrawable background = (LayerDrawable) drawable;
316            final int N = background.getNumberOfLayers();
317            Drawable[] outDrawables = new Drawable[N];
318
319            for (int i = 0; i < N; i++) {
320                int id = background.getId(i);
321                outDrawables[i] = tileify(background.getDrawable(i),
322                        (id == R.id.progress || id == R.id.secondaryProgress));
323            }
324
325            LayerDrawable newBg = new LayerDrawable(outDrawables);
326
327            for (int i = 0; i < N; i++) {
328                newBg.setId(i, background.getId(i));
329            }
330
331            return newBg;
332
333        } else if (drawable instanceof StateListDrawable) {
334            StateListDrawable in = (StateListDrawable) drawable;
335            StateListDrawable out = new StateListDrawable();
336            int numStates = in.getStateCount();
337            for (int i = 0; i < numStates; i++) {
338                out.addState(in.getStateSet(i), tileify(in.getStateDrawable(i), clip));
339            }
340            return out;
341
342        } else if (drawable instanceof BitmapDrawable) {
343            final Bitmap tileBitmap = ((BitmapDrawable) drawable).getBitmap();
344            if (mSampleTile == null) {
345                mSampleTile = tileBitmap;
346            }
347
348            final ShapeDrawable shapeDrawable = new ShapeDrawable(getDrawableShape());
349
350            final BitmapShader bitmapShader = new BitmapShader(tileBitmap,
351                    Shader.TileMode.REPEAT, Shader.TileMode.CLAMP);
352            shapeDrawable.getPaint().setShader(bitmapShader);
353
354            return (clip) ? new ClipDrawable(shapeDrawable, Gravity.LEFT,
355                    ClipDrawable.HORIZONTAL) : shapeDrawable;
356        }
357
358        return drawable;
359    }
360
361    Shape getDrawableShape() {
362        final float[] roundedCorners = new float[] { 5, 5, 5, 5, 5, 5, 5, 5 };
363        return new RoundRectShape(roundedCorners, null, null);
364    }
365
366    /**
367     * Convert a AnimationDrawable for use as a barberpole animation.
368     * Each frame of the animation is wrapped in a ClipDrawable and
369     * given a tiling BitmapShader.
370     */
371    private Drawable tileifyIndeterminate(Drawable drawable) {
372        if (drawable instanceof AnimationDrawable) {
373            AnimationDrawable background = (AnimationDrawable) drawable;
374            final int N = background.getNumberOfFrames();
375            AnimationDrawable newBg = new AnimationDrawable();
376            newBg.setOneShot(background.isOneShot());
377
378            for (int i = 0; i < N; i++) {
379                Drawable frame = tileify(background.getFrame(i), true);
380                frame.setLevel(10000);
381                newBg.addFrame(frame, background.getDuration(i));
382            }
383            newBg.setLevel(10000);
384            drawable = newBg;
385        }
386        return drawable;
387    }
388
389    /**
390     * <p>
391     * Initialize the progress bar's default values:
392     * </p>
393     * <ul>
394     * <li>progress = 0</li>
395     * <li>max = 100</li>
396     * <li>animation duration = 4000 ms</li>
397     * <li>indeterminate = false</li>
398     * <li>behavior = repeat</li>
399     * </ul>
400     */
401    private void initProgressBar() {
402        mMax = 100;
403        mProgress = 0;
404        mSecondaryProgress = 0;
405        mIndeterminate = false;
406        mOnlyIndeterminate = false;
407        mDuration = 4000;
408        mBehavior = AlphaAnimation.RESTART;
409        mMinWidth = 24;
410        mMaxWidth = 48;
411        mMinHeight = 24;
412        mMaxHeight = 48;
413    }
414
415    /**
416     * <p>Indicate whether this progress bar is in indeterminate mode.</p>
417     *
418     * @return true if the progress bar is in indeterminate mode
419     */
420    @ViewDebug.ExportedProperty(category = "progress")
421    public synchronized boolean isIndeterminate() {
422        return mIndeterminate;
423    }
424
425    /**
426     * <p>Change the indeterminate mode for this progress bar. In indeterminate
427     * mode, the progress is ignored and the progress bar shows an infinite
428     * animation instead.</p>
429     *
430     * If this progress bar's style only supports indeterminate mode (such as the circular
431     * progress bars), then this will be ignored.
432     *
433     * @param indeterminate true to enable the indeterminate mode
434     */
435    @android.view.RemotableViewMethod
436    public synchronized void setIndeterminate(boolean indeterminate) {
437        if ((!mOnlyIndeterminate || !mIndeterminate) && indeterminate != mIndeterminate) {
438            mIndeterminate = indeterminate;
439
440            if (indeterminate) {
441                // swap between indeterminate and regular backgrounds
442                mCurrentDrawable = mIndeterminateDrawable;
443                startAnimation();
444            } else {
445                mCurrentDrawable = mProgressDrawable;
446                stopAnimation();
447            }
448        }
449    }
450
451    /**
452     * <p>Get the drawable used to draw the progress bar in
453     * indeterminate mode.</p>
454     *
455     * @return a {@link android.graphics.drawable.Drawable} instance
456     *
457     * @see #setIndeterminateDrawable(android.graphics.drawable.Drawable)
458     * @see #setIndeterminate(boolean)
459     */
460    public Drawable getIndeterminateDrawable() {
461        return mIndeterminateDrawable;
462    }
463
464    /**
465     * <p>Define the drawable used to draw the progress bar in
466     * indeterminate mode.</p>
467     *
468     * @param d the new drawable
469     *
470     * @see #getIndeterminateDrawable()
471     * @see #setIndeterminate(boolean)
472     */
473    public void setIndeterminateDrawable(Drawable d) {
474        if (d != null) {
475            d.setCallback(this);
476        }
477        mIndeterminateDrawable = d;
478        if (mIndeterminateDrawable != null && canResolveLayoutDirection()) {
479            mIndeterminateDrawable.setLayoutDirection(getLayoutDirection());
480        }
481        if (mIndeterminate) {
482            mCurrentDrawable = d;
483            postInvalidate();
484        }
485    }
486
487    /**
488     * <p>Get the drawable used to draw the progress bar in
489     * progress mode.</p>
490     *
491     * @return a {@link android.graphics.drawable.Drawable} instance
492     *
493     * @see #setProgressDrawable(android.graphics.drawable.Drawable)
494     * @see #setIndeterminate(boolean)
495     */
496    public Drawable getProgressDrawable() {
497        return mProgressDrawable;
498    }
499
500    /**
501     * <p>Define the drawable used to draw the progress bar in
502     * progress mode.</p>
503     *
504     * @param d the new drawable
505     *
506     * @see #getProgressDrawable()
507     * @see #setIndeterminate(boolean)
508     */
509    public void setProgressDrawable(Drawable d) {
510        boolean needUpdate;
511        if (mProgressDrawable != null && d != mProgressDrawable) {
512            mProgressDrawable.setCallback(null);
513            needUpdate = true;
514        } else {
515            needUpdate = false;
516        }
517
518        if (d != null) {
519            d.setCallback(this);
520            if (canResolveLayoutDirection()) {
521                d.setLayoutDirection(getLayoutDirection());
522            }
523
524            // Make sure the ProgressBar is always tall enough
525            int drawableHeight = d.getMinimumHeight();
526            if (mMaxHeight < drawableHeight) {
527                mMaxHeight = drawableHeight;
528                requestLayout();
529            }
530        }
531        mProgressDrawable = d;
532        if (!mIndeterminate) {
533            mCurrentDrawable = d;
534            postInvalidate();
535        }
536
537        if (needUpdate) {
538            updateDrawableBounds(getWidth(), getHeight());
539            updateDrawableState();
540            doRefreshProgress(R.id.progress, mProgress, false, false);
541            doRefreshProgress(R.id.secondaryProgress, mSecondaryProgress, false, false);
542        }
543    }
544
545    /**
546     * @return The drawable currently used to draw the progress bar
547     */
548    Drawable getCurrentDrawable() {
549        return mCurrentDrawable;
550    }
551
552    @Override
553    protected boolean verifyDrawable(Drawable who) {
554        return who == mProgressDrawable || who == mIndeterminateDrawable
555                || super.verifyDrawable(who);
556    }
557
558    @Override
559    public void jumpDrawablesToCurrentState() {
560        super.jumpDrawablesToCurrentState();
561        if (mProgressDrawable != null) mProgressDrawable.jumpToCurrentState();
562        if (mIndeterminateDrawable != null) mIndeterminateDrawable.jumpToCurrentState();
563    }
564
565    /**
566     * @hide
567     */
568    @Override
569    public void onResolveDrawables(int layoutDirection) {
570        final Drawable d = mCurrentDrawable;
571        if (d != null) {
572            d.setLayoutDirection(layoutDirection);
573        }
574        if (mIndeterminateDrawable != null) {
575            mIndeterminateDrawable.setLayoutDirection(layoutDirection);
576        }
577        if (mProgressDrawable != null) {
578            mProgressDrawable.setLayoutDirection(layoutDirection);
579        }
580    }
581
582    @Override
583    public void postInvalidate() {
584        if (!mNoInvalidate) {
585            super.postInvalidate();
586        }
587    }
588
589    private class RefreshProgressRunnable implements Runnable {
590        public void run() {
591            synchronized (ProgressBar.this) {
592                final int count = mRefreshData.size();
593                for (int i = 0; i < count; i++) {
594                    final RefreshData rd = mRefreshData.get(i);
595                    doRefreshProgress(rd.id, rd.progress, rd.fromUser, true);
596                    rd.recycle();
597                }
598                mRefreshData.clear();
599                mRefreshIsPosted = false;
600            }
601        }
602    }
603
604    private static class RefreshData {
605        private static final int POOL_MAX = 24;
606        private static final SynchronizedPool<RefreshData> sPool =
607                new SynchronizedPool<RefreshData>(POOL_MAX);
608
609        public int id;
610        public int progress;
611        public boolean fromUser;
612
613        public static RefreshData obtain(int id, int progress, boolean fromUser) {
614            RefreshData rd = sPool.acquire();
615            if (rd == null) {
616                rd = new RefreshData();
617            }
618            rd.id = id;
619            rd.progress = progress;
620            rd.fromUser = fromUser;
621            return rd;
622        }
623
624        public void recycle() {
625            sPool.release(this);
626        }
627    }
628
629    private synchronized void doRefreshProgress(int id, int progress, boolean fromUser,
630            boolean callBackToApp) {
631        float scale = mMax > 0 ? (float) progress / (float) mMax : 0;
632        final Drawable d = mCurrentDrawable;
633        if (d != null) {
634            Drawable progressDrawable = null;
635
636            if (d instanceof LayerDrawable) {
637                progressDrawable = ((LayerDrawable) d).findDrawableByLayerId(id);
638                if (progressDrawable != null && canResolveLayoutDirection()) {
639                    progressDrawable.setLayoutDirection(getLayoutDirection());
640                }
641            }
642
643            final int level = (int) (scale * MAX_LEVEL);
644            (progressDrawable != null ? progressDrawable : d).setLevel(level);
645        } else {
646            invalidate();
647        }
648
649        if (callBackToApp && id == R.id.progress) {
650            onProgressRefresh(scale, fromUser);
651        }
652    }
653
654    void onProgressRefresh(float scale, boolean fromUser) {
655        if (AccessibilityManager.getInstance(mContext).isEnabled()) {
656            scheduleAccessibilityEventSender();
657        }
658    }
659
660    private synchronized void refreshProgress(int id, int progress, boolean fromUser) {
661        if (mUiThreadId == Thread.currentThread().getId()) {
662            doRefreshProgress(id, progress, fromUser, true);
663        } else {
664            if (mRefreshProgressRunnable == null) {
665                mRefreshProgressRunnable = new RefreshProgressRunnable();
666            }
667
668            final RefreshData rd = RefreshData.obtain(id, progress, fromUser);
669            mRefreshData.add(rd);
670            if (mAttached && !mRefreshIsPosted) {
671                post(mRefreshProgressRunnable);
672                mRefreshIsPosted = true;
673            }
674        }
675    }
676
677    /**
678     * <p>Set the current progress to the specified value. Does not do anything
679     * if the progress bar is in indeterminate mode.</p>
680     *
681     * @param progress the new progress, between 0 and {@link #getMax()}
682     *
683     * @see #setIndeterminate(boolean)
684     * @see #isIndeterminate()
685     * @see #getProgress()
686     * @see #incrementProgressBy(int)
687     */
688    @android.view.RemotableViewMethod
689    public synchronized void setProgress(int progress) {
690        setProgress(progress, false);
691    }
692
693    @android.view.RemotableViewMethod
694    synchronized void setProgress(int progress, boolean fromUser) {
695        if (mIndeterminate) {
696            return;
697        }
698
699        if (progress < 0) {
700            progress = 0;
701        }
702
703        if (progress > mMax) {
704            progress = mMax;
705        }
706
707        if (progress != mProgress) {
708            mProgress = progress;
709            refreshProgress(R.id.progress, mProgress, fromUser);
710        }
711    }
712
713    /**
714     * <p>
715     * Set the current secondary progress to the specified value. Does not do
716     * anything if the progress bar is in indeterminate mode.
717     * </p>
718     *
719     * @param secondaryProgress the new secondary progress, between 0 and {@link #getMax()}
720     * @see #setIndeterminate(boolean)
721     * @see #isIndeterminate()
722     * @see #getSecondaryProgress()
723     * @see #incrementSecondaryProgressBy(int)
724     */
725    @android.view.RemotableViewMethod
726    public synchronized void setSecondaryProgress(int secondaryProgress) {
727        if (mIndeterminate) {
728            return;
729        }
730
731        if (secondaryProgress < 0) {
732            secondaryProgress = 0;
733        }
734
735        if (secondaryProgress > mMax) {
736            secondaryProgress = mMax;
737        }
738
739        if (secondaryProgress != mSecondaryProgress) {
740            mSecondaryProgress = secondaryProgress;
741            refreshProgress(R.id.secondaryProgress, mSecondaryProgress, false);
742        }
743    }
744
745    /**
746     * <p>Get the progress bar's current level of progress. Return 0 when the
747     * progress bar is in indeterminate mode.</p>
748     *
749     * @return the current progress, between 0 and {@link #getMax()}
750     *
751     * @see #setIndeterminate(boolean)
752     * @see #isIndeterminate()
753     * @see #setProgress(int)
754     * @see #setMax(int)
755     * @see #getMax()
756     */
757    @ViewDebug.ExportedProperty(category = "progress")
758    public synchronized int getProgress() {
759        return mIndeterminate ? 0 : mProgress;
760    }
761
762    /**
763     * <p>Get the progress bar's current level of secondary progress. Return 0 when the
764     * progress bar is in indeterminate mode.</p>
765     *
766     * @return the current secondary progress, between 0 and {@link #getMax()}
767     *
768     * @see #setIndeterminate(boolean)
769     * @see #isIndeterminate()
770     * @see #setSecondaryProgress(int)
771     * @see #setMax(int)
772     * @see #getMax()
773     */
774    @ViewDebug.ExportedProperty(category = "progress")
775    public synchronized int getSecondaryProgress() {
776        return mIndeterminate ? 0 : mSecondaryProgress;
777    }
778
779    /**
780     * <p>Return the upper limit of this progress bar's range.</p>
781     *
782     * @return a positive integer
783     *
784     * @see #setMax(int)
785     * @see #getProgress()
786     * @see #getSecondaryProgress()
787     */
788    @ViewDebug.ExportedProperty(category = "progress")
789    public synchronized int getMax() {
790        return mMax;
791    }
792
793    /**
794     * <p>Set the range of the progress bar to 0...<tt>max</tt>.</p>
795     *
796     * @param max the upper range of this progress bar
797     *
798     * @see #getMax()
799     * @see #setProgress(int)
800     * @see #setSecondaryProgress(int)
801     */
802    @android.view.RemotableViewMethod
803    public synchronized void setMax(int max) {
804        if (max < 0) {
805            max = 0;
806        }
807        if (max != mMax) {
808            mMax = max;
809            postInvalidate();
810
811            if (mProgress > max) {
812                mProgress = max;
813            }
814            refreshProgress(R.id.progress, mProgress, false);
815        }
816    }
817
818    /**
819     * <p>Increase the progress bar's progress by the specified amount.</p>
820     *
821     * @param diff the amount by which the progress must be increased
822     *
823     * @see #setProgress(int)
824     */
825    public synchronized final void incrementProgressBy(int diff) {
826        setProgress(mProgress + diff);
827    }
828
829    /**
830     * <p>Increase the progress bar's secondary progress by the specified amount.</p>
831     *
832     * @param diff the amount by which the secondary progress must be increased
833     *
834     * @see #setSecondaryProgress(int)
835     */
836    public synchronized final void incrementSecondaryProgressBy(int diff) {
837        setSecondaryProgress(mSecondaryProgress + diff);
838    }
839
840    /**
841     * <p>Start the indeterminate progress animation.</p>
842     */
843    void startAnimation() {
844        if (getVisibility() != VISIBLE) {
845            return;
846        }
847
848        if (mIndeterminateDrawable instanceof Animatable) {
849            mShouldStartAnimationDrawable = true;
850            mHasAnimation = false;
851        } else {
852            mHasAnimation = true;
853
854            if (mInterpolator == null) {
855                mInterpolator = new LinearInterpolator();
856            }
857
858            if (mTransformation == null) {
859                mTransformation = new Transformation();
860            } else {
861                mTransformation.clear();
862            }
863
864            if (mAnimation == null) {
865                mAnimation = new AlphaAnimation(0.0f, 1.0f);
866            } else {
867                mAnimation.reset();
868            }
869
870            mAnimation.setRepeatMode(mBehavior);
871            mAnimation.setRepeatCount(Animation.INFINITE);
872            mAnimation.setDuration(mDuration);
873            mAnimation.setInterpolator(mInterpolator);
874            mAnimation.setStartTime(Animation.START_ON_FIRST_FRAME);
875        }
876        postInvalidate();
877    }
878
879    /**
880     * <p>Stop the indeterminate progress animation.</p>
881     */
882    void stopAnimation() {
883        mHasAnimation = false;
884        if (mIndeterminateDrawable instanceof Animatable) {
885            ((Animatable) mIndeterminateDrawable).stop();
886            mShouldStartAnimationDrawable = false;
887        }
888        postInvalidate();
889    }
890
891    /**
892     * Sets the acceleration curve for the indeterminate animation.
893     * The interpolator is loaded as a resource from the specified context.
894     *
895     * @param context The application environment
896     * @param resID The resource identifier of the interpolator to load
897     */
898    public void setInterpolator(Context context, int resID) {
899        setInterpolator(AnimationUtils.loadInterpolator(context, resID));
900    }
901
902    /**
903     * Sets the acceleration curve for the indeterminate animation.
904     * Defaults to a linear interpolation.
905     *
906     * @param interpolator The interpolator which defines the acceleration curve
907     */
908    public void setInterpolator(Interpolator interpolator) {
909        mInterpolator = interpolator;
910    }
911
912    /**
913     * Gets the acceleration curve type for the indeterminate animation.
914     *
915     * @return the {@link Interpolator} associated to this animation
916     */
917    public Interpolator getInterpolator() {
918        return mInterpolator;
919    }
920
921    @Override
922    @RemotableViewMethod
923    public void setVisibility(int v) {
924        if (getVisibility() != v) {
925            super.setVisibility(v);
926
927            if (mIndeterminate) {
928                // let's be nice with the UI thread
929                if (v == GONE || v == INVISIBLE) {
930                    stopAnimation();
931                } else {
932                    startAnimation();
933                }
934            }
935        }
936    }
937
938    @Override
939    protected void onVisibilityChanged(View changedView, int visibility) {
940        super.onVisibilityChanged(changedView, visibility);
941
942        if (mIndeterminate) {
943            // let's be nice with the UI thread
944            if (visibility == GONE || visibility == INVISIBLE) {
945                stopAnimation();
946            } else {
947                startAnimation();
948            }
949        }
950    }
951
952    @Override
953    public void invalidateDrawable(Drawable dr) {
954        if (!mInDrawing) {
955            if (verifyDrawable(dr)) {
956                final Rect dirty = dr.getBounds();
957                final int scrollX = mScrollX + mPaddingLeft;
958                final int scrollY = mScrollY + mPaddingTop;
959
960                invalidate(dirty.left + scrollX, dirty.top + scrollY,
961                        dirty.right + scrollX, dirty.bottom + scrollY);
962            } else {
963                super.invalidateDrawable(dr);
964            }
965        }
966    }
967
968    @Override
969    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
970        updateDrawableBounds(w, h);
971    }
972
973    private void updateDrawableBounds(int w, int h) {
974        // onDraw will translate the canvas so we draw starting at 0,0.
975        // Subtract out padding for the purposes of the calculations below.
976        w -= mPaddingRight + mPaddingLeft;
977        h -= mPaddingTop + mPaddingBottom;
978
979        int right = w;
980        int bottom = h;
981        int top = 0;
982        int left = 0;
983
984        if (mIndeterminateDrawable != null) {
985            // Aspect ratio logic does not apply to AnimationDrawables
986            if (mOnlyIndeterminate && !(mIndeterminateDrawable instanceof AnimationDrawable)) {
987                // Maintain aspect ratio. Certain kinds of animated drawables
988                // get very confused otherwise.
989                final int intrinsicWidth = mIndeterminateDrawable.getIntrinsicWidth();
990                final int intrinsicHeight = mIndeterminateDrawable.getIntrinsicHeight();
991                final float intrinsicAspect = (float) intrinsicWidth / intrinsicHeight;
992                final float boundAspect = (float) w / h;
993                if (intrinsicAspect != boundAspect) {
994                    if (boundAspect > intrinsicAspect) {
995                        // New width is larger. Make it smaller to match height.
996                        final int width = (int) (h * intrinsicAspect);
997                        left = (w - width) / 2;
998                        right = left + width;
999                    } else {
1000                        // New height is larger. Make it smaller to match width.
1001                        final int height = (int) (w * (1 / intrinsicAspect));
1002                        top = (h - height) / 2;
1003                        bottom = top + height;
1004                    }
1005                }
1006            }
1007            if (isLayoutRtl()) {
1008                int tempLeft = left;
1009                left = w - right;
1010                right = w - tempLeft;
1011            }
1012            mIndeterminateDrawable.setBounds(left, top, right, bottom);
1013        }
1014
1015        if (mProgressDrawable != null) {
1016            mProgressDrawable.setBounds(0, 0, right, bottom);
1017        }
1018    }
1019
1020    @Override
1021    protected synchronized void onDraw(Canvas canvas) {
1022        super.onDraw(canvas);
1023
1024        Drawable d = mCurrentDrawable;
1025        if (d != null) {
1026            // Translate canvas so a indeterminate circular progress bar with padding
1027            // rotates properly in its animation
1028            canvas.save();
1029            if(isLayoutRtl()) {
1030                canvas.translate(getWidth() - mPaddingRight, mPaddingTop);
1031                canvas.scale(-1.0f, 1.0f);
1032            } else {
1033                canvas.translate(mPaddingLeft, mPaddingTop);
1034            }
1035            long time = getDrawingTime();
1036            if (mHasAnimation) {
1037                mAnimation.getTransformation(time, mTransformation);
1038                float scale = mTransformation.getAlpha();
1039                try {
1040                    mInDrawing = true;
1041                    d.setLevel((int) (scale * MAX_LEVEL));
1042                } finally {
1043                    mInDrawing = false;
1044                }
1045                postInvalidateOnAnimation();
1046            }
1047            d.draw(canvas);
1048            canvas.restore();
1049            if (mShouldStartAnimationDrawable && d instanceof Animatable) {
1050                ((Animatable) d).start();
1051                mShouldStartAnimationDrawable = false;
1052            }
1053        }
1054    }
1055
1056    @Override
1057    protected synchronized void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
1058        Drawable d = mCurrentDrawable;
1059
1060        int dw = 0;
1061        int dh = 0;
1062        if (d != null) {
1063            dw = Math.max(mMinWidth, Math.min(mMaxWidth, d.getIntrinsicWidth()));
1064            dh = Math.max(mMinHeight, Math.min(mMaxHeight, d.getIntrinsicHeight()));
1065        }
1066        updateDrawableState();
1067        dw += mPaddingLeft + mPaddingRight;
1068        dh += mPaddingTop + mPaddingBottom;
1069
1070        setMeasuredDimension(resolveSizeAndState(dw, widthMeasureSpec, 0),
1071                resolveSizeAndState(dh, heightMeasureSpec, 0));
1072    }
1073
1074    @Override
1075    protected void drawableStateChanged() {
1076        super.drawableStateChanged();
1077        updateDrawableState();
1078    }
1079
1080    private void updateDrawableState() {
1081        int[] state = getDrawableState();
1082
1083        if (mProgressDrawable != null && mProgressDrawable.isStateful()) {
1084            mProgressDrawable.setState(state);
1085        }
1086
1087        if (mIndeterminateDrawable != null && mIndeterminateDrawable.isStateful()) {
1088            mIndeterminateDrawable.setState(state);
1089        }
1090    }
1091
1092    static class SavedState extends BaseSavedState {
1093        int progress;
1094        int secondaryProgress;
1095
1096        /**
1097         * Constructor called from {@link ProgressBar#onSaveInstanceState()}
1098         */
1099        SavedState(Parcelable superState) {
1100            super(superState);
1101        }
1102
1103        /**
1104         * Constructor called from {@link #CREATOR}
1105         */
1106        private SavedState(Parcel in) {
1107            super(in);
1108            progress = in.readInt();
1109            secondaryProgress = in.readInt();
1110        }
1111
1112        @Override
1113        public void writeToParcel(Parcel out, int flags) {
1114            super.writeToParcel(out, flags);
1115            out.writeInt(progress);
1116            out.writeInt(secondaryProgress);
1117        }
1118
1119        public static final Parcelable.Creator<SavedState> CREATOR
1120                = new Parcelable.Creator<SavedState>() {
1121            public SavedState createFromParcel(Parcel in) {
1122                return new SavedState(in);
1123            }
1124
1125            public SavedState[] newArray(int size) {
1126                return new SavedState[size];
1127            }
1128        };
1129    }
1130
1131    @Override
1132    public Parcelable onSaveInstanceState() {
1133        // Force our ancestor class to save its state
1134        Parcelable superState = super.onSaveInstanceState();
1135        SavedState ss = new SavedState(superState);
1136
1137        ss.progress = mProgress;
1138        ss.secondaryProgress = mSecondaryProgress;
1139
1140        return ss;
1141    }
1142
1143    @Override
1144    public void onRestoreInstanceState(Parcelable state) {
1145        SavedState ss = (SavedState) state;
1146        super.onRestoreInstanceState(ss.getSuperState());
1147
1148        setProgress(ss.progress);
1149        setSecondaryProgress(ss.secondaryProgress);
1150    }
1151
1152    @Override
1153    protected void onAttachedToWindow() {
1154        super.onAttachedToWindow();
1155        if (mIndeterminate) {
1156            startAnimation();
1157        }
1158        if (mRefreshData != null) {
1159            synchronized (this) {
1160                final int count = mRefreshData.size();
1161                for (int i = 0; i < count; i++) {
1162                    final RefreshData rd = mRefreshData.get(i);
1163                    doRefreshProgress(rd.id, rd.progress, rd.fromUser, true);
1164                    rd.recycle();
1165                }
1166                mRefreshData.clear();
1167            }
1168        }
1169        mAttached = true;
1170    }
1171
1172    @Override
1173    protected void onDetachedFromWindow() {
1174        if (mIndeterminate) {
1175            stopAnimation();
1176        }
1177        if (mRefreshProgressRunnable != null) {
1178            removeCallbacks(mRefreshProgressRunnable);
1179        }
1180        if (mRefreshProgressRunnable != null && mRefreshIsPosted) {
1181            removeCallbacks(mRefreshProgressRunnable);
1182        }
1183        if (mAccessibilityEventSender != null) {
1184            removeCallbacks(mAccessibilityEventSender);
1185        }
1186        // This should come after stopAnimation(), otherwise an invalidate message remains in the
1187        // queue, which can prevent the entire view hierarchy from being GC'ed during a rotation
1188        super.onDetachedFromWindow();
1189        mAttached = false;
1190    }
1191
1192    @Override
1193    public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
1194        super.onInitializeAccessibilityEvent(event);
1195        event.setClassName(ProgressBar.class.getName());
1196        event.setItemCount(mMax);
1197        event.setCurrentItemIndex(mProgress);
1198    }
1199
1200    @Override
1201    public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
1202        super.onInitializeAccessibilityNodeInfo(info);
1203        info.setClassName(ProgressBar.class.getName());
1204    }
1205
1206    /**
1207     * Schedule a command for sending an accessibility event.
1208     * </br>
1209     * Note: A command is used to ensure that accessibility events
1210     *       are sent at most one in a given time frame to save
1211     *       system resources while the progress changes quickly.
1212     */
1213    private void scheduleAccessibilityEventSender() {
1214        if (mAccessibilityEventSender == null) {
1215            mAccessibilityEventSender = new AccessibilityEventSender();
1216        } else {
1217            removeCallbacks(mAccessibilityEventSender);
1218        }
1219        postDelayed(mAccessibilityEventSender, TIMEOUT_SEND_ACCESSIBILITY_EVENT);
1220    }
1221
1222    /**
1223     * Command for sending an accessibility event.
1224     */
1225    private class AccessibilityEventSender implements Runnable {
1226        public void run() {
1227            sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_SELECTED);
1228        }
1229    }
1230}
1231