content: /*
   * 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.Build;
  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 &lt; 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 &lt;ProgressBar&gt;} 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>
   * &lt;ProgressBar
   *     style="@android:style/Widget.ProgressBar.Horizontal"
   *     ... /&gt;</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&mdash;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&mdash;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>
   * &lt;LinearLayout
   *     android:orientation="horizontal"
   *     ... &gt;
   *     &lt;ProgressBar
   *         android:layout_width="wrap_content"
   *         android:layout_height="wrap_content"
   *         style="@android:style/Widget.ProgressBar.Small"
   *         android:layout_marginRight="5dp" /&gt;
   *     &lt;TextView
   *         android:layout_width="wrap_content"
   *         android:layout_height="wrap_content"
   *         android:text="@string/loading" /&gt;
   * &lt;/LinearLayout&gt;</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 boolean IS_HONEYCOMB = Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB;
      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 int mIndeterminateRealLeft;
      private int mIndeterminateRealTop;
      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[] { 5, 5, 5, 5, 5, 5, 5, 5 };
          return new RoundRectShape(roundedCorners, null, null);
      }
  
      /**
       * 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, false, false);
              doRefreshProgress(android.R.id.secondaryProgress, mSecondaryProgress, false, false);
          }
      }
  
      /**
       * @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.0f, 1.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(0, 0, right - left, bottom - top);
              mIndeterminateRealLeft = left;
              mIndeterminateRealTop = top;
          }
  
          if (mProgressDrawable != null) {
              mProgressDrawable.setBounds(0, 0, 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() + mIndeterminateRealLeft, getPaddingTop() + mIndeterminateRealTop);
              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();
  
          if (IS_HONEYCOMB) {
              setMeasuredDimension(View.resolveSizeAndState(dw, widthMeasureSpec, 0),
                      View.resolveSizeAndState(dh, heightMeasureSpec, 0));
          } else {
              setMeasuredDimension(View.resolveSize(dw, widthMeasureSpec),
                      View.resolveSize(dh, heightMeasureSpec));
          }
      }
  
      @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);
          }
      }
  }
  