Project: cow
Code Examples
/*
 * Copyright (C) 2006 The Android Open Source Project 
 * 
 * Licensed under the Apache License, Version 2.0 (the "License"); 
 * you may not use this file except in compliance with the License. 
 * You may obtain a copy of the License at 
 * 
 *      http://www.apache.org/licenses/LICENSE-2.0 
 * 
 * Unless required by applicable law or agreed to in writing, software 
 * distributed under the License is distributed on an "AS IS" BASIS, 
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 
 * See the License for the specific language governing permissions and 
 * limitations under the License. 
 */
 
package com.actionbarsherlock.internal.widget; 
 
import android.content.Context; 
import android.content.res.TypedArray; 
import android.graphics.Bitmap; 
import android.graphics.BitmapShader; 
import android.graphics.Canvas; 
import android.graphics.Rect; 
import android.graphics.Shader; 
import android.graphics.drawable.Animatable; 
import android.graphics.drawable.AnimationDrawable; 
import android.graphics.drawable.BitmapDrawable; 
import android.graphics.drawable.ClipDrawable; 
import android.graphics.drawable.Drawable; 
import android.graphics.drawable.LayerDrawable; 
import android.graphics.drawable.ShapeDrawable; 
import android.graphics.drawable.shapes.RoundRectShape; 
import android.graphics.drawable.shapes.Shape; 
import android.os.Parcel; 
import android.os.Parcelable; 
import android.os.SystemClock; 
import android.util.AttributeSet; 
import android.view.Gravity; 
import android.view.View; 
import android.view.ViewDebug; 
import android.view.accessibility.AccessibilityEvent; 
import android.view.accessibility.AccessibilityManager; 
import android.view.animation.AlphaAnimation; 
import android.view.animation.Animation; 
import android.view.animation.AnimationUtils; 
import android.view.animation.Interpolator; 
import android.view.animation.LinearInterpolator; 
import android.view.animation.Transformation; 
import android.widget.RemoteViews.RemoteView; 
 
 
/**
 * <p> 
 * Visual indicator of progress in some operation.  Displays a bar to the user 
 * representing how far the operation has progressed; the application can 
 * change the amount of progress (modifying the length of the bar) as it moves 
 * forward.  There is also a secondary progress displayable on a progress bar 
 * which is useful for displaying intermediate progress, such as the buffer 
 * level during a streaming playback progress bar. 
 * </p> 
 * 
 * <p> 
 * A progress bar can also be made indeterminate. In indeterminate mode, the 
 * progress bar shows a cyclic animation without an indication of progress. This mode is used by 
 * applications when the length of the task is unknown. The indeterminate progress bar can be either 
 * a spinning wheel or a horizontal bar. 
 * </p> 
 * 
 * <p>The following code example shows how a progress bar can be used from 
 * a worker thread to update the user interface to notify the user of progress: 
 * </p> 
 * 
 * <pre> 
 * public class MyActivity extends Activity { 
 *     private static final int PROGRESS = 0x1; 
 * 
 *     private ProgressBar mProgress; 
 *     private int mProgressStatus = 0; 
 * 
 *     private Handler mHandler = new Handler(); 
 * 
 *     protected void onCreate(Bundle icicle) { 
 *         super.onCreate(icicle); 
 * 
 *         setContentView(R.layout.progressbar_activity); 
 * 
 *         mProgress = (ProgressBar) findViewById(R.id.progress_bar); 
 * 
 *         // Start lengthy operation in a background thread 
 *         new Thread(new Runnable() { 
 *             public void run() { 
 *                 while (mProgressStatus < 100) { 
 *                     mProgressStatus = doWork(); 
 * 
 *                     // Update the progress bar 
 *                     mHandler.post(new Runnable() { 
 *                         public void run() { 
 *                             mProgress.setProgress(mProgressStatus); 
 *                         } 
 *                     }); 
 *                 } 
 *             } 
 *         }).start(); 
 *     } 
 * }</pre> 
 * 
 * <p>To add a progress bar to a layout file, you can use the {@code <ProgressBar>} element. 
 * By default, the progress bar is a spinning wheel (an indeterminate indicator). To change to a 
 * horizontal progress bar, apply the {@link android.R.style#Widget_ProgressBar_Horizontal 
 * Widget.ProgressBar.Horizontal} style, like so:</p> 
 * 
 * <pre> 
 * <ProgressBar 
 *     style="@android:style/Widget.ProgressBar.Horizontal" 
 *     ... /></pre> 
 * 
 * <p>If you will use the progress bar to show real progress, you must use the horizontal bar. You 
 * can then increment the  progress with {@link #incrementProgressBy incrementProgressBy()} or 
 * {@link #setProgress setProgress()}. By default, the progress bar is full when it reaches 100. If 
 * necessary, you can adjust the maximum value (the value for a full bar) using the {@link 
 * android.R.styleable#ProgressBar_max android:max} attribute. Other attributes available are listed 
 * below.</p> 
 * 
 * <p>Another common style to apply to the progress bar is {@link 
 * android.R.style#Widget_ProgressBar_Small Widget.ProgressBar.Small}, which shows a smaller 
 * version of the spinning wheel—useful when waiting for content to load. 
 * For example, you can insert this kind of progress bar into your default layout for 
 * a view that will be populated by some content fetched from the Internet—the spinning wheel 
 * appears immediately and when your application receives the content, it replaces the progress bar 
 * with the loaded content. For example:</p> 
 * 
 * <pre> 
 * <LinearLayout 
 *     android:orientation="horizontal" 
 *     ... > 
 *     <ProgressBar 
 *         android:layout_width="wrap_content" 
 *         android:layout_height="wrap_content" 
 *         style="@android:style/Widget.ProgressBar.Small" 
 *         android:layout_marginRight="5dp" /> 
 *     <TextView 
 *         android:layout_width="wrap_content" 
 *         android:layout_height="wrap_content" 
 *         android:text="@string/loading" /> 
 * </LinearLayout></pre> 
 * 
 * <p>Other progress bar styles provided by the system include:</p> 
 * <ul> 
 * <li>{@link android.R.style#Widget_ProgressBar_Horizontal Widget.ProgressBar.Horizontal}</li> 
 * <li>{@link android.R.style#Widget_ProgressBar_Small Widget.ProgressBar.Small}</li> 
 * <li>{@link android.R.style#Widget_ProgressBar_Large Widget.ProgressBar.Large}</li> 
 * <li>{@link android.R.style#Widget_ProgressBar_Inverse Widget.ProgressBar.Inverse}</li> 
 * <li>{@link android.R.style#Widget_ProgressBar_Small_Inverse 
 * Widget.ProgressBar.Small.Inverse}</li> 
 * <li>{@link android.R.style#Widget_ProgressBar_Large_Inverse 
 * Widget.ProgressBar.Large.Inverse}</li> 
 * </ul> 
 * <p>The "inverse" styles provide an inverse color scheme for the spinner, which may be necessary 
 * if your application uses a light colored theme (a white background).</p> 
 * 
 * <p><strong>XML attributes</b></strong> 
 * <p> 
 * See {@link android.R.styleable#ProgressBar ProgressBar Attributes}, 
 * {@link android.R.styleable#View View Attributes} 
 * </p> 
 * 
 * @attr ref android.R.styleable#ProgressBar_animationResolution 
 * @attr ref android.R.styleable#ProgressBar_indeterminate 
 * @attr ref android.R.styleable#ProgressBar_indeterminateBehavior 
 * @attr ref android.R.styleable#ProgressBar_indeterminateDrawable 
 * @attr ref android.R.styleable#ProgressBar_indeterminateDuration 
 * @attr ref android.R.styleable#ProgressBar_indeterminateOnly 
 * @attr ref android.R.styleable#ProgressBar_interpolator 
 * @attr ref android.R.styleable#ProgressBar_max 
 * @attr ref android.R.styleable#ProgressBar_maxHeight 
 * @attr ref android.R.styleable#ProgressBar_maxWidth 
 * @attr ref android.R.styleable#ProgressBar_minHeight 
 * @attr ref android.R.styleable#ProgressBar_minWidth 
 * @attr ref android.R.styleable#ProgressBar_progress 
 * @attr ref android.R.styleable#ProgressBar_progressDrawable 
 * @attr ref android.R.styleable#ProgressBar_secondaryProgress 
 */
 
@RemoteView 
public class IcsProgressBar extends View { 
    private static final int MAX_LEVEL = 10000
    private static final int ANIMATION_RESOLUTION = 200
    private static final int TIMEOUT_SEND_ACCESSIBILITY_EVENT = 200
 
    private static final int[] ProgressBar = new int[] { 
        android.R.attr.maxWidth, 
        android.R.attr.maxHeight, 
        android.R.attr.max, 
        android.R.attr.progress, 
        android.R.attr.secondaryProgress, 
        android.R.attr.indeterminate, 
        android.R.attr.indeterminateOnly, 
        android.R.attr.indeterminateDrawable, 
        android.R.attr.progressDrawable, 
        android.R.attr.indeterminateDuration, 
        android.R.attr.indeterminateBehavior, 
        android.R.attr.minWidth, 
        android.R.attr.minHeight, 
        android.R.attr.interpolator, 
        android.R.attr.animationResolution, 
    }; 
    private static final int ProgressBar_maxWidth = 0
    private static final int ProgressBar_maxHeight = 1
    private static final int ProgressBar_max = 2
    private static final int ProgressBar_progress = 3
    private static final int ProgressBar_secondaryProgress = 4
    private static final int ProgressBar_indeterminate = 5
    private static final int ProgressBar_indeterminateOnly = 6
    private static final int ProgressBar_indeterminateDrawable = 7
    private static final int ProgressBar_progressDrawable = 8
    private static final int ProgressBar_indeterminateDuration = 9
    private static final int ProgressBar_indeterminateBehavior = 10
    private static final int ProgressBar_minWidth = 11
    private static final int ProgressBar_minHeight = 12
    private static final int ProgressBar_interpolator = 13
    private static final int ProgressBar_animationResolution = 14
 
    int mMinWidth; 
    int mMaxWidth; 
    int mMinHeight; 
    int mMaxHeight; 
 
    private int mProgress; 
    private int mSecondaryProgress; 
    private int mMax; 
 
    private int mBehavior; 
    private int mDuration; 
    private boolean mIndeterminate; 
    private boolean mOnlyIndeterminate; 
    private Transformation mTransformation; 
    private AlphaAnimation mAnimation; 
    private Drawable mIndeterminateDrawable; 
    private Drawable mProgressDrawable; 
    private Drawable mCurrentDrawable; 
    Bitmap mSampleTile; 
    private boolean mNoInvalidate; 
    private Interpolator mInterpolator; 
    private RefreshProgressRunnable mRefreshProgressRunnable; 
    private long mUiThreadId; 
    private boolean mShouldStartAnimationDrawable; 
    private long mLastDrawTime; 
 
    private boolean mInDrawing; 
 
    private int mAnimationResolution; 
 
    private AccessibilityManager mAccessibilityManager; 
    private AccessibilityEventSender mAccessibilityEventSender; 
 
    /**
     * Create a new progress bar with range 0...100 and initial progress of 0. 
     * @param context the application environment 
     */
 
    public IcsProgressBar(Context context) { 
        this(context, null); 
    } 
 
    public IcsProgressBar(Context context, AttributeSet attrs) { 
        this(context, attrs, android.R.attr.progressBarStyle); 
    } 
 
    public IcsProgressBar(Context context, AttributeSet attrs, int defStyle) { 
        this(context, attrs, defStyle, 0); 
    } 
 
    /**
     * @hide 
     */
 
    public IcsProgressBar(Context context, AttributeSet attrs, int defStyle, int styleRes) { 
        super(context, attrs, defStyle); 
        mUiThreadId = Thread.currentThread().getId(); 
        initProgressBar(); 
 
        TypedArray a = 
            context.obtainStyledAttributes(attrs, /*R.styleable.*/ProgressBar, defStyle, styleRes); 
 
        mNoInvalidate = true
 
        Drawable drawable = a.getDrawable(/*R.styleable.*/ProgressBar_progressDrawable); 
        if (drawable != null) { 
            drawable = tileify(drawable, false); 
            // Calling this method can set mMaxHeight, make sure the corresponding 
            // XML attribute for mMaxHeight is read after calling this method 
            setProgressDrawable(drawable); 
        } 
 
 
        mDuration = a.getInt(/*R.styleable.*/ProgressBar_indeterminateDuration, mDuration); 
 
        mMinWidth = a.getDimensionPixelSize(/*R.styleable.*/ProgressBar_minWidth, mMinWidth); 
        mMaxWidth = a.getDimensionPixelSize(/*R.styleable.*/ProgressBar_maxWidth, mMaxWidth); 
        mMinHeight = a.getDimensionPixelSize(/*R.styleable.*/ProgressBar_minHeight, mMinHeight); 
        mMaxHeight = a.getDimensionPixelSize(/*R.styleable.*/ProgressBar_maxHeight, mMaxHeight); 
 
        mBehavior = a.getInt(/*R.styleable.*/ProgressBar_indeterminateBehavior, mBehavior); 
 
        final int resID = a.getResourceId( 
                /*com.android.internal.R.styleable.*/ProgressBar_interpolator, 
                android.R.anim.linear_interpolator); // default to linear interpolator 
        if (resID > 0) { 
            setInterpolator(context, resID); 
        } 
 
        setMax(a.getInt(/*R.styleable.*/ProgressBar_max, mMax)); 
 
        setProgress(a.getInt(/*R.styleable.*/ProgressBar_progress, mProgress)); 
 
        setSecondaryProgress( 
                a.getInt(/*R.styleable.*/ProgressBar_secondaryProgress, mSecondaryProgress)); 
 
        drawable = a.getDrawable(/*R.styleable.*/ProgressBar_indeterminateDrawable); 
        if (drawable != null) { 
            drawable = tileifyIndeterminate(drawable); 
            setIndeterminateDrawable(drawable); 
        } 
 
        mOnlyIndeterminate = a.getBoolean( 
                /*R.styleable.*/ProgressBar_indeterminateOnly, mOnlyIndeterminate); 
 
        mNoInvalidate = false
 
        setIndeterminate(mOnlyIndeterminate || a.getBoolean( 
                /*R.styleable.*/ProgressBar_indeterminate, mIndeterminate)); 
 
        mAnimationResolution = a.getInteger(/*R.styleable.*/ProgressBar_animationResolution, 
                ANIMATION_RESOLUTION); 
 
        a.recycle(); 
 
        mAccessibilityManager = (AccessibilityManager)context.getSystemService(Context.ACCESSIBILITY_SERVICE); 
    } 
 
    /**
     * Converts a drawable to a tiled version of itself. It will recursively 
     * traverse layer and state list drawables. 
     */
 
    private Drawable tileify(Drawable drawable, boolean clip) { 
 
        if (drawable instanceof LayerDrawable) { 
            LayerDrawable background = (LayerDrawable) drawable; 
            final int N = background.getNumberOfLayers(); 
            Drawable[] outDrawables = new Drawable[N]; 
 
            for (int i = 0; i < N; i++) { 
                int id = background.getId(i); 
                outDrawables[i] = tileify(background.getDrawable(i), 
                        (id == android.R.id.progress || id == android.R.id.secondaryProgress)); 
            } 
 
            LayerDrawable newBg = new LayerDrawable(outDrawables); 
 
            for (int i = 0; i < N; i++) { 
                newBg.setId(i, background.getId(i)); 
            } 
 
            return newBg; 
 
        }/* else if (drawable instanceof StateListDrawable) {
            StateListDrawable in = (StateListDrawable) drawable; 
            StateListDrawable out = new StateListDrawable(); 
            int numStates = in.getStateCount(); 
            for (int i = 0; i < numStates; i++) { 
                out.addState(in.getStateSet(i), tileify(in.getStateDrawable(i), clip)); 
            } 
            return out; 
 
        }*/
 else if (drawable instanceof BitmapDrawable) { 
            final Bitmap tileBitmap = ((BitmapDrawable) drawable).getBitmap(); 
            if (mSampleTile == null) { 
                mSampleTile = tileBitmap; 
            } 
 
            final ShapeDrawable shapeDrawable = new ShapeDrawable(getDrawableShape()); 
 
            final BitmapShader bitmapShader = new BitmapShader(tileBitmap, 
                    Shader.TileMode.REPEAT, Shader.TileMode.CLAMP); 
            shapeDrawable.getPaint().setShader(bitmapShader); 
 
            return (clip) ? new ClipDrawable(shapeDrawable, Gravity.LEFT, 
                    ClipDrawable.HORIZONTAL) : shapeDrawable; 
        } 
 
        return drawable; 
    } 
 
    Shape getDrawableShape() { 
        final float[] roundedCorners = new float[] { 55555555 }; 
        return new RoundRectShape(roundedCorners, nullnull); 
    } 
 
    /**
     * Convert a AnimationDrawable for use as a barberpole animation. 
     * Each frame of the animation is wrapped in a ClipDrawable and 
     * given a tiling BitmapShader. 
     */
 
    private Drawable tileifyIndeterminate(Drawable drawable) { 
        if (drawable instanceof AnimationDrawable) { 
            AnimationDrawable background = (AnimationDrawable) drawable; 
            final int N = background.getNumberOfFrames(); 
            AnimationDrawable newBg = new AnimationDrawable(); 
            newBg.setOneShot(background.isOneShot()); 
 
            for (int i = 0; i < N; i++) { 
                Drawable frame = tileify(background.getFrame(i), true); 
                frame.setLevel(10000); 
                newBg.addFrame(frame, background.getDuration(i)); 
            } 
            newBg.setLevel(10000); 
            drawable = newBg; 
        } 
        return drawable; 
    } 
 
    /**
     * <p> 
     * Initialize the progress bar's default values: 
     * </p> 
     * <ul> 
     * <li>progress = 0</li> 
     * <li>max = 100</li> 
     * <li>animation duration = 4000 ms</li> 
     * <li>indeterminate = false</li> 
     * <li>behavior = repeat</li> 
     * </ul> 
     */
 
    private void initProgressBar() { 
        mMax = 100
        mProgress = 0
        mSecondaryProgress = 0
        mIndeterminate = false
        mOnlyIndeterminate = false
        mDuration = 4000
        mBehavior = AlphaAnimation.RESTART; 
        mMinWidth = 24
        mMaxWidth = 48
        mMinHeight = 24
        mMaxHeight = 48
    } 
 
    /**
     * <p>Indicate whether this progress bar is in indeterminate mode.</p> 
     * 
     * @return true if the progress bar is in indeterminate mode 
     */
 
    @ViewDebug.ExportedProperty(category = "progress"
    public synchronized boolean isIndeterminate() { 
        return mIndeterminate; 
    } 
 
    /**
     * <p>Change the indeterminate mode for this progress bar. In indeterminate 
     * mode, the progress is ignored and the progress bar shows an infinite 
     * animation instead.</p> 
     * 
     * If this progress bar's style only supports indeterminate mode (such as the circular 
     * progress bars), then this will be ignored. 
     * 
     * @param indeterminate true to enable the indeterminate mode 
     */
 
    public synchronized void setIndeterminate(boolean indeterminate) { 
        if ((!mOnlyIndeterminate || !mIndeterminate) && indeterminate != mIndeterminate) { 
            mIndeterminate = indeterminate; 
 
            if (indeterminate) { 
                // swap between indeterminate and regular backgrounds 
                mCurrentDrawable = mIndeterminateDrawable; 
                startAnimation(); 
            } else { 
                mCurrentDrawable = mProgressDrawable; 
                stopAnimation(); 
            } 
        } 
    } 
 
    /**
     * <p>Get the drawable used to draw the progress bar in 
     * indeterminate mode.</p> 
     * 
     * @return a {@link android.graphics.drawable.Drawable} instance 
     * 
     * @see #setIndeterminateDrawable(android.graphics.drawable.Drawable) 
     * @see #setIndeterminate(boolean) 
     */
 
    public Drawable getIndeterminateDrawable() { 
        return mIndeterminateDrawable; 
    } 
 
    /**
     * <p>Define the drawable used to draw the progress bar in 
     * indeterminate mode.</p> 
     * 
     * @param d the new drawable 
     * 
     * @see #getIndeterminateDrawable() 
     * @see #setIndeterminate(boolean) 
     */
 
    public void setIndeterminateDrawable(Drawable d) { 
        if (d != null) { 
            d.setCallback(this); 
        } 
        mIndeterminateDrawable = d; 
        if (mIndeterminate) { 
            mCurrentDrawable = d; 
            postInvalidate(); 
        } 
    } 
 
    /**
     * <p>Get the drawable used to draw the progress bar in 
     * progress mode.</p> 
     * 
     * @return a {@link android.graphics.drawable.Drawable} instance 
     * 
     * @see #setProgressDrawable(android.graphics.drawable.Drawable) 
     * @see #setIndeterminate(boolean) 
     */
 
    public Drawable getProgressDrawable() { 
        return mProgressDrawable; 
    } 
 
    /**
     * <p>Define the drawable used to draw the progress bar in 
     * progress mode.</p> 
     * 
     * @param d the new drawable 
     * 
     * @see #getProgressDrawable() 
     * @see #setIndeterminate(boolean) 
     */
 
    public void setProgressDrawable(Drawable d) { 
        boolean needUpdate; 
        if (mProgressDrawable != null && d != mProgressDrawable) { 
            mProgressDrawable.setCallback(null); 
            needUpdate = true
        } else { 
            needUpdate = false
        } 
 
        if (d != null) { 
            d.setCallback(this); 
 
            // Make sure the ProgressBar is always tall enough 
            int drawableHeight = d.getMinimumHeight(); 
            if (mMaxHeight < drawableHeight) { 
                mMaxHeight = drawableHeight; 
                requestLayout(); 
            } 
        } 
        mProgressDrawable = d; 
        if (!mIndeterminate) { 
            mCurrentDrawable = d; 
            postInvalidate(); 
        } 
 
        if (needUpdate) { 
            updateDrawableBounds(getWidth(), getHeight()); 
            updateDrawableState(); 
            doRefreshProgress(android.R.id.progress, mProgress, falsefalse); 
            doRefreshProgress(android.R.id.secondaryProgress, mSecondaryProgress, falsefalse); 
        } 
    } 
 
    /**
     * @return The drawable currently used to draw the progress bar 
     */
 
    Drawable getCurrentDrawable() { 
        return mCurrentDrawable; 
    } 
 
    @Override 
    protected boolean verifyDrawable(Drawable who) { 
        return who == mProgressDrawable || who == mIndeterminateDrawable 
                || super.verifyDrawable(who); 
    } 
 
    @Override 
    public void jumpDrawablesToCurrentState() { 
        super.jumpDrawablesToCurrentState(); 
        if (mProgressDrawable != null) mProgressDrawable.jumpToCurrentState(); 
        if (mIndeterminateDrawable != null) mIndeterminateDrawable.jumpToCurrentState(); 
    } 
 
    @Override 
    public void postInvalidate() { 
        if (!mNoInvalidate) { 
            super.postInvalidate(); 
        } 
    } 
 
    private class RefreshProgressRunnable implements Runnable { 
 
        private int mId; 
        private int mProgress; 
        private boolean mFromUser; 
 
        RefreshProgressRunnable(int id, int progress, boolean fromUser) { 
            mId = id; 
            mProgress = progress; 
            mFromUser = fromUser; 
        } 
 
        public void run() { 
            doRefreshProgress(mId, mProgress, mFromUser, true); 
            // Put ourselves back in the cache when we are done 
            mRefreshProgressRunnable = this
        } 
 
        public void setup(int id, int progress, boolean fromUser) { 
            mId = id; 
            mProgress = progress; 
            mFromUser = fromUser; 
        } 
 
    } 
 
    private synchronized void doRefreshProgress(int id, int progress, boolean fromUser, 
            boolean callBackToApp) { 
        float scale = mMax > 0 ? (float) progress / (float) mMax : 0
        final Drawable d = mCurrentDrawable; 
        if (d != null) { 
            Drawable progressDrawable = null
 
            if (d instanceof LayerDrawable) { 
                progressDrawable = ((LayerDrawable) d).findDrawableByLayerId(id); 
            } 
 
            final int level = (int) (scale * MAX_LEVEL); 
            (progressDrawable != null ? progressDrawable : d).setLevel(level); 
        } else { 
            invalidate(); 
        } 
 
        if (callBackToApp && id == android.R.id.progress) { 
            onProgressRefresh(scale, fromUser); 
        } 
    } 
 
    void onProgressRefresh(float scale, boolean fromUser) { 
        if (mAccessibilityManager.isEnabled()) { 
            scheduleAccessibilityEventSender(); 
        } 
    } 
 
    private synchronized void refreshProgress(int id, int progress, boolean fromUser) { 
        if (mUiThreadId == Thread.currentThread().getId()) { 
            doRefreshProgress(id, progress, fromUser, true); 
        } else { 
            RefreshProgressRunnable r; 
            if (mRefreshProgressRunnable != null) { 
                // Use cached RefreshProgressRunnable if available 
                r = mRefreshProgressRunnable; 
                // Uncache it 
                mRefreshProgressRunnable = null
                r.setup(id, progress, fromUser); 
            } else { 
                // Make a new one 
                r = new RefreshProgressRunnable(id, progress, fromUser); 
            } 
            post(r); 
        } 
    } 
 
    /**
     * <p>Set the current progress to the specified value. Does not do anything 
     * if the progress bar is in indeterminate mode.</p> 
     * 
     * @param progress the new progress, between 0 and {@link #getMax()} 
     * 
     * @see #setIndeterminate(boolean) 
     * @see #isIndeterminate() 
     * @see #getProgress() 
     * @see #incrementProgressBy(int) 
     */
 
    public synchronized void setProgress(int progress) { 
        setProgress(progress, false); 
    } 
 
    synchronized void setProgress(int progress, boolean fromUser) { 
        if (mIndeterminate) { 
            return
        } 
 
        if (progress < 0) { 
            progress = 0
        } 
 
        if (progress > mMax) { 
            progress = mMax; 
        } 
 
        if (progress != mProgress) { 
            mProgress = progress; 
            refreshProgress(android.R.id.progress, mProgress, fromUser); 
        } 
    } 
 
    /**
     * <p> 
     * Set the current secondary progress to the specified value. Does not do 
     * anything if the progress bar is in indeterminate mode. 
     * </p> 
     * 
     * @param secondaryProgress the new secondary progress, between 0 and {@link #getMax()} 
     * @see #setIndeterminate(boolean) 
     * @see #isIndeterminate() 
     * @see #getSecondaryProgress() 
     * @see #incrementSecondaryProgressBy(int) 
     */
 
    public synchronized void setSecondaryProgress(int secondaryProgress) { 
        if (mIndeterminate) { 
            return
        } 
 
        if (secondaryProgress < 0) { 
            secondaryProgress = 0
        } 
 
        if (secondaryProgress > mMax) { 
            secondaryProgress = mMax; 
        } 
 
        if (secondaryProgress != mSecondaryProgress) { 
            mSecondaryProgress = secondaryProgress; 
            refreshProgress(android.R.id.secondaryProgress, mSecondaryProgress, false); 
        } 
    } 
 
    /**
     * <p>Get the progress bar's current level of progress. Return 0 when the 
     * progress bar is in indeterminate mode.</p> 
     * 
     * @return the current progress, between 0 and {@link #getMax()} 
     * 
     * @see #setIndeterminate(boolean) 
     * @see #isIndeterminate() 
     * @see #setProgress(int) 
     * @see #setMax(int) 
     * @see #getMax() 
     */
 
    @ViewDebug.ExportedProperty(category = "progress"
    public synchronized int getProgress() { 
        return mIndeterminate ? 0 : mProgress; 
    } 
 
    /**
     * <p>Get the progress bar's current level of secondary progress. Return 0 when the 
     * progress bar is in indeterminate mode.</p> 
     * 
     * @return the current secondary progress, between 0 and {@link #getMax()} 
     * 
     * @see #setIndeterminate(boolean) 
     * @see #isIndeterminate() 
     * @see #setSecondaryProgress(int) 
     * @see #setMax(int) 
     * @see #getMax() 
     */
 
    @ViewDebug.ExportedProperty(category = "progress"
    public synchronized int getSecondaryProgress() { 
        return mIndeterminate ? 0 : mSecondaryProgress; 
    } 
 
    /**
     * <p>Return the upper limit of this progress bar's range.</p> 
     * 
     * @return a positive integer 
     * 
     * @see #setMax(int) 
     * @see #getProgress() 
     * @see #getSecondaryProgress() 
     */
 
    @ViewDebug.ExportedProperty(category = "progress"
    public synchronized int getMax() { 
        return mMax; 
    } 
 
    /**
     * <p>Set the range of the progress bar to 0...<tt>max</tt>.</p> 
     * 
     * @param max the upper range of this progress bar 
     * 
     * @see #getMax() 
     * @see #setProgress(int) 
     * @see #setSecondaryProgress(int) 
     */
 
    public synchronized void setMax(int max) { 
        if (max < 0) { 
            max = 0
        } 
        if (max != mMax) { 
            mMax = max; 
            postInvalidate(); 
 
            if (mProgress > max) { 
                mProgress = max; 
            } 
            refreshProgress(android.R.id.progress, mProgress, false); 
        } 
    } 
 
    /**
     * <p>Increase the progress bar's progress by the specified amount.</p> 
     * 
     * @param diff the amount by which the progress must be increased 
     * 
     * @see #setProgress(int) 
     */
 
    public synchronized final void incrementProgressBy(int diff) { 
        setProgress(mProgress + diff); 
    } 
 
    /**
     * <p>Increase the progress bar's secondary progress by the specified amount.</p> 
     * 
     * @param diff the amount by which the secondary progress must be increased 
     * 
     * @see #setSecondaryProgress(int) 
     */
 
    public synchronized final void incrementSecondaryProgressBy(int diff) { 
        setSecondaryProgress(mSecondaryProgress + diff); 
    } 
 
    /**
     * <p>Start the indeterminate progress animation.</p> 
     */
 
    void startAnimation() { 
        if (getVisibility() != VISIBLE) { 
            return
        } 
 
        if (mIndeterminateDrawable instanceof Animatable) { 
            mShouldStartAnimationDrawable = true
            mAnimation = null
        } else { 
            if (mInterpolator == null) { 
                mInterpolator = new LinearInterpolator(); 
            } 
 
            mTransformation = new Transformation(); 
            mAnimation = new AlphaAnimation(0.0f1.0f); 
            mAnimation.setRepeatMode(mBehavior); 
            mAnimation.setRepeatCount(Animation.INFINITE); 
            mAnimation.setDuration(mDuration); 
            mAnimation.setInterpolator(mInterpolator); 
            mAnimation.setStartTime(Animation.START_ON_FIRST_FRAME); 
        } 
        postInvalidate(); 
    } 
 
    /**
     * <p>Stop the indeterminate progress animation.</p> 
     */
 
    void stopAnimation() { 
        mAnimation = null
        mTransformation = null
        if (mIndeterminateDrawable instanceof Animatable) { 
            ((Animatable) mIndeterminateDrawable).stop(); 
            mShouldStartAnimationDrawable = false
        } 
        postInvalidate(); 
    } 
 
    /**
     * Sets the acceleration curve for the indeterminate animation. 
     * The interpolator is loaded as a resource from the specified context. 
     * 
     * @param context The application environment 
     * @param resID The resource identifier of the interpolator to load 
     */
 
    public void setInterpolator(Context context, int resID) { 
        setInterpolator(AnimationUtils.loadInterpolator(context, resID)); 
    } 
 
    /**
     * Sets the acceleration curve for the indeterminate animation. 
     * Defaults to a linear interpolation. 
     * 
     * @param interpolator The interpolator which defines the acceleration curve 
     */
 
    public void setInterpolator(Interpolator interpolator) { 
        mInterpolator = interpolator; 
    } 
 
    /**
     * Gets the acceleration curve type for the indeterminate animation. 
     * 
     * @return the {@link Interpolator} associated to this animation 
     */
 
    public Interpolator getInterpolator() { 
        return mInterpolator; 
    } 
 
    @Override 
    public void setVisibility(int v) { 
        if (getVisibility() != v) { 
            super.setVisibility(v); 
 
            if (mIndeterminate) { 
                // let's be nice with the UI thread 
                if (v == GONE || v == INVISIBLE) { 
                    stopAnimation(); 
                } else { 
                    startAnimation(); 
                } 
            } 
        } 
    } 
 
    @Override 
    protected void onVisibilityChanged(View changedView, int visibility) { 
        super.onVisibilityChanged(changedView, visibility); 
 
        if (mIndeterminate) { 
            // let's be nice with the UI thread 
            if (visibility == GONE || visibility == INVISIBLE) { 
                stopAnimation(); 
            } else { 
                startAnimation(); 
            } 
        } 
    } 
 
    @Override 
    public void invalidateDrawable(Drawable dr) { 
        if (!mInDrawing) { 
            if (verifyDrawable(dr)) { 
                final Rect dirty = dr.getBounds(); 
                final int scrollX = getScrollX() + getPaddingLeft(); 
                final int scrollY = getScrollY() + getPaddingTop(); 
 
                invalidate(dirty.left + scrollX, dirty.top + scrollY, 
                        dirty.right + scrollX, dirty.bottom + scrollY); 
            } else { 
                super.invalidateDrawable(dr); 
            } 
        } 
    } 
 
    /**
     * @hide 
     * 
    @Override 
    public int getResolvedLayoutDirection(Drawable who) { 
        return (who == mProgressDrawable || who == mIndeterminateDrawable) ? 
            getResolvedLayoutDirection() : super.getResolvedLayoutDirection(who); 
    } 
    */
 
 
    @Override 
    protected void onSizeChanged(int w, int h, int oldw, int oldh) { 
        updateDrawableBounds(w, h); 
    } 
 
    private void updateDrawableBounds(int w, int h) { 
        // onDraw will translate the canvas so we draw starting at 0,0 
        int right = w - getPaddingRight() - getPaddingLeft(); 
        int bottom = h - getPaddingBottom() - getPaddingTop(); 
        int top = 0
        int left = 0
 
        if (mIndeterminateDrawable != null) { 
            // Aspect ratio logic does not apply to AnimationDrawables 
            if (mOnlyIndeterminate && !(mIndeterminateDrawable instanceof AnimationDrawable)) { 
                // Maintain aspect ratio. Certain kinds of animated drawables 
                // get very confused otherwise. 
                final int intrinsicWidth = mIndeterminateDrawable.getIntrinsicWidth(); 
                final int intrinsicHeight = mIndeterminateDrawable.getIntrinsicHeight(); 
                final float intrinsicAspect = (float) intrinsicWidth / intrinsicHeight; 
                final float boundAspect = (float) w / h; 
                if (intrinsicAspect != boundAspect) { 
                    if (boundAspect > intrinsicAspect) { 
                        // New width is larger. Make it smaller to match height. 
                        final int width = (int) (h * intrinsicAspect); 
                        left = (w - width) / 2
                        right = left + width; 
                    } else { 
                        // New height is larger. Make it smaller to match width. 
                        final int height = (int) (w * (1 / intrinsicAspect)); 
                        top = (h - height) / 2
                        bottom = top + height; 
                    } 
                } 
            } 
            mIndeterminateDrawable.setBounds(left, top, right, bottom); 
        } 
 
        if (mProgressDrawable != null) { 
            mProgressDrawable.setBounds(00, right, bottom); 
        } 
    } 
 
    @Override 
    protected synchronized void onDraw(Canvas canvas) { 
        super.onDraw(canvas); 
 
        Drawable d = mCurrentDrawable; 
        if (d != null) { 
            // Translate canvas so a indeterminate circular progress bar with padding 
            // rotates properly in its animation 
            canvas.save(); 
            canvas.translate(getPaddingLeft(), getPaddingTop()); 
            long time = getDrawingTime(); 
            if (mAnimation != null) { 
                mAnimation.getTransformation(time, mTransformation); 
                float scale = mTransformation.getAlpha(); 
                try { 
                    mInDrawing = true
                    d.setLevel((int) (scale * MAX_LEVEL)); 
                } finally { 
                    mInDrawing = false
                } 
                if (SystemClock.uptimeMillis() - mLastDrawTime >= mAnimationResolution) { 
                    mLastDrawTime = SystemClock.uptimeMillis(); 
                    postInvalidateDelayed(mAnimationResolution); 
                } 
            } 
            d.draw(canvas); 
            canvas.restore(); 
            if (mShouldStartAnimationDrawable && d instanceof Animatable) { 
                ((Animatable) d).start(); 
                mShouldStartAnimationDrawable = false
            } 
        } 
    } 
 
    @Override 
    protected synchronized void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 
        Drawable d = mCurrentDrawable; 
 
        int dw = 0
        int dh = 0
        if (d != null) { 
            dw = Math.max(mMinWidth, Math.min(mMaxWidth, d.getIntrinsicWidth())); 
            dh = Math.max(mMinHeight, Math.min(mMaxHeight, d.getIntrinsicHeight())); 
        } 
        updateDrawableState(); 
        dw += getPaddingLeft() + getPaddingRight(); 
        dh += getPaddingTop() + getPaddingBottom(); 
 
        setMeasuredDimension(IcsView.resolveSizeAndState(dw, widthMeasureSpec, 0), 
                IcsView.resolveSizeAndState(dh, heightMeasureSpec, 0)); 
    } 
 
    @Override 
    protected void drawableStateChanged() { 
        super.drawableStateChanged(); 
        updateDrawableState(); 
    } 
 
    private void updateDrawableState() { 
        int[] state = getDrawableState(); 
 
        if (mProgressDrawable != null && mProgressDrawable.isStateful()) { 
            mProgressDrawable.setState(state); 
        } 
 
        if (mIndeterminateDrawable != null && mIndeterminateDrawable.isStateful()) { 
            mIndeterminateDrawable.setState(state); 
        } 
    } 
 
    static class SavedState extends BaseSavedState { 
        int progress; 
        int secondaryProgress; 
 
        /**
         * Constructor called from {@link IcsProgressBar#onSaveInstanceState()} 
         */
 
        SavedState(Parcelable superState) { 
            super(superState); 
        } 
 
        /**
         * Constructor called from {@link #CREATOR} 
         */
 
        private SavedState(Parcel in) { 
            super(in); 
            progress = in.readInt(); 
            secondaryProgress = in.readInt(); 
        } 
 
        @Override 
        public void writeToParcel(Parcel out, int flags) { 
            super.writeToParcel(out, flags); 
            out.writeInt(progress); 
            out.writeInt(secondaryProgress); 
        } 
 
        public static final Parcelable.Creator<SavedState> CREATOR 
                = new Parcelable.Creator<SavedState>() { 
            public SavedState createFromParcel(Parcel in) { 
                return new SavedState(in); 
            } 
 
            public SavedState[] newArray(int size) { 
                return new SavedState[size]; 
            } 
        }; 
    } 
 
    @Override 
    public Parcelable onSaveInstanceState() { 
        // Force our ancestor class to save its state 
        Parcelable superState = super.onSaveInstanceState(); 
        SavedState ss = new SavedState(superState); 
 
        ss.progress = mProgress; 
        ss.secondaryProgress = mSecondaryProgress; 
 
        return ss; 
    } 
 
    @Override 
    public void onRestoreInstanceState(Parcelable state) { 
        SavedState ss = (SavedState) state; 
        super.onRestoreInstanceState(ss.getSuperState()); 
 
        setProgress(ss.progress); 
        setSecondaryProgress(ss.secondaryProgress); 
    } 
 
    @Override 
    protected void onAttachedToWindow() { 
        super.onAttachedToWindow(); 
        if (mIndeterminate) { 
            startAnimation(); 
        } 
    } 
 
    @Override 
    protected void onDetachedFromWindow() { 
        if (mIndeterminate) { 
            stopAnimation(); 
        } 
        if(mRefreshProgressRunnable != null) { 
            removeCallbacks(mRefreshProgressRunnable); 
        } 
        if (mAccessibilityEventSender != null) { 
            removeCallbacks(mAccessibilityEventSender); 
        } 
        // This should come after stopAnimation(), otherwise an invalidate message remains in the 
        // queue, which can prevent the entire view hierarchy from being GC'ed during a rotation 
        super.onDetachedFromWindow(); 
    } 
 
    @Override 
    public void onInitializeAccessibilityEvent(AccessibilityEvent event) { 
        super.onInitializeAccessibilityEvent(event); 
        event.setItemCount(mMax); 
        event.setCurrentItemIndex(mProgress); 
    } 
 
    /**
     * Schedule a command for sending an accessibility event. 
     * </br> 
     * Note: A command is used to ensure that accessibility events 
     *       are sent at most one in a given time frame to save 
     *       system resources while the progress changes quickly. 
     */
 
    private void scheduleAccessibilityEventSender() { 
        if (mAccessibilityEventSender == null) { 
            mAccessibilityEventSender = new AccessibilityEventSender(); 
        } else { 
            removeCallbacks(mAccessibilityEventSender); 
        } 
        postDelayed(mAccessibilityEventSender, TIMEOUT_SEND_ACCESSIBILITY_EVENT); 
    } 
 
    /**
     * Command for sending an accessibility event. 
     */
 
    private class AccessibilityEventSender implements Runnable { 
        public void run() { 
            sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_SELECTED); 
        } 
    } 
}