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