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