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