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