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 (mIndeterminate) {
    482             mCurrentDrawable = d;
    483             postInvalidate();
    484         }
    485     }
    486 
    487     /**
    488      * <p>Get the drawable used to draw the progress bar in
    489      * progress mode.</p>
    490      *
    491      * @return a {@link android.graphics.drawable.Drawable} instance
    492      *
    493      * @see #setProgressDrawable(android.graphics.drawable.Drawable)
    494      * @see #setIndeterminate(boolean)
    495      */
    496     public Drawable getProgressDrawable() {
    497         return mProgressDrawable;
    498     }
    499 
    500     /**
    501      * <p>Define the drawable used to draw the progress bar in
    502      * progress mode.</p>
    503      *
    504      * @param d the new drawable
    505      *
    506      * @see #getProgressDrawable()
    507      * @see #setIndeterminate(boolean)
    508      */
    509     public void setProgressDrawable(Drawable d) {
    510         boolean needUpdate;
    511         if (mProgressDrawable != null && d != mProgressDrawable) {
    512             mProgressDrawable.setCallback(null);
    513             needUpdate = true;
    514         } else {
    515             needUpdate = false;
    516         }
    517 
    518         if (d != null) {
    519             d.setCallback(this);
    520 
    521             // Make sure the ProgressBar is always tall enough
    522             int drawableHeight = d.getMinimumHeight();
    523             if (mMaxHeight < drawableHeight) {
    524                 mMaxHeight = drawableHeight;
    525                 requestLayout();
    526             }
    527         }
    528         mProgressDrawable = d;
    529         if (!mIndeterminate) {
    530             mCurrentDrawable = d;
    531             postInvalidate();
    532         }
    533 
    534         if (needUpdate) {
    535             updateDrawableBounds(getWidth(), getHeight());
    536             updateDrawableState();
    537             doRefreshProgress(R.id.progress, mProgress, false, false);
    538             doRefreshProgress(R.id.secondaryProgress, mSecondaryProgress, false, false);
    539         }
    540     }
    541 
    542     /**
    543      * @return The drawable currently used to draw the progress bar
    544      */
    545     Drawable getCurrentDrawable() {
    546         return mCurrentDrawable;
    547     }
    548 
    549     @Override
    550     protected boolean verifyDrawable(Drawable who) {
    551         return who == mProgressDrawable || who == mIndeterminateDrawable
    552                 || super.verifyDrawable(who);
    553     }
    554 
    555     @Override
    556     public void jumpDrawablesToCurrentState() {
    557         super.jumpDrawablesToCurrentState();
    558         if (mProgressDrawable != null) mProgressDrawable.jumpToCurrentState();
    559         if (mIndeterminateDrawable != null) mIndeterminateDrawable.jumpToCurrentState();
    560     }
    561 
    562     @Override
    563     public void postInvalidate() {
    564         if (!mNoInvalidate) {
    565             super.postInvalidate();
    566         }
    567     }
    568 
    569     private class RefreshProgressRunnable implements Runnable {
    570         public void run() {
    571             synchronized (ProgressBar.this) {
    572                 final int count = mRefreshData.size();
    573                 for (int i = 0; i < count; i++) {
    574                     final RefreshData rd = mRefreshData.get(i);
    575                     doRefreshProgress(rd.id, rd.progress, rd.fromUser, true);
    576                     rd.recycle();
    577                 }
    578                 mRefreshData.clear();
    579                 mRefreshIsPosted = false;
    580             }
    581         }
    582     }
    583 
    584     private static class RefreshData implements Poolable<RefreshData> {
    585         public int id;
    586         public int progress;
    587         public boolean fromUser;
    588 
    589         private RefreshData mNext;
    590         private boolean mIsPooled;
    591 
    592         private static final int POOL_MAX = 24;
    593         private static final Pool<RefreshData> sPool = Pools.synchronizedPool(
    594                 Pools.finitePool(new PoolableManager<RefreshData>() {
    595                     @Override
    596                     public RefreshData newInstance() {
    597                         return new RefreshData();
    598                     }
    599 
    600                     @Override
    601                     public void onAcquired(RefreshData element) {
    602                     }
    603 
    604                     @Override
    605                     public void onReleased(RefreshData element) {
    606                     }
    607                 }, POOL_MAX));
    608 
    609         public static RefreshData obtain(int id, int progress, boolean fromUser) {
    610             RefreshData rd = sPool.acquire();
    611             rd.id = id;
    612             rd.progress = progress;
    613             rd.fromUser = fromUser;
    614             return rd;
    615         }
    616 
    617         public void recycle() {
    618             sPool.release(this);
    619         }
    620 
    621         @Override
    622         public void setNextPoolable(RefreshData element) {
    623             mNext = element;
    624         }
    625 
    626         @Override
    627         public RefreshData getNextPoolable() {
    628             return mNext;
    629         }
    630 
    631         @Override
    632         public boolean isPooled() {
    633             return mIsPooled;
    634         }
    635 
    636         @Override
    637         public void setPooled(boolean isPooled) {
    638             mIsPooled = isPooled;
    639         }
    640     }
    641 
    642     private synchronized void doRefreshProgress(int id, int progress, boolean fromUser,
    643             boolean callBackToApp) {
    644         float scale = mMax > 0 ? (float) progress / (float) mMax : 0;
    645         final Drawable d = mCurrentDrawable;
    646         if (d != null) {
    647             Drawable progressDrawable = null;
    648 
    649             if (d instanceof LayerDrawable) {
    650                 progressDrawable = ((LayerDrawable) d).findDrawableByLayerId(id);
    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     /**
    979      * @hide
    980      */
    981     @Override
    982     public int getResolvedLayoutDirection(Drawable who) {
    983         return (who == mProgressDrawable || who == mIndeterminateDrawable) ?
    984             getResolvedLayoutDirection() : super.getResolvedLayoutDirection(who);
    985     }
    986 
    987     @Override
    988     protected void onSizeChanged(int w, int h, int oldw, int oldh) {
    989         updateDrawableBounds(w, h);
    990     }
    991 
    992     private void updateDrawableBounds(int w, int h) {
    993         // onDraw will translate the canvas so we draw starting at 0,0
    994         int right = w - mPaddingRight - mPaddingLeft;
    995         int bottom = h - mPaddingBottom - mPaddingTop;
    996         int top = 0;
    997         int left = 0;
    998 
    999         if (mIndeterminateDrawable != null) {
   1000             // Aspect ratio logic does not apply to AnimationDrawables
   1001             if (mOnlyIndeterminate && !(mIndeterminateDrawable instanceof AnimationDrawable)) {
   1002                 // Maintain aspect ratio. Certain kinds of animated drawables
   1003                 // get very confused otherwise.
   1004                 final int intrinsicWidth = mIndeterminateDrawable.getIntrinsicWidth();
   1005                 final int intrinsicHeight = mIndeterminateDrawable.getIntrinsicHeight();
   1006                 final float intrinsicAspect = (float) intrinsicWidth / intrinsicHeight;
   1007                 final float boundAspect = (float) w / h;
   1008                 if (intrinsicAspect != boundAspect) {
   1009                     if (boundAspect > intrinsicAspect) {
   1010                         // New width is larger. Make it smaller to match height.
   1011                         final int width = (int) (h * intrinsicAspect);
   1012                         left = (w - width) / 2;
   1013                         right = left + width;
   1014                     } else {
   1015                         // New height is larger. Make it smaller to match width.
   1016                         final int height = (int) (w * (1 / intrinsicAspect));
   1017                         top = (h - height) / 2;
   1018                         bottom = top + height;
   1019                     }
   1020                 }
   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             canvas.translate(mPaddingLeft, mPaddingTop);
   1040             long time = getDrawingTime();
   1041             if (mHasAnimation) {
   1042                 mAnimation.getTransformation(time, mTransformation);
   1043                 float scale = mTransformation.getAlpha();
   1044                 try {
   1045                     mInDrawing = true;
   1046                     d.setLevel((int) (scale * MAX_LEVEL));
   1047                 } finally {
   1048                     mInDrawing = false;
   1049                 }
   1050                 postInvalidateOnAnimation();
   1051             }
   1052             d.draw(canvas);
   1053             canvas.restore();
   1054             if (mShouldStartAnimationDrawable && d instanceof Animatable) {
   1055                 ((Animatable) d).start();
   1056                 mShouldStartAnimationDrawable = false;
   1057             }
   1058         }
   1059     }
   1060 
   1061     @Override
   1062     protected synchronized void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
   1063         Drawable d = mCurrentDrawable;
   1064 
   1065         int dw = 0;
   1066         int dh = 0;
   1067         if (d != null) {
   1068             dw = Math.max(mMinWidth, Math.min(mMaxWidth, d.getIntrinsicWidth()));
   1069             dh = Math.max(mMinHeight, Math.min(mMaxHeight, d.getIntrinsicHeight()));
   1070         }
   1071         updateDrawableState();
   1072         dw += mPaddingLeft + mPaddingRight;
   1073         dh += mPaddingTop + mPaddingBottom;
   1074 
   1075         setMeasuredDimension(resolveSizeAndState(dw, widthMeasureSpec, 0),
   1076                 resolveSizeAndState(dh, heightMeasureSpec, 0));
   1077     }
   1078 
   1079     @Override
   1080     protected void drawableStateChanged() {
   1081         super.drawableStateChanged();
   1082         updateDrawableState();
   1083     }
   1084 
   1085     private void updateDrawableState() {
   1086         int[] state = getDrawableState();
   1087 
   1088         if (mProgressDrawable != null && mProgressDrawable.isStateful()) {
   1089             mProgressDrawable.setState(state);
   1090         }
   1091 
   1092         if (mIndeterminateDrawable != null && mIndeterminateDrawable.isStateful()) {
   1093             mIndeterminateDrawable.setState(state);
   1094         }
   1095     }
   1096 
   1097     static class SavedState extends BaseSavedState {
   1098         int progress;
   1099         int secondaryProgress;
   1100 
   1101         /**
   1102          * Constructor called from {@link ProgressBar#onSaveInstanceState()}
   1103          */
   1104         SavedState(Parcelable superState) {
   1105             super(superState);
   1106         }
   1107 
   1108         /**
   1109          * Constructor called from {@link #CREATOR}
   1110          */
   1111         private SavedState(Parcel in) {
   1112             super(in);
   1113             progress = in.readInt();
   1114             secondaryProgress = in.readInt();
   1115         }
   1116 
   1117         @Override
   1118         public void writeToParcel(Parcel out, int flags) {
   1119             super.writeToParcel(out, flags);
   1120             out.writeInt(progress);
   1121             out.writeInt(secondaryProgress);
   1122         }
   1123 
   1124         public static final Parcelable.Creator<SavedState> CREATOR
   1125                 = new Parcelable.Creator<SavedState>() {
   1126             public SavedState createFromParcel(Parcel in) {
   1127                 return new SavedState(in);
   1128             }
   1129 
   1130             public SavedState[] newArray(int size) {
   1131                 return new SavedState[size];
   1132             }
   1133         };
   1134     }
   1135 
   1136     @Override
   1137     public Parcelable onSaveInstanceState() {
   1138         // Force our ancestor class to save its state
   1139         Parcelable superState = super.onSaveInstanceState();
   1140         SavedState ss = new SavedState(superState);
   1141 
   1142         ss.progress = mProgress;
   1143         ss.secondaryProgress = mSecondaryProgress;
   1144 
   1145         return ss;
   1146     }
   1147 
   1148     @Override
   1149     public void onRestoreInstanceState(Parcelable state) {
   1150         SavedState ss = (SavedState) state;
   1151         super.onRestoreInstanceState(ss.getSuperState());
   1152 
   1153         setProgress(ss.progress);
   1154         setSecondaryProgress(ss.secondaryProgress);
   1155     }
   1156 
   1157     @Override
   1158     protected void onAttachedToWindow() {
   1159         super.onAttachedToWindow();
   1160         if (mIndeterminate) {
   1161             startAnimation();
   1162         }
   1163         if (mRefreshData != null) {
   1164             synchronized (this) {
   1165                 final int count = mRefreshData.size();
   1166                 for (int i = 0; i < count; i++) {
   1167                     final RefreshData rd = mRefreshData.get(i);
   1168                     doRefreshProgress(rd.id, rd.progress, rd.fromUser, true);
   1169                     rd.recycle();
   1170                 }
   1171                 mRefreshData.clear();
   1172             }
   1173         }
   1174         mAttached = true;
   1175     }
   1176 
   1177     @Override
   1178     protected void onDetachedFromWindow() {
   1179         if (mIndeterminate) {
   1180             stopAnimation();
   1181         }
   1182         if (mRefreshProgressRunnable != null) {
   1183             removeCallbacks(mRefreshProgressRunnable);
   1184         }
   1185         if (mRefreshProgressRunnable != null && mRefreshIsPosted) {
   1186             removeCallbacks(mRefreshProgressRunnable);
   1187         }
   1188         if (mAccessibilityEventSender != null) {
   1189             removeCallbacks(mAccessibilityEventSender);
   1190         }
   1191         // This should come after stopAnimation(), otherwise an invalidate message remains in the
   1192         // queue, which can prevent the entire view hierarchy from being GC'ed during a rotation
   1193         super.onDetachedFromWindow();
   1194         mAttached = false;
   1195     }
   1196 
   1197     @Override
   1198     public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
   1199         super.onInitializeAccessibilityEvent(event);
   1200         event.setClassName(ProgressBar.class.getName());
   1201         event.setItemCount(mMax);
   1202         event.setCurrentItemIndex(mProgress);
   1203     }
   1204 
   1205     @Override
   1206     public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
   1207         super.onInitializeAccessibilityNodeInfo(info);
   1208         info.setClassName(ProgressBar.class.getName());
   1209     }
   1210 
   1211     /**
   1212      * Schedule a command for sending an accessibility event.
   1213      * </br>
   1214      * Note: A command is used to ensure that accessibility events
   1215      *       are sent at most one in a given time frame to save
   1216      *       system resources while the progress changes quickly.
   1217      */
   1218     private void scheduleAccessibilityEventSender() {
   1219         if (mAccessibilityEventSender == null) {
   1220             mAccessibilityEventSender = new AccessibilityEventSender();
   1221         } else {
   1222             removeCallbacks(mAccessibilityEventSender);
   1223         }
   1224         postDelayed(mAccessibilityEventSender, TIMEOUT_SEND_ACCESSIBILITY_EVENT);
   1225     }
   1226 
   1227     /**
   1228      * Command for sending an accessibility event.
   1229      */
   1230     private class AccessibilityEventSender implements Runnable {
   1231         public void run() {
   1232             sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_SELECTED);
   1233         }
   1234     }
   1235 }
   1236