1package android.support.v7.internal.widget;
2
3/*
4 * Copyright (C) 2013 Android Open Source Project
5 *
6 * Licensed under the Apache License, Version 2.0 (the "License");
7 * you may not use this file except in compliance with the License.
8 * You may obtain a copy of the License at
9 *
10 *      http://www.apache.org/licenses/LICENSE-2.0
11 *
12 * Unless required by applicable law or agreed to in writing, software
13 * distributed under the License is distributed on an "AS IS" BASIS,
14 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 * See the License for the specific language governing permissions and
16 * limitations under the License.
17 */
18
19
20import android.content.Context;
21import android.content.res.TypedArray;
22import android.graphics.Bitmap;
23import android.graphics.BitmapShader;
24import android.graphics.Canvas;
25import android.graphics.Rect;
26import android.graphics.Shader;
27import android.graphics.drawable.Animatable;
28import android.graphics.drawable.AnimationDrawable;
29import android.graphics.drawable.BitmapDrawable;
30import android.graphics.drawable.ClipDrawable;
31import android.graphics.drawable.Drawable;
32import android.graphics.drawable.LayerDrawable;
33import android.graphics.drawable.ShapeDrawable;
34import android.graphics.drawable.shapes.RoundRectShape;
35import android.graphics.drawable.shapes.Shape;
36import android.os.Parcel;
37import android.os.Parcelable;
38import android.os.SystemClock;
39import android.util.AttributeSet;
40import android.view.Gravity;
41import android.view.View;
42import android.view.animation.AlphaAnimation;
43import android.view.animation.Animation;
44import android.view.animation.AnimationUtils;
45import android.view.animation.Interpolator;
46import android.view.animation.LinearInterpolator;
47import android.view.animation.Transformation;
48
49/**
50 * @hide
51 */
52public class ProgressBarICS extends View {
53
54    private static final int MAX_LEVEL = 10000;
55    private static final int ANIMATION_RESOLUTION = 200;
56
57    /**
58     * android.R.styleable.ProgressBar is internalised, so we need to create it ourselves.
59      */
60    private static final int[] android_R_styleable_ProgressBar = new int[]{
61            android.R.attr.max,
62            android.R.attr.progress,
63            android.R.attr.secondaryProgress,
64            android.R.attr.indeterminate,
65            android.R.attr.indeterminateOnly,
66            android.R.attr.indeterminateDrawable,
67            android.R.attr.progressDrawable,
68            android.R.attr.indeterminateDuration,
69            android.R.attr.indeterminateBehavior,
70            android.R.attr.minWidth,
71            android.R.attr.maxWidth,
72            android.R.attr.minHeight,
73            android.R.attr.maxHeight,
74            android.R.attr.interpolator,
75    };
76
77    int mMinWidth;
78    int mMaxWidth;
79    int mMinHeight;
80    int mMaxHeight;
81
82    private int mProgress;
83    private int mSecondaryProgress;
84    private int mMax;
85
86    private int mBehavior;
87    private int mDuration;
88    private boolean mIndeterminate;
89    private boolean mOnlyIndeterminate;
90    private Transformation mTransformation;
91    private AlphaAnimation mAnimation;
92    private Drawable mIndeterminateDrawable;
93    private Drawable mProgressDrawable;
94    private Drawable mCurrentDrawable;
95    Bitmap mSampleTile;
96    private boolean mNoInvalidate;
97    private Interpolator mInterpolator;
98    private RefreshProgressRunnable mRefreshProgressRunnable;
99    private long mUiThreadId;
100    private boolean mShouldStartAnimationDrawable;
101    private long mLastDrawTime;
102
103    private boolean mInDrawing;
104
105    /**
106     * @hide
107     */
108    public ProgressBarICS(Context context, AttributeSet attrs, int defStyle, int styleRes) {
109        super(context, attrs, defStyle);
110        mUiThreadId = Thread.currentThread().getId();
111        initProgressBar();
112
113        TypedArray a = context.obtainStyledAttributes(attrs, android_R_styleable_ProgressBar,
114                defStyle, styleRes);
115
116        mNoInvalidate = true;
117
118        setMax(a.getInt(0, mMax));
119        setProgress(a.getInt(1, mProgress));
120        setSecondaryProgress(a.getInt(2, mSecondaryProgress));
121
122        final boolean indeterminate = a.getBoolean(3, mIndeterminate);
123        mOnlyIndeterminate = a.getBoolean(4, mOnlyIndeterminate);
124
125        Drawable drawable = a.getDrawable(5);
126        if (drawable != null) {
127            drawable = tileifyIndeterminate(drawable);
128            setIndeterminateDrawable(drawable);
129        }
130
131        drawable = a.getDrawable(6);
132        if (drawable != null) {
133            drawable = tileify(drawable, false);
134            // Calling this method can set mMaxHeight, make sure the corresponding
135            // XML attribute for mMaxHeight is read after calling this method
136            setProgressDrawable(drawable);
137        }
138
139        mDuration = a.getInt(7, mDuration);
140        mBehavior = a.getInt(8, mBehavior);
141        mMinWidth = a.getDimensionPixelSize(9, mMinWidth);
142        mMaxWidth = a.getDimensionPixelSize(10, mMaxWidth);
143        mMinHeight = a.getDimensionPixelSize(11, mMinHeight);
144        mMaxHeight = a.getDimensionPixelSize(12, mMaxHeight);
145
146        final int resID = a.getResourceId(13, android.R.anim.linear_interpolator);
147        if (resID > 0) {
148            setInterpolator(context, resID);
149        }
150
151        a.recycle();
152
153        mNoInvalidate = false;
154        setIndeterminate(mOnlyIndeterminate || indeterminate);
155    }
156
157    /**
158     * Converts a drawable to a tiled version of itself. It will recursively
159     * traverse layer and state list drawables.
160     */
161    private Drawable tileify(Drawable drawable, boolean clip) {
162
163        if (drawable instanceof LayerDrawable) {
164            LayerDrawable background = (LayerDrawable) drawable;
165            final int N = background.getNumberOfLayers();
166            Drawable[] outDrawables = new Drawable[N];
167
168            for (int i = 0; i < N; i++) {
169                int id = background.getId(i);
170                outDrawables[i] = tileify(background.getDrawable(i),
171                        (id == android.R.id.progress || id == android.R.id.secondaryProgress));
172            }
173
174            LayerDrawable newBg = new LayerDrawable(outDrawables);
175
176            for (int i = 0; i < N; i++) {
177                newBg.setId(i, background.getId(i));
178            }
179
180            return newBg;
181
182        } else if (drawable instanceof BitmapDrawable) {
183            final Bitmap tileBitmap = ((BitmapDrawable) drawable).getBitmap();
184            if (mSampleTile == null) {
185                mSampleTile = tileBitmap;
186            }
187
188            final ShapeDrawable shapeDrawable = new ShapeDrawable(getDrawableShape());
189
190            final BitmapShader bitmapShader = new BitmapShader(tileBitmap,
191                    Shader.TileMode.REPEAT, Shader.TileMode.CLAMP);
192            shapeDrawable.getPaint().setShader(bitmapShader);
193
194            return (clip) ? new ClipDrawable(shapeDrawable, Gravity.LEFT,
195                    ClipDrawable.HORIZONTAL) : shapeDrawable;
196        }
197
198        return drawable;
199    }
200
201    Shape getDrawableShape() {
202        final float[] roundedCorners = new float[] { 5, 5, 5, 5, 5, 5, 5, 5 };
203        return new RoundRectShape(roundedCorners, null, null);
204    }
205
206    /**
207     * Convert a AnimationDrawable for use as a barberpole animation.
208     * Each frame of the animation is wrapped in a ClipDrawable and
209     * given a tiling BitmapShader.
210     */
211    private Drawable tileifyIndeterminate(Drawable drawable) {
212        if (drawable instanceof AnimationDrawable) {
213            AnimationDrawable background = (AnimationDrawable) drawable;
214            final int N = background.getNumberOfFrames();
215            AnimationDrawable newBg = new AnimationDrawable();
216            newBg.setOneShot(background.isOneShot());
217
218            for (int i = 0; i < N; i++) {
219                Drawable frame = tileify(background.getFrame(i), true);
220                frame.setLevel(10000);
221                newBg.addFrame(frame, background.getDuration(i));
222            }
223            newBg.setLevel(10000);
224            drawable = newBg;
225        }
226        return drawable;
227    }
228
229    /**
230     * <p>
231     * Initialize the progress bar's default values:
232     * </p>
233     * <ul>
234     * <li>progress = 0</li>
235     * <li>max = 100</li>
236     * <li>animation duration = 4000 ms</li>
237     * <li>indeterminate = false</li>
238     * <li>behavior = repeat</li>
239     * </ul>
240     */
241    private void initProgressBar() {
242        mMax = 100;
243        mProgress = 0;
244        mSecondaryProgress = 0;
245        mIndeterminate = false;
246        mOnlyIndeterminate = false;
247        mDuration = 4000;
248        mBehavior = AlphaAnimation.RESTART;
249        mMinWidth = 24;
250        mMaxWidth = 48;
251        mMinHeight = 24;
252        mMaxHeight = 48;
253    }
254
255    /**
256     * <p>Indicate whether this progress bar is in indeterminate mode.</p>
257     *
258     * @return true if the progress bar is in indeterminate mode
259     */
260    public synchronized boolean isIndeterminate() {
261        return mIndeterminate;
262    }
263
264    /**
265     * <p>Change the indeterminate mode for this progress bar. In indeterminate
266     * mode, the progress is ignored and the progress bar shows an infinite
267     * animation instead.</p>
268     *
269     * If this progress bar's style only supports indeterminate mode (such as the circular
270     * progress bars), then this will be ignored.
271     *
272     * @param indeterminate true to enable the indeterminate mode
273     */
274    public synchronized void setIndeterminate(boolean indeterminate) {
275        if ((!mOnlyIndeterminate || !mIndeterminate) && indeterminate != mIndeterminate) {
276            mIndeterminate = indeterminate;
277
278            if (indeterminate) {
279                // swap between indeterminate and regular backgrounds
280                mCurrentDrawable = mIndeterminateDrawable;
281                startAnimation();
282            } else {
283                mCurrentDrawable = mProgressDrawable;
284                stopAnimation();
285            }
286        }
287    }
288
289    /**
290     * <p>Get the drawable used to draw the progress bar in
291     * indeterminate mode.</p>
292     *
293     * @return a {@link android.graphics.drawable.Drawable} instance
294     *
295     * @see #setIndeterminateDrawable(android.graphics.drawable.Drawable)
296     * @see #setIndeterminate(boolean)
297     */
298    public Drawable getIndeterminateDrawable() {
299        return mIndeterminateDrawable;
300    }
301
302    /**
303     * <p>Define the drawable used to draw the progress bar in
304     * indeterminate mode.</p>
305     *
306     * @param d the new drawable
307     *
308     * @see #getIndeterminateDrawable()
309     * @see #setIndeterminate(boolean)
310     */
311    public void setIndeterminateDrawable(Drawable d) {
312        if (d != null) {
313            d.setCallback(this);
314        }
315        mIndeterminateDrawable = d;
316        if (mIndeterminate) {
317            mCurrentDrawable = d;
318            postInvalidate();
319        }
320    }
321
322    /**
323     * <p>Get the drawable used to draw the progress bar in
324     * progress mode.</p>
325     *
326     * @return a {@link android.graphics.drawable.Drawable} instance
327     *
328     * @see #setProgressDrawable(android.graphics.drawable.Drawable)
329     * @see #setIndeterminate(boolean)
330     */
331    public Drawable getProgressDrawable() {
332        return mProgressDrawable;
333    }
334
335    /**
336     * <p>Define the drawable used to draw the progress bar in
337     * progress mode.</p>
338     *
339     * @param d the new drawable
340     *
341     * @see #getProgressDrawable()
342     * @see #setIndeterminate(boolean)
343     */
344    public void setProgressDrawable(Drawable d) {
345        boolean needUpdate;
346        if (mProgressDrawable != null && d != mProgressDrawable) {
347            mProgressDrawable.setCallback(null);
348            needUpdate = true;
349        } else {
350            needUpdate = false;
351        }
352
353        if (d != null) {
354            d.setCallback(this);
355
356            // Make sure the android_R_styleable_ProgressBar is always tall enough
357            int drawableHeight = d.getMinimumHeight();
358            if (mMaxHeight < drawableHeight) {
359                mMaxHeight = drawableHeight;
360                requestLayout();
361            }
362        }
363        mProgressDrawable = d;
364        if (!mIndeterminate) {
365            mCurrentDrawable = d;
366            postInvalidate();
367        }
368
369        if (needUpdate) {
370            updateDrawableBounds(getWidth(), getHeight());
371            updateDrawableState();
372            doRefreshProgress(android.R.id.progress, mProgress, false, false);
373            doRefreshProgress(android.R.id.secondaryProgress, mSecondaryProgress, false, false);
374        }
375    }
376
377    @Override
378    protected boolean verifyDrawable(Drawable who) {
379        return who == mProgressDrawable || who == mIndeterminateDrawable
380                || super.verifyDrawable(who);
381    }
382
383    @Override
384    public void postInvalidate() {
385        if (!mNoInvalidate) {
386            super.postInvalidate();
387        }
388    }
389
390    private class RefreshProgressRunnable implements Runnable {
391
392        private int mId;
393        private int mProgress;
394        private boolean mFromUser;
395
396        RefreshProgressRunnable(int id, int progress, boolean fromUser) {
397            mId = id;
398            mProgress = progress;
399            mFromUser = fromUser;
400        }
401
402        public void run() {
403            doRefreshProgress(mId, mProgress, mFromUser, true);
404            // Put ourselves back in the cache when we are done
405            mRefreshProgressRunnable = this;
406        }
407
408        public void setup(int id, int progress, boolean fromUser) {
409            mId = id;
410            mProgress = progress;
411            mFromUser = fromUser;
412        }
413
414    }
415
416    private synchronized void doRefreshProgress(int id, int progress, boolean fromUser,
417            boolean callBackToApp) {
418        float scale = mMax > 0 ? (float) progress / (float) mMax : 0;
419        final Drawable d = mCurrentDrawable;
420        if (d != null) {
421            Drawable progressDrawable = null;
422
423            if (d instanceof LayerDrawable) {
424                progressDrawable = ((LayerDrawable) d).findDrawableByLayerId(id);
425            }
426
427            final int level = (int) (scale * MAX_LEVEL);
428            (progressDrawable != null ? progressDrawable : d).setLevel(level);
429        } else {
430            invalidate();
431        }
432    }
433
434    private synchronized void refreshProgress(int id, int progress, boolean fromUser) {
435        if (mUiThreadId == Thread.currentThread().getId()) {
436            doRefreshProgress(id, progress, fromUser, true);
437        } else {
438            RefreshProgressRunnable r;
439            if (mRefreshProgressRunnable != null) {
440                // Use cached RefreshProgressRunnable if available
441                r = mRefreshProgressRunnable;
442                // Uncache it
443                mRefreshProgressRunnable = null;
444                r.setup(id, progress, fromUser);
445            } else {
446                // Make a new one
447                r = new RefreshProgressRunnable(id, progress, fromUser);
448            }
449            post(r);
450        }
451    }
452
453    /**
454     * <p>Set the current progress to the specified value. Does not do anything
455     * if the progress bar is in indeterminate mode.</p>
456     *
457     * @param progress the new progress, between 0 and {@link #getMax()}
458     *
459     * @see #setIndeterminate(boolean)
460     * @see #isIndeterminate()
461     * @see #getProgress()
462     * @see #incrementProgressBy(int)
463     */
464    public synchronized void setProgress(int progress) {
465        setProgress(progress, false);
466    }
467
468    synchronized void setProgress(int progress, boolean fromUser) {
469        if (mIndeterminate) {
470            return;
471        }
472
473        if (progress < 0) {
474            progress = 0;
475        }
476
477        if (progress > mMax) {
478            progress = mMax;
479        }
480
481        if (progress != mProgress) {
482            mProgress = progress;
483            refreshProgress(android.R.id.progress, mProgress, fromUser);
484        }
485    }
486
487    /**
488     * <p>
489     * Set the current secondary progress to the specified value. Does not do
490     * anything if the progress bar is in indeterminate mode.
491     * </p>
492     *
493     * @param secondaryProgress the new secondary progress, between 0 and {@link #getMax()}
494     * @see #setIndeterminate(boolean)
495     * @see #isIndeterminate()
496     * @see #getSecondaryProgress()
497     * @see #incrementSecondaryProgressBy(int)
498     */
499    public synchronized void setSecondaryProgress(int secondaryProgress) {
500        if (mIndeterminate) {
501            return;
502        }
503
504        if (secondaryProgress < 0) {
505            secondaryProgress = 0;
506        }
507
508        if (secondaryProgress > mMax) {
509            secondaryProgress = mMax;
510        }
511
512        if (secondaryProgress != mSecondaryProgress) {
513            mSecondaryProgress = secondaryProgress;
514            refreshProgress(android.R.id.secondaryProgress, mSecondaryProgress, false);
515        }
516    }
517
518    /**
519     * <p>Get the progress bar's current level of progress. Return 0 when the
520     * progress bar is in indeterminate mode.</p>
521     *
522     * @return the current progress, between 0 and {@link #getMax()}
523     *
524     * @see #setIndeterminate(boolean)
525     * @see #isIndeterminate()
526     * @see #setProgress(int)
527     * @see #setMax(int)
528     * @see #getMax()
529     */
530    public synchronized int getProgress() {
531        return mIndeterminate ? 0 : mProgress;
532    }
533
534    /**
535     * <p>Get the progress bar's current level of secondary progress. Return 0 when the
536     * progress bar is in indeterminate mode.</p>
537     *
538     * @return the current secondary progress, between 0 and {@link #getMax()}
539     *
540     * @see #setIndeterminate(boolean)
541     * @see #isIndeterminate()
542     * @see #setSecondaryProgress(int)
543     * @see #setMax(int)
544     * @see #getMax()
545     */
546    public synchronized int getSecondaryProgress() {
547        return mIndeterminate ? 0 : mSecondaryProgress;
548    }
549
550    /**
551     * <p>Return the upper limit of this progress bar's range.</p>
552     *
553     * @return a positive integer
554     *
555     * @see #setMax(int)
556     * @see #getProgress()
557     * @see #getSecondaryProgress()
558     */
559    public synchronized int getMax() {
560        return mMax;
561    }
562
563    /**
564     * <p>Set the range of the progress bar to 0...<tt>max</tt>.</p>
565     *
566     * @param max the upper range of this progress bar
567     *
568     * @see #getMax()
569     * @see #setProgress(int)
570     * @see #setSecondaryProgress(int)
571     */
572    public synchronized void setMax(int max) {
573        if (max < 0) {
574            max = 0;
575        }
576        if (max != mMax) {
577            mMax = max;
578            postInvalidate();
579
580            if (mProgress > max) {
581                mProgress = max;
582            }
583            refreshProgress(android.R.id.progress, mProgress, false);
584        }
585    }
586
587    /**
588     * <p>Increase the progress bar's progress by the specified amount.</p>
589     *
590     * @param diff the amount by which the progress must be increased
591     *
592     * @see #setProgress(int)
593     */
594    public synchronized final void incrementProgressBy(int diff) {
595        setProgress(mProgress + diff);
596    }
597
598    /**
599     * <p>Increase the progress bar's secondary progress by the specified amount.</p>
600     *
601     * @param diff the amount by which the secondary progress must be increased
602     *
603     * @see #setSecondaryProgress(int)
604     */
605    public synchronized final void incrementSecondaryProgressBy(int diff) {
606        setSecondaryProgress(mSecondaryProgress + diff);
607    }
608
609    /**
610     * <p>Start the indeterminate progress animation.</p>
611     */
612    void startAnimation() {
613        if (getVisibility() != VISIBLE) {
614            return;
615        }
616
617        if (mIndeterminateDrawable instanceof Animatable) {
618            mShouldStartAnimationDrawable = true;
619            mAnimation = null;
620        } else {
621            if (mInterpolator == null) {
622                mInterpolator = new LinearInterpolator();
623            }
624
625            mTransformation = new Transformation();
626            mAnimation = new AlphaAnimation(0.0f, 1.0f);
627            mAnimation.setRepeatMode(mBehavior);
628            mAnimation.setRepeatCount(Animation.INFINITE);
629            mAnimation.setDuration(mDuration);
630            mAnimation.setInterpolator(mInterpolator);
631            mAnimation.setStartTime(Animation.START_ON_FIRST_FRAME);
632        }
633        postInvalidate();
634    }
635
636    /**
637     * <p>Stop the indeterminate progress animation.</p>
638     */
639    void stopAnimation() {
640        mAnimation = null;
641        mTransformation = null;
642        if (mIndeterminateDrawable instanceof Animatable) {
643            ((Animatable) mIndeterminateDrawable).stop();
644            mShouldStartAnimationDrawable = false;
645        }
646        postInvalidate();
647    }
648
649    /**
650     * Sets the acceleration curve for the indeterminate animation.
651     * The interpolator is loaded as a resource from the specified context.
652     *
653     * @param context The application environment
654     * @param resID The resource identifier of the interpolator to load
655     */
656    public void setInterpolator(Context context, int resID) {
657        setInterpolator(AnimationUtils.loadInterpolator(context, resID));
658    }
659
660    /**
661     * Sets the acceleration curve for the indeterminate animation.
662     * Defaults to a linear interpolation.
663     *
664     * @param interpolator The interpolator which defines the acceleration curve
665     */
666    public void setInterpolator(Interpolator interpolator) {
667        mInterpolator = interpolator;
668    }
669
670    /**
671     * Gets the acceleration curve type for the indeterminate animation.
672     *
673     * @return the {@link Interpolator} associated to this animation
674     */
675    public Interpolator getInterpolator() {
676        return mInterpolator;
677    }
678
679    @Override
680    public void setVisibility(int v) {
681        if (getVisibility() != v) {
682            super.setVisibility(v);
683
684            if (mIndeterminate) {
685                // let's be nice with the UI thread
686                if (v == GONE || v == INVISIBLE) {
687                    stopAnimation();
688                } else {
689                    startAnimation();
690                }
691            }
692        }
693    }
694
695    @Override
696    protected void onVisibilityChanged(View changedView, int visibility) {
697        super.onVisibilityChanged(changedView, visibility);
698
699        if (mIndeterminate) {
700            // let's be nice with the UI thread
701            if (visibility == GONE || visibility == INVISIBLE) {
702                stopAnimation();
703            } else {
704                startAnimation();
705            }
706        }
707    }
708
709    @Override
710    public void invalidateDrawable(Drawable dr) {
711        if (!mInDrawing) {
712            if (verifyDrawable(dr)) {
713                final Rect dirty = dr.getBounds();
714                final int scrollX = getScrollX() + getPaddingLeft();
715                final int scrollY = getScrollY() + getPaddingTop();
716
717                invalidate(dirty.left + scrollX, dirty.top + scrollY,
718                        dirty.right + scrollX, dirty.bottom + scrollY);
719            } else {
720                super.invalidateDrawable(dr);
721            }
722        }
723    }
724
725    @Override
726    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
727        updateDrawableBounds(w, h);
728    }
729
730    private void updateDrawableBounds(int w, int h) {
731        // onDraw will translate the canvas so we draw starting at 0,0
732        int right = w - getPaddingRight() - getPaddingLeft();
733        int bottom = h - getPaddingBottom() - getPaddingTop();
734        int top = 0;
735        int left = 0;
736
737        if (mIndeterminateDrawable != null) {
738            // Aspect ratio logic does not apply to AnimationDrawables
739            if (mOnlyIndeterminate && !(mIndeterminateDrawable instanceof AnimationDrawable)) {
740                // Maintain aspect ratio. Certain kinds of animated drawables
741                // get very confused otherwise.
742                final int intrinsicWidth = mIndeterminateDrawable.getIntrinsicWidth();
743                final int intrinsicHeight = mIndeterminateDrawable.getIntrinsicHeight();
744                final float intrinsicAspect = (float) intrinsicWidth / intrinsicHeight;
745                final float boundAspect = (float) w / h;
746                if (intrinsicAspect != boundAspect) {
747                    if (boundAspect > intrinsicAspect) {
748                        // New width is larger. Make it smaller to match height.
749                        final int width = (int) (h * intrinsicAspect);
750                        left = (w - width) / 2;
751                        right = left + width;
752                    } else {
753                        // New height is larger. Make it smaller to match width.
754                        final int height = (int) (w * (1 / intrinsicAspect));
755                        top = (h - height) / 2;
756                        bottom = top + height;
757                    }
758                }
759            }
760            mIndeterminateDrawable.setBounds(left, top, right, bottom);
761        }
762
763        if (mProgressDrawable != null) {
764            mProgressDrawable.setBounds(0, 0, right, bottom);
765        }
766    }
767
768    @Override
769    protected synchronized void onDraw(Canvas canvas) {
770        super.onDraw(canvas);
771
772        Drawable d = mCurrentDrawable;
773        if (d != null) {
774            // Translate canvas so a indeterminate circular progress bar with padding
775            // rotates properly in its animation
776            canvas.save();
777            canvas.translate(getPaddingLeft(), getPaddingTop());
778            long time = getDrawingTime();
779            if (mAnimation != null) {
780                mAnimation.getTransformation(time, mTransformation);
781                float scale = mTransformation.getAlpha();
782                try {
783                    mInDrawing = true;
784                    d.setLevel((int) (scale * MAX_LEVEL));
785                } finally {
786                    mInDrawing = false;
787                }
788                if (SystemClock.uptimeMillis() - mLastDrawTime >= ANIMATION_RESOLUTION) {
789                    mLastDrawTime = SystemClock.uptimeMillis();
790                    postInvalidateDelayed(ANIMATION_RESOLUTION);
791                }
792            }
793            d.draw(canvas);
794            canvas.restore();
795            if (mShouldStartAnimationDrawable && d instanceof Animatable) {
796                ((Animatable) d).start();
797                mShouldStartAnimationDrawable = false;
798            }
799        }
800    }
801
802    @Override
803    protected synchronized void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
804        Drawable d = mCurrentDrawable;
805
806        int dw = 0;
807        int dh = 0;
808        if (d != null) {
809            dw = Math.max(mMinWidth, Math.min(mMaxWidth, d.getIntrinsicWidth()));
810            dh = Math.max(mMinHeight, Math.min(mMaxHeight, d.getIntrinsicHeight()));
811        }
812        updateDrawableState();
813        dw += getPaddingLeft() + getPaddingRight();
814        dh += getPaddingTop() + getPaddingBottom();
815
816        setMeasuredDimension(resolveSize(dw, widthMeasureSpec),
817                resolveSize(dh, heightMeasureSpec));
818    }
819
820    @Override
821    protected void drawableStateChanged() {
822        super.drawableStateChanged();
823        updateDrawableState();
824    }
825
826    private void updateDrawableState() {
827        int[] state = getDrawableState();
828
829        if (mProgressDrawable != null && mProgressDrawable.isStateful()) {
830            mProgressDrawable.setState(state);
831        }
832
833        if (mIndeterminateDrawable != null && mIndeterminateDrawable.isStateful()) {
834            mIndeterminateDrawable.setState(state);
835        }
836    }
837
838    static class SavedState extends BaseSavedState {
839        int progress;
840        int secondaryProgress;
841
842        /**
843         * Constructor called from {@link ProgressBarICS#onSaveInstanceState()}
844         */
845        SavedState(Parcelable superState) {
846            super(superState);
847        }
848
849        /**
850         * Constructor called from {@link #CREATOR}
851         */
852        private SavedState(Parcel in) {
853            super(in);
854            progress = in.readInt();
855            secondaryProgress = in.readInt();
856        }
857
858        @Override
859        public void writeToParcel(Parcel out, int flags) {
860            super.writeToParcel(out, flags);
861            out.writeInt(progress);
862            out.writeInt(secondaryProgress);
863        }
864
865        public static final Parcelable.Creator<SavedState> CREATOR
866                = new Parcelable.Creator<SavedState>() {
867            public SavedState createFromParcel(Parcel in) {
868                return new SavedState(in);
869            }
870
871            public SavedState[] newArray(int size) {
872                return new SavedState[size];
873            }
874        };
875    }
876
877    @Override
878    public Parcelable onSaveInstanceState() {
879        // Force our ancestor class to save its state
880        Parcelable superState = super.onSaveInstanceState();
881        SavedState ss = new SavedState(superState);
882
883        ss.progress = mProgress;
884        ss.secondaryProgress = mSecondaryProgress;
885
886        return ss;
887    }
888
889    @Override
890    public void onRestoreInstanceState(Parcelable state) {
891        SavedState ss = (SavedState) state;
892        super.onRestoreInstanceState(ss.getSuperState());
893
894        setProgress(ss.progress);
895        setSecondaryProgress(ss.secondaryProgress);
896    }
897
898    @Override
899    protected void onAttachedToWindow() {
900        super.onAttachedToWindow();
901        if (mIndeterminate) {
902            startAnimation();
903        }
904    }
905
906    @Override
907    protected void onDetachedFromWindow() {
908        if (mIndeterminate) {
909            stopAnimation();
910        }
911        if(mRefreshProgressRunnable != null) {
912            removeCallbacks(mRefreshProgressRunnable);
913        }
914
915        // This should come after stopAnimation(), otherwise an invalidate message remains in the
916        // queue, which can prevent the entire view hierarchy from being GC'ed during a rotation
917        super.onDetachedFromWindow();
918    }
919
920}