Home | History | Annotate | Download | only in widget
      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 android.widget;
     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      */
    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     /**
    919      * @hide
    920      */
    921     @Override
    922     public int getResolvedLayoutDirection(Drawable who) {
    923         return (who == mProgressDrawable || who == mIndeterminateDrawable) ?
    924             getResolvedLayoutDirection() : super.getResolvedLayoutDirection(who);
    925     }
    926 
    927     @Override
    928     protected void onSizeChanged(int w, int h, int oldw, int oldh) {
    929         updateDrawableBounds(w, h);
    930     }
    931 
    932     private void updateDrawableBounds(int w, int h) {
    933         // onDraw will translate the canvas so we draw starting at 0,0
    934         int right = w - mPaddingRight - mPaddingLeft;
    935         int bottom = h - mPaddingBottom - mPaddingTop;
    936         int top = 0;
    937         int left = 0;
    938 
    939         if (mIndeterminateDrawable != null) {
    940             // Aspect ratio logic does not apply to AnimationDrawables
    941             if (mOnlyIndeterminate && !(mIndeterminateDrawable instanceof AnimationDrawable)) {
    942                 // Maintain aspect ratio. Certain kinds of animated drawables
    943                 // get very confused otherwise.
    944                 final int intrinsicWidth = mIndeterminateDrawable.getIntrinsicWidth();
    945                 final int intrinsicHeight = mIndeterminateDrawable.getIntrinsicHeight();
    946                 final float intrinsicAspect = (float) intrinsicWidth / intrinsicHeight;
    947                 final float boundAspect = (float) w / h;
    948                 if (intrinsicAspect != boundAspect) {
    949                     if (boundAspect > intrinsicAspect) {
    950                         // New width is larger. Make it smaller to match height.
    951                         final int width = (int) (h * intrinsicAspect);
    952                         left = (w - width) / 2;
    953                         right = left + width;
    954                     } else {
    955                         // New height is larger. Make it smaller to match width.
    956                         final int height = (int) (w * (1 / intrinsicAspect));
    957                         top = (h - height) / 2;
    958                         bottom = top + height;
    959                     }
    960                 }
    961             }
    962             mIndeterminateDrawable.setBounds(left, top, right, bottom);
    963         }
    964 
    965         if (mProgressDrawable != null) {
    966             mProgressDrawable.setBounds(0, 0, right, bottom);
    967         }
    968     }
    969 
    970     @Override
    971     protected synchronized void onDraw(Canvas canvas) {
    972         super.onDraw(canvas);
    973 
    974         Drawable d = mCurrentDrawable;
    975         if (d != null) {
    976             // Translate canvas so a indeterminate circular progress bar with padding
    977             // rotates properly in its animation
    978             canvas.save();
    979             canvas.translate(mPaddingLeft, mPaddingTop);
    980             long time = getDrawingTime();
    981             if (mAnimation != null) {
    982                 mAnimation.getTransformation(time, mTransformation);
    983                 float scale = mTransformation.getAlpha();
    984                 try {
    985                     mInDrawing = true;
    986                     d.setLevel((int) (scale * MAX_LEVEL));
    987                 } finally {
    988                     mInDrawing = false;
    989                 }
    990                 if (SystemClock.uptimeMillis() - mLastDrawTime >= mAnimationResolution) {
    991                     mLastDrawTime = SystemClock.uptimeMillis();
    992                     postInvalidateDelayed(mAnimationResolution);
    993                 }
    994             }
    995             d.draw(canvas);
    996             canvas.restore();
    997             if (mShouldStartAnimationDrawable && d instanceof Animatable) {
    998                 ((Animatable) d).start();
    999                 mShouldStartAnimationDrawable = false;
   1000             }
   1001         }
   1002     }
   1003 
   1004     @Override
   1005     protected synchronized void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
   1006         Drawable d = mCurrentDrawable;
   1007 
   1008         int dw = 0;
   1009         int dh = 0;
   1010         if (d != null) {
   1011             dw = Math.max(mMinWidth, Math.min(mMaxWidth, d.getIntrinsicWidth()));
   1012             dh = Math.max(mMinHeight, Math.min(mMaxHeight, d.getIntrinsicHeight()));
   1013         }
   1014         updateDrawableState();
   1015         dw += mPaddingLeft + mPaddingRight;
   1016         dh += mPaddingTop + mPaddingBottom;
   1017 
   1018         setMeasuredDimension(resolveSizeAndState(dw, widthMeasureSpec, 0),
   1019                 resolveSizeAndState(dh, heightMeasureSpec, 0));
   1020     }
   1021 
   1022     @Override
   1023     protected void drawableStateChanged() {
   1024         super.drawableStateChanged();
   1025         updateDrawableState();
   1026     }
   1027 
   1028     private void updateDrawableState() {
   1029         int[] state = getDrawableState();
   1030 
   1031         if (mProgressDrawable != null && mProgressDrawable.isStateful()) {
   1032             mProgressDrawable.setState(state);
   1033         }
   1034 
   1035         if (mIndeterminateDrawable != null && mIndeterminateDrawable.isStateful()) {
   1036             mIndeterminateDrawable.setState(state);
   1037         }
   1038     }
   1039 
   1040     static class SavedState extends BaseSavedState {
   1041         int progress;
   1042         int secondaryProgress;
   1043 
   1044         /**
   1045          * Constructor called from {@link ProgressBar#onSaveInstanceState()}
   1046          */
   1047         SavedState(Parcelable superState) {
   1048             super(superState);
   1049         }
   1050 
   1051         /**
   1052          * Constructor called from {@link #CREATOR}
   1053          */
   1054         private SavedState(Parcel in) {
   1055             super(in);
   1056             progress = in.readInt();
   1057             secondaryProgress = in.readInt();
   1058         }
   1059 
   1060         @Override
   1061         public void writeToParcel(Parcel out, int flags) {
   1062             super.writeToParcel(out, flags);
   1063             out.writeInt(progress);
   1064             out.writeInt(secondaryProgress);
   1065         }
   1066 
   1067         public static final Parcelable.Creator<SavedState> CREATOR
   1068                 = new Parcelable.Creator<SavedState>() {
   1069             public SavedState createFromParcel(Parcel in) {
   1070                 return new SavedState(in);
   1071             }
   1072 
   1073             public SavedState[] newArray(int size) {
   1074                 return new SavedState[size];
   1075             }
   1076         };
   1077     }
   1078 
   1079     @Override
   1080     public Parcelable onSaveInstanceState() {
   1081         // Force our ancestor class to save its state
   1082         Parcelable superState = super.onSaveInstanceState();
   1083         SavedState ss = new SavedState(superState);
   1084 
   1085         ss.progress = mProgress;
   1086         ss.secondaryProgress = mSecondaryProgress;
   1087 
   1088         return ss;
   1089     }
   1090 
   1091     @Override
   1092     public void onRestoreInstanceState(Parcelable state) {
   1093         SavedState ss = (SavedState) state;
   1094         super.onRestoreInstanceState(ss.getSuperState());
   1095 
   1096         setProgress(ss.progress);
   1097         setSecondaryProgress(ss.secondaryProgress);
   1098     }
   1099 
   1100     @Override
   1101     protected void onAttachedToWindow() {
   1102         super.onAttachedToWindow();
   1103         if (mIndeterminate) {
   1104             startAnimation();
   1105         }
   1106     }
   1107 
   1108     @Override
   1109     protected void onDetachedFromWindow() {
   1110         if (mIndeterminate) {
   1111             stopAnimation();
   1112         }
   1113         if(mRefreshProgressRunnable != null) {
   1114             removeCallbacks(mRefreshProgressRunnable);
   1115         }
   1116         if (mAccessibilityEventSender != null) {
   1117             removeCallbacks(mAccessibilityEventSender);
   1118         }
   1119         // This should come after stopAnimation(), otherwise an invalidate message remains in the
   1120         // queue, which can prevent the entire view hierarchy from being GC'ed during a rotation
   1121         super.onDetachedFromWindow();
   1122     }
   1123 
   1124     @Override
   1125     public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
   1126         super.onInitializeAccessibilityEvent(event);
   1127         event.setItemCount(mMax);
   1128         event.setCurrentItemIndex(mProgress);
   1129     }
   1130 
   1131     /**
   1132      * Schedule a command for sending an accessibility event.
   1133      * </br>
   1134      * Note: A command is used to ensure that accessibility events
   1135      *       are sent at most one in a given time frame to save
   1136      *       system resources while the progress changes quickly.
   1137      */
   1138     private void scheduleAccessibilityEventSender() {
   1139         if (mAccessibilityEventSender == null) {
   1140             mAccessibilityEventSender = new AccessibilityEventSender();
   1141         } else {
   1142             removeCallbacks(mAccessibilityEventSender);
   1143         }
   1144         postDelayed(mAccessibilityEventSender, TIMEOUT_SEND_ACCESSIBILITY_EVENT);
   1145     }
   1146 
   1147     /**
   1148      * Command for sending an accessibility event.
   1149      */
   1150     private class AccessibilityEventSender implements Runnable {
   1151         public void run() {
   1152             sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_SELECTED);
   1153         }
   1154     }
   1155 }
   1156