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