1/*
2 * Copyright (C) 2006 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package com.android.musicfx.seekbar;
18
19import com.android.internal.R;
20
21import android.content.Context;
22import android.content.res.TypedArray;
23import android.graphics.Bitmap;
24import android.graphics.BitmapShader;
25import android.graphics.Canvas;
26import android.graphics.Rect;
27import android.graphics.Shader;
28import android.graphics.drawable.Animatable;
29import android.graphics.drawable.AnimationDrawable;
30import android.graphics.drawable.BitmapDrawable;
31import android.graphics.drawable.ClipDrawable;
32import android.graphics.drawable.Drawable;
33import android.graphics.drawable.LayerDrawable;
34import android.graphics.drawable.ShapeDrawable;
35import android.graphics.drawable.StateListDrawable;
36import android.graphics.drawable.shapes.RoundRectShape;
37import android.graphics.drawable.shapes.Shape;
38import android.os.Parcel;
39import android.os.Parcelable;
40import android.os.SystemClock;
41import android.util.AttributeSet;
42import android.view.Gravity;
43import android.view.RemotableViewMethod;
44import android.view.View;
45import android.view.ViewDebug;
46import android.view.accessibility.AccessibilityEvent;
47import android.view.accessibility.AccessibilityManager;
48import android.view.animation.AlphaAnimation;
49import android.view.animation.Animation;
50import android.view.animation.AnimationUtils;
51import android.view.animation.Interpolator;
52import android.view.animation.LinearInterpolator;
53import android.view.animation.Transformation;
54import 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
189public 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     */
230    public ProgressBar(Context context) {
231        this(context, null);
232    }
233
234    public ProgressBar(Context context, AttributeSet attrs) {
235        this(context, attrs, com.android.internal.R.attr.progressBarStyle);
236    }
237
238    public ProgressBar(Context context, AttributeSet attrs, int defStyle) {
239        this(context, attrs, defStyle, 0);
240    }
241
242    /**
243     * @hide
244     */
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     */
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
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     */
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     */
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")
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
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     */
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     */
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     */
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     */
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     */
541    Drawable getCurrentDrawable() {
542        return mCurrentDrawable;
543    }
544
545    @Override
546    protected boolean verifyDrawable(Drawable who) {
547        return who == mProgressDrawable || who == mIndeterminateDrawable
548                || super.verifyDrawable(who);
549    }
550
551    @Override
552    public void jumpDrawablesToCurrentState() {
553        super.jumpDrawablesToCurrentState();
554        if (mProgressDrawable != null) mProgressDrawable.jumpToCurrentState();
555        if (mIndeterminateDrawable != null) mIndeterminateDrawable.jumpToCurrentState();
556    }
557
558    @Override
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
571        RefreshProgressRunnable(int id, int progress, boolean fromUser) {
572            mId = id;
573            mProgress = progress;
574            mFromUser = fromUser;
575        }
576
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
583        public void setup(int id, int progress, boolean fromUser) {
584            mId = id;
585            mProgress = progress;
586            mFromUser = fromUser;
587        }
588
589    }
590
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
613    void onProgressRefresh(float scale, boolean fromUser) {
614        if (AccessibilityManager.getInstance(mContext).isEnabled()) {
615            scheduleAccessibilityEventSender();
616        }
617    }
618
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
650    public synchronized void setProgress(int progress) {
651        setProgress(progress, false);
652    }
653
654    @android.view.RemotableViewMethod
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
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")
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")
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")
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
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     */
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     */
797    public synchronized final void incrementSecondaryProgressBy(int diff) {
798        setSecondaryProgress(mSecondaryProgress + diff);
799    }
800
801    /**
802     * <p>Start the indeterminate progress animation.</p>
803     */
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     */
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     */
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     */
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     */
867    public Interpolator getInterpolator() {
868        return mInterpolator;
869    }
870
871    @Override
872    @RemotableViewMethod
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
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
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
919    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
920        updateDrawableBounds(w, h);
921    }
922
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
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
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
1014    protected void drawableStateChanged() {
1015        super.drawableStateChanged();
1016        updateDrawableState();
1017    }
1018
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         */
1038        SavedState(Parcelable superState) {
1039            super(superState);
1040        }
1041
1042        /**
1043         * Constructor called from {@link #CREATOR}
1044         */
1045        private SavedState(Parcel in) {
1046            super(in);
1047            progress = in.readInt();
1048            secondaryProgress = in.readInt();
1049        }
1050
1051        @Override
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
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
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
1092    protected void onAttachedToWindow() {
1093        super.onAttachedToWindow();
1094        if (mIndeterminate) {
1095            startAnimation();
1096        }
1097    }
1098
1099    @Override
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
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     */
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 {
1142        public void run() {
1143            sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_SELECTED);
1144        }
1145    }
1146}
1147