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