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