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