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 android.widget;
  
  import android.animation.ObjectAnimator;
  import android.annotation.InterpolatorRes;
  import android.annotation.NonNull;
  import android.annotation.Nullable;
  import android.annotation.Px;
  import android.annotation.UnsupportedAppUsage;
  import android.content.Context;
  import android.content.res.ColorStateList;
  import android.content.res.TypedArray;
  import android.graphics.BlendMode;
  import android.graphics.Canvas;
  import android.graphics.PorterDuff;
  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.StateListDrawable;
  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.util.AttributeSet;
  import android.util.FloatProperty;
  import android.util.MathUtils;
  import android.util.Pools.SynchronizedPool;
  import android.view.Gravity;
  import android.view.RemotableViewMethod;
  import android.view.View;
  import android.view.ViewDebug;
  import android.view.ViewHierarchyEncoder;
  import android.view.accessibility.AccessibilityEvent;
  import android.view.accessibility.AccessibilityManager;
  import android.view.accessibility.AccessibilityNodeInfo;
  import android.view.animation.AlphaAnimation;
  import android.view.animation.Animation;
  import android.view.animation.AnimationUtils;
  import android.view.animation.DecelerateInterpolator;
  import android.view.animation.Interpolator;
  import android.view.animation.LinearInterpolator;
  import android.view.animation.Transformation;
  import android.view.inspector.InspectableProperty;
  import android.widget.RemoteViews.RemoteView;
  
  import com.android.internal.R;
  
  import java.util.ArrayList;
  
  /**
   * <p>
   * A user interface element that indicates the progress of an operation.
   * Progress bar supports two modes to represent progress: determinate, and indeterminate. For
   * a visual overview of the difference between determinate and indeterminate progress modes, see
   * <a href="https://material.io/guidelines/components/progress-activity.html#progress-activity-types-of-indicators">
   * Progress & activity</a>.
   * Display progress bars to a user in a non-interruptive way.
   * Show the progress bar in your app's user interface or in a notification
   * instead of within a dialog.
   * </p>
   * <h3>Indeterminate Progress</h3>
   * <p>
   * Use indeterminate mode for the progress bar when you do not know how long an
   * operation will take.
   * Indeterminate mode is the default for progress bar and shows a cyclic animation without a
   * specific amount of progress indicated.
   * The following example shows an indeterminate progress bar:
   * <pre>
   * &lt;ProgressBar
   *      android:id="@+id/indeterminateBar"
   *      android:layout_width="wrap_content"
   *      android:layout_height="wrap_content"
   *      /&gt;
   * </pre>
   * </p>
   * <h3>Determinate Progress</h3>
   * <p>
   * Use determinate mode for the progress bar when you want to show that a specific quantity of
   * progress has occurred.
   * For example, the percent remaining of a file being retrieved, the amount records in
   * a batch written to database, or the percent remaining of an audio file that is playing.
   * <p>
   * <p>
   * To indicate determinate progress, you set the style of the progress bar to
   * {@link android.R.style#Widget_ProgressBar_Horizontal} and set the amount of progress.
   * The following example shows a determinate progress bar that is 25% complete:
   * <pre>
   * &lt;ProgressBar
   *      android:id="@+id/determinateBar"
   *      style="@android:style/Widget.ProgressBar.Horizontal"
   *      android:layout_width="wrap_content"
   *      android:layout_height="wrap_content"
   *      android:progress="25"/&gt;
   * </pre>
   * You can update the percentage of progress displayed by using the
   * {@link #setProgress(int)} method, or by calling
   * {@link #incrementProgressBy(int)} to increase the current progress completed
   * by a specified amount.
   * By default, the progress bar is full when the progress value reaches 100.
   * You can adjust this default by setting the
   * {@link android.R.styleable#ProgressBar_max android:max} attribute.
   * </p>
   * <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_min
   * @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_mirrorForRtl
   * @attr ref android.R.styleable#ProgressBar_progress
   * @attr ref android.R.styleable#ProgressBar_progressDrawable
   * @attr ref android.R.styleable#ProgressBar_secondaryProgress
   */
  @RemoteView
  public class ProgressBar extends View {
  
      private static final int MAX_LEVEL = 10000;
      private static final int TIMEOUT_SEND_ACCESSIBILITY_EVENT = 200;
  
      /** Interpolator used for smooth progress animations. */
      private static final DecelerateInterpolator PROGRESS_ANIM_INTERPOLATOR =
              new DecelerateInterpolator();
  
      /** Duration of smooth progress animations. */
      private static final int PROGRESS_ANIM_DURATION = 80;
  
      /**
       * Outside the framework, please use {@link ProgressBar#getMinWidth()} and
       * {@link ProgressBar#setMinWidth(int)} instead of accessing these directly.
       */
      @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
      int mMinWidth;
      int mMaxWidth;
      /**
       * Outside the framework, please use {@link ProgressBar#getMinHeight()} and
       * {@link ProgressBar#setMinHeight(int)} instead of accessing these directly.
       */
      @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
      int mMinHeight;
      /**
       * Outside the framework, please use {@link ProgressBar#getMaxHeight()} ()} and
       * {@link ProgressBar#setMaxHeight(int)} (int)} instead of accessing these directly.
       */
      @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
      int mMaxHeight;
  
      private int mProgress;
      private int mSecondaryProgress;
      private int mMin;
      private boolean mMinInitialized;
      private int mMax;
      private boolean mMaxInitialized;
  
      private int mBehavior;
      // Better to define a Drawable that implements Animatable if you want to modify animation
      // characteristics programatically.
      @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 124052713)
      private int mDuration;
      @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
      private boolean mIndeterminate;
      @UnsupportedAppUsage(trackingBug = 124049927)
      private boolean mOnlyIndeterminate;
      private Transformation mTransformation;
      private AlphaAnimation mAnimation;
      private boolean mHasAnimation;
  
      private Drawable mIndeterminateDrawable;
      private Drawable mProgressDrawable;
      /**
       * Outside the framework, instead of accessing this directly, please use
       * {@link #getCurrentDrawable()}, {@link #setProgressDrawable(Drawable)},
       * {@link #setIndeterminateDrawable(Drawable)} and their tiled versions.
       */
      @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
      private Drawable mCurrentDrawable;
      private ProgressTintInfo mProgressTintInfo;
  
      int mSampleWidth = 0;
      private boolean mNoInvalidate;
      private Interpolator mInterpolator;
      private RefreshProgressRunnable mRefreshProgressRunnable;
      private long mUiThreadId;
      private boolean mShouldStartAnimationDrawable;
  
      private boolean mInDrawing;
      private boolean mAttached;
      private boolean mRefreshIsPosted;
  
      /** Value used to track progress animation, in the range [0...1]. */
      private float mVisualProgress;
  
      @UnsupportedAppUsage
      boolean mMirrorForRtl = false;
  
      private boolean mAggregatedIsVisible;
  
      private final ArrayList<RefreshData> mRefreshData = new ArrayList<RefreshData>();
  
      private AccessibilityEventSender mAccessibilityEventSender;
  
      /**
       * Create a new progress bar with range 0...100 and initial progress of 0.
       * @param context the application environment
       */
      public ProgressBar(Context context) {
          this(context, null);
      }
  
      public ProgressBar(Context context, AttributeSet attrs) {
          this(context, attrs, com.android.internal.R.attr.progressBarStyle);
      }
  
      public ProgressBar(Context context, AttributeSet attrs, int defStyleAttr) {
          this(context, attrs, defStyleAttr, 0);
      }
  
      public ProgressBar(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
          super(context, attrs, defStyleAttr, defStyleRes);
  
          mUiThreadId = Thread.currentThread().getId();
          initProgressBar();
  
          final TypedArray a = context.obtainStyledAttributes(
                  attrs, R.styleable.ProgressBar, defStyleAttr, defStyleRes);
          saveAttributeDataForStyleable(context, R.styleable.ProgressBar,
                  attrs, a, defStyleAttr, defStyleRes);
  
          mNoInvalidate = true;
  
          final Drawable progressDrawable = a.getDrawable(R.styleable.ProgressBar_progressDrawable);
          if (progressDrawable != null) {
              // Calling setProgressDrawable can set mMaxHeight, so make sure the
              // corresponding XML attribute for mMaxHeight is read after calling
              // this method.
              if (needsTileify(progressDrawable)) {
                  setProgressDrawableTiled(progressDrawable);
              } else {
                  setProgressDrawable(progressDrawable);
              }
          }
  
          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);
          }
  
          setMin(a.getInt(R.styleable.ProgressBar_min, mMin));
          setMax(a.getInt(R.styleable.ProgressBar_max, mMax));
  
          setProgress(a.getInt(R.styleable.ProgressBar_progress, mProgress));
  
          setSecondaryProgress(a.getInt(
                  R.styleable.ProgressBar_secondaryProgress, mSecondaryProgress));
  
          final Drawable indeterminateDrawable = a.getDrawable(
                  R.styleable.ProgressBar_indeterminateDrawable);
          if (indeterminateDrawable != null) {
              if (needsTileify(indeterminateDrawable)) {
                  setIndeterminateDrawableTiled(indeterminateDrawable);
              } else {
                  setIndeterminateDrawable(indeterminateDrawable);
              }
          }
  
          mOnlyIndeterminate = a.getBoolean(
                  R.styleable.ProgressBar_indeterminateOnly, mOnlyIndeterminate);
  
          mNoInvalidate = false;
  
          setIndeterminate(mOnlyIndeterminate || a.getBoolean(
                  R.styleable.ProgressBar_indeterminate, mIndeterminate));
  
          mMirrorForRtl = a.getBoolean(R.styleable.ProgressBar_mirrorForRtl, mMirrorForRtl);
  
          if (a.hasValue(R.styleable.ProgressBar_progressTintMode)) {
              if (mProgressTintInfo == null) {
                  mProgressTintInfo = new ProgressTintInfo();
              }
              mProgressTintInfo.mProgressBlendMode = Drawable.parseBlendMode(a.getInt(
                      R.styleable.ProgressBar_progressTintMode, -1), null);
              mProgressTintInfo.mHasProgressTintMode = true;
          }
  
          if (a.hasValue(R.styleable.ProgressBar_progressTint)) {
              if (mProgressTintInfo == null) {
                  mProgressTintInfo = new ProgressTintInfo();
              }
              mProgressTintInfo.mProgressTintList = a.getColorStateList(
                      R.styleable.ProgressBar_progressTint);
              mProgressTintInfo.mHasProgressTint = true;
          }
  
          if (a.hasValue(R.styleable.ProgressBar_progressBackgroundTintMode)) {
              if (mProgressTintInfo == null) {
                  mProgressTintInfo = new ProgressTintInfo();
              }
              mProgressTintInfo.mProgressBackgroundBlendMode = Drawable.parseBlendMode(a.getInt(
                      R.styleable.ProgressBar_progressBackgroundTintMode, -1), null);
              mProgressTintInfo.mHasProgressBackgroundTintMode = true;
          }
  
          if (a.hasValue(R.styleable.ProgressBar_progressBackgroundTint)) {
              if (mProgressTintInfo == null) {
                  mProgressTintInfo = new ProgressTintInfo();
              }
              mProgressTintInfo.mProgressBackgroundTintList = a.getColorStateList(
                      R.styleable.ProgressBar_progressBackgroundTint);
              mProgressTintInfo.mHasProgressBackgroundTint = true;
          }
  
          if (a.hasValue(R.styleable.ProgressBar_secondaryProgressTintMode)) {
              if (mProgressTintInfo == null) {
                  mProgressTintInfo = new ProgressTintInfo();
              }
              mProgressTintInfo.mSecondaryProgressBlendMode = Drawable.parseBlendMode(
                      a.getInt(R.styleable.ProgressBar_secondaryProgressTintMode, -1), null);
              mProgressTintInfo.mHasSecondaryProgressTintMode = true;
          }
  
          if (a.hasValue(R.styleable.ProgressBar_secondaryProgressTint)) {
              if (mProgressTintInfo == null) {
                  mProgressTintInfo = new ProgressTintInfo();
              }
              mProgressTintInfo.mSecondaryProgressTintList = a.getColorStateList(
                      R.styleable.ProgressBar_secondaryProgressTint);
              mProgressTintInfo.mHasSecondaryProgressTint = true;
          }
  
          if (a.hasValue(R.styleable.ProgressBar_indeterminateTintMode)) {
              if (mProgressTintInfo == null) {
                  mProgressTintInfo = new ProgressTintInfo();
              }
              mProgressTintInfo.mIndeterminateBlendMode = Drawable.parseBlendMode(a.getInt(
                      R.styleable.ProgressBar_indeterminateTintMode, -1), null);
              mProgressTintInfo.mHasIndeterminateTintMode = true;
          }
  
          if (a.hasValue(R.styleable.ProgressBar_indeterminateTint)) {
              if (mProgressTintInfo == null) {
                  mProgressTintInfo = new ProgressTintInfo();
              }
              mProgressTintInfo.mIndeterminateTintList = a.getColorStateList(
                      R.styleable.ProgressBar_indeterminateTint);
              mProgressTintInfo.mHasIndeterminateTint = true;
          }
  
          a.recycle();
  
          applyProgressTints();
          applyIndeterminateTint();
  
          // If not explicitly specified this view is important for accessibility.
          if (getImportantForAccessibility() == View.IMPORTANT_FOR_ACCESSIBILITY_AUTO) {
              setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_YES);
          }
      }
  
      /**
       * Sets the minimum width the progress bar can have.
       * @param minWidth the minimum width to be set, in pixels
       * @attr ref android.R.styleable#ProgressBar_minWidth
       */
      public void setMinWidth(@Px int minWidth) {
          mMinWidth = minWidth;
          requestLayout();
      }
  
      /**
       * @return the minimum width the progress bar can have, in pixels
       */
      @Px public int getMinWidth() {
          return mMinWidth;
      }
  
      /**
       * Sets the maximum width the progress bar can have.
       * @param maxWidth the maximum width to be set, in pixels
       * @attr ref android.R.styleable#ProgressBar_maxWidth
       */
      public void setMaxWidth(@Px int maxWidth) {
          mMaxWidth = maxWidth;
          requestLayout();
      }
  
      /**
       * @return the maximum width the progress bar can have, in pixels
       */
      @Px public int getMaxWidth() {
          return mMaxWidth;
      }
  
      /**
       * Sets the minimum height the progress bar can have.
       * @param minHeight the minimum height to be set, in pixels
       * @attr ref android.R.styleable#ProgressBar_minHeight
       */
      public void setMinHeight(@Px int minHeight) {
          mMinHeight = minHeight;
          requestLayout();
      }
  
      /**
       * @return the minimum height the progress bar can have, in pixels
       */
      @Px public int getMinHeight() {
          return mMinHeight;
      }
  
      /**
       * Sets the maximum height the progress bar can have.
       * @param maxHeight the maximum height to be set, in pixels
       * @attr ref android.R.styleable#ProgressBar_maxHeight
       */
      public void setMaxHeight(@Px int maxHeight) {
          mMaxHeight = maxHeight;
          requestLayout();
      }
  
      /**
       * @return the maximum height the progress bar can have, in pixels
       */
      @Px public int getMaxHeight() {
          return mMaxHeight;
      }
  
      /**
       * Returns {@code true} if the target drawable needs to be tileified.
       *
       * @param dr the drawable to check
       * @return {@code true} if the target drawable needs to be tileified,
       *         {@code false} otherwise
       */
      private static boolean needsTileify(Drawable dr) {
          if (dr instanceof LayerDrawable) {
              final LayerDrawable orig = (LayerDrawable) dr;
              final int N = orig.getNumberOfLayers();
              for (int i = 0; i < N; i++) {
                  if (needsTileify(orig.getDrawable(i))) {
                      return true;
                  }
              }
              return false;
          }
  
          if (dr instanceof StateListDrawable) {
              final StateListDrawable in = (StateListDrawable) dr;
              final int N = in.getStateCount();
              for (int i = 0; i < N; i++) {
                  if (needsTileify(in.getStateDrawable(i))) {
                      return true;
                  }
              }
              return false;
          }
  
          // If there's a bitmap that's not wrapped with a ClipDrawable or
          // ScaleDrawable, we'll need to wrap it and apply tiling.
          if (dr instanceof BitmapDrawable) {
              return true;
          }
  
          return false;
      }
  
      /**
       * Converts a drawable to a tiled version of itself. It will recursively
       * traverse layer and state list drawables.
       */
      @UnsupportedAppUsage
      private Drawable tileify(Drawable drawable, boolean clip) {
          // TODO: This is a terrible idea that potentially destroys any drawable
          // that extends any of these classes. We *really* need to remove this.
  
          if (drawable instanceof LayerDrawable) {
              final LayerDrawable orig = (LayerDrawable) drawable;
              final int N = orig.getNumberOfLayers();
              final Drawable[] outDrawables = new Drawable[N];
  
              for (int i = 0; i < N; i++) {
                  final int id = orig.getId(i);
                  outDrawables[i] = tileify(orig.getDrawable(i),
                          (id == R.id.progress || id == R.id.secondaryProgress));
              }
  
              final LayerDrawable clone = new LayerDrawable(outDrawables);
              for (int i = 0; i < N; i++) {
                  clone.setId(i, orig.getId(i));
                  clone.setLayerGravity(i, orig.getLayerGravity(i));
                  clone.setLayerWidth(i, orig.getLayerWidth(i));
                  clone.setLayerHeight(i, orig.getLayerHeight(i));
                  clone.setLayerInsetLeft(i, orig.getLayerInsetLeft(i));
                  clone.setLayerInsetRight(i, orig.getLayerInsetRight(i));
                  clone.setLayerInsetTop(i, orig.getLayerInsetTop(i));
                  clone.setLayerInsetBottom(i, orig.getLayerInsetBottom(i));
                  clone.setLayerInsetStart(i, orig.getLayerInsetStart(i));
                  clone.setLayerInsetEnd(i, orig.getLayerInsetEnd(i));
              }
  
              return clone;
          }
  
          if (drawable instanceof StateListDrawable) {
              final StateListDrawable in = (StateListDrawable) drawable;
              final StateListDrawable out = new StateListDrawable();
              final int N = in.getStateCount();
              for (int i = 0; i < N; i++) {
                  out.addState(in.getStateSet(i), tileify(in.getStateDrawable(i), clip));
              }
  
              return out;
          }
  
          if (drawable instanceof BitmapDrawable) {
              final Drawable.ConstantState cs = drawable.getConstantState();
              final BitmapDrawable clone = (BitmapDrawable) cs.newDrawable(getResources());
              clone.setTileModeXY(Shader.TileMode.REPEAT, Shader.TileMode.CLAMP);
  
              if (mSampleWidth <= 0) {
                  mSampleWidth = clone.getIntrinsicWidth();
              }
  
              if (clip) {
                  return new ClipDrawable(clone, Gravity.LEFT, ClipDrawable.HORIZONTAL);
              } else {
                  return clone;
              }
          }
  
          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() {
          mMin = 0;
          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
       */
      @InspectableProperty
      @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
       */
      @android.view.RemotableViewMethod
      public synchronized void setIndeterminate(boolean indeterminate) {
          if ((!mOnlyIndeterminate || !mIndeterminate) && indeterminate != mIndeterminate) {
              mIndeterminate = indeterminate;
  
              if (indeterminate) {
                  // swap between indeterminate and regular backgrounds
                  swapCurrentDrawable(mIndeterminateDrawable);
                  startAnimation();
              } else {
                  swapCurrentDrawable(mProgressDrawable);
                  stopAnimation();
              }
          }
      }
  
      private void swapCurrentDrawable(Drawable newDrawable) {
          final Drawable oldDrawable = mCurrentDrawable;
          mCurrentDrawable = newDrawable;
  
          if (oldDrawable != mCurrentDrawable) {
              if (oldDrawable != null) {
                  oldDrawable.setVisible(false, false);
              }
              if (mCurrentDrawable != null) {
                  mCurrentDrawable.setVisible(getWindowVisibility() == VISIBLE && isShown(), false);
              }
          }
      }
  
      /**
       * <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)
       */
      @InspectableProperty
      public Drawable getIndeterminateDrawable() {
          return mIndeterminateDrawable;
      }
  
      /**
       * Define the drawable used to draw the progress bar in indeterminate mode.
       *
       * <p>For the Drawable to animate, it must implement {@link Animatable}, or override
       * {@link Drawable#onLevelChange(int)}.  A Drawable that implements Animatable will be animated
       * via that interface and therefore provides the greatest amount of customization. A Drawable
       * that only overrides onLevelChange(int) is animated directly by ProgressBar and only the
       * animation {@link android.R.styleable#ProgressBar_indeterminateDuration duration},
           * {@link android.R.styleable#ProgressBar_indeterminateBehavior repeating behavior}, and
       * {@link #setInterpolator(Interpolator) interpolator} can be modified, and only before the
       * indeterminate animation begins.
       *
       * @param d the new drawable
       * @attr ref android.R.styleable#ProgressBar_indeterminateDrawable
       * @see #getIndeterminateDrawable()
       * @see #setIndeterminate(boolean)
       */
      public void setIndeterminateDrawable(Drawable d) {
          if (mIndeterminateDrawable != d) {
              if (mIndeterminateDrawable != null) {
                  mIndeterminateDrawable.setCallback(null);
                  unscheduleDrawable(mIndeterminateDrawable);
              }
  
              mIndeterminateDrawable = d;
  
              if (d != null) {
                  d.setCallback(this);
                  d.setLayoutDirection(getLayoutDirection());
                  if (d.isStateful()) {
                      d.setState(getDrawableState());
                  }
                  applyIndeterminateTint();
              }
  
              if (mIndeterminate) {
                  swapCurrentDrawable(d);
                  postInvalidate();
              }
          }
      }
  
      /**
       * Applies a tint to the indeterminate drawable. Does not modify the
       * current tint mode, which is {@link PorterDuff.Mode#SRC_IN} by default.
       * <p>
       * Subsequent calls to {@link #setIndeterminateDrawable(Drawable)} will
       * automatically mutate the drawable and apply the specified tint and
       * tint mode using
       * {@link Drawable#setTintList(ColorStateList)}.
       *
       * @param tint the tint to apply, may be {@code null} to clear tint
       *
       * @attr ref android.R.styleable#ProgressBar_indeterminateTint
       * @see #getIndeterminateTintList()
       * @see Drawable#setTintList(ColorStateList)
       */
      @RemotableViewMethod
      public void setIndeterminateTintList(@Nullable ColorStateList tint) {
          if (mProgressTintInfo == null) {
              mProgressTintInfo = new ProgressTintInfo();
          }
          mProgressTintInfo.mIndeterminateTintList = tint;
          mProgressTintInfo.mHasIndeterminateTint = true;
  
          applyIndeterminateTint();
      }
  
      /**
       * @return the tint applied to the indeterminate drawable
       * @attr ref android.R.styleable#ProgressBar_indeterminateTint
       * @see #setIndeterminateTintList(ColorStateList)
       */
      @InspectableProperty(name = "indeterminateTint")
      @Nullable
      public ColorStateList getIndeterminateTintList() {
          return mProgressTintInfo != null ? mProgressTintInfo.mIndeterminateTintList : null;
      }
  
      /**
       * Specifies the blending mode used to apply the tint specified by
       * {@link #setIndeterminateTintList(ColorStateList)} to the indeterminate
       * drawable. The default mode is {@link PorterDuff.Mode#SRC_IN}.
       *
       * @param tintMode the blending mode used to apply the tint, may be
       *                 {@code null} to clear tint
       * @attr ref android.R.styleable#ProgressBar_indeterminateTintMode
       * @see #setIndeterminateTintList(ColorStateList)
       * @see Drawable#setTintMode(PorterDuff.Mode)
       *
       */
      public void setIndeterminateTintMode(@Nullable PorterDuff.Mode tintMode) {
          setIndeterminateTintBlendMode(tintMode != null
                  ? BlendMode.fromValue(tintMode.nativeInt) : null);
      }
  
      /**
       * Specifies the blending mode used to apply the tint specified by
       * {@link #setIndeterminateTintList(ColorStateList)} to the indeterminate
       * drawable. The default mode is {@link PorterDuff.Mode#SRC_IN}.
       *
       * @param blendMode the blending mode used to apply the tint, may be
       *                 {@code null} to clear tint
       * @attr ref android.R.styleable#ProgressBar_indeterminateTintMode
       * @see #setIndeterminateTintList(ColorStateList)
       * @see Drawable#setTintBlendMode(BlendMode)
       */
      public void setIndeterminateTintBlendMode(@Nullable BlendMode blendMode) {
          if (mProgressTintInfo == null) {
              mProgressTintInfo = new ProgressTintInfo();
          }
          mProgressTintInfo.mIndeterminateBlendMode = blendMode;
          mProgressTintInfo.mHasIndeterminateTintMode = true;
  
          applyIndeterminateTint();
      }
  
      /**
       * Returns the blending mode used to apply the tint to the indeterminate
       * drawable, if specified.
       *
       * @return the blending mode used to apply the tint to the indeterminate
       *         drawable
       * @attr ref android.R.styleable#ProgressBar_indeterminateTintMode
       * @see #setIndeterminateTintMode(PorterDuff.Mode)
       */
      @InspectableProperty
      @Nullable
      public PorterDuff.Mode getIndeterminateTintMode() {
          BlendMode mode = getIndeterminateTintBlendMode();
          return mode != null ? BlendMode.blendModeToPorterDuffMode(mode) : null;
      }
  
      /**
       * Returns the blending mode used to apply the tint to the indeterminate
       * drawable, if specified.
       *
       * @return the blending mode used to apply the tint to the indeterminate
       *         drawable
       * @attr ref android.R.styleable#ProgressBar_indeterminateTintMode
       * @see #setIndeterminateTintBlendMode(BlendMode)
       */
      @InspectableProperty(attributeId = R.styleable.ProgressBar_indeterminateTintMode)
      @Nullable
      public BlendMode getIndeterminateTintBlendMode() {
          return mProgressTintInfo != null ? mProgressTintInfo.mIndeterminateBlendMode : null;
      }
  
      private void applyIndeterminateTint() {
          if (mIndeterminateDrawable != null && mProgressTintInfo != null) {
              final ProgressTintInfo tintInfo = mProgressTintInfo;
              if (tintInfo.mHasIndeterminateTint || tintInfo.mHasIndeterminateTintMode) {
                  mIndeterminateDrawable = mIndeterminateDrawable.mutate();
  
                  if (tintInfo.mHasIndeterminateTint) {
                      mIndeterminateDrawable.setTintList(tintInfo.mIndeterminateTintList);
                  }
  
                  if (tintInfo.mHasIndeterminateTintMode) {
                      mIndeterminateDrawable.setTintBlendMode(tintInfo.mIndeterminateBlendMode);
                  }
  
                  // The drawable (or one of its children) may not have been
                  // stateful before applying the tint, so let's try again.
                  if (mIndeterminateDrawable.isStateful()) {
                      mIndeterminateDrawable.setState(getDrawableState());
                  }
              }
          }
      }
  
      /**
       * Define the tileable drawable used to draw the progress bar in
       * indeterminate mode.
       * <p>
       * If the drawable is a BitmapDrawable or contains BitmapDrawables, a
       * tiled copy will be generated for display as a progress bar.
       *
       * @param d the new drawable
       * @see #getIndeterminateDrawable()
       * @see #setIndeterminate(boolean)
       */
      public void setIndeterminateDrawableTiled(Drawable d) {
          if (d != null) {
              d = tileifyIndeterminate(d);
          }
  
          setIndeterminateDrawable(d);
      }
  
      /**
       * <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)
       */
      @InspectableProperty
      public Drawable getProgressDrawable() {
          return mProgressDrawable;
      }
  
      /**
       * Define the drawable used to draw the progress bar in progress mode.
       *
       * @param d the new drawable
       * @see #getProgressDrawable()
       * @see #setIndeterminate(boolean)
       */
      public void setProgressDrawable(Drawable d) {
          if (mProgressDrawable != d) {
              if (mProgressDrawable != null) {
                  mProgressDrawable.setCallback(null);
                  unscheduleDrawable(mProgressDrawable);
              }
  
              mProgressDrawable = d;
  
              if (d != null) {
                  d.setCallback(this);
                  d.setLayoutDirection(getLayoutDirection());
                  if (d.isStateful()) {
                      d.setState(getDrawableState());
                  }
  
                  // Make sure the ProgressBar is always tall enough
                  int drawableHeight = d.getMinimumHeight();
                  if (mMaxHeight < drawableHeight) {
                      mMaxHeight = drawableHeight;
                      requestLayout();
                  }
  
                  applyProgressTints();
              }
  
              if (!mIndeterminate) {
                  swapCurrentDrawable(d);
                  postInvalidate();
              }
  
              updateDrawableBounds(getWidth(), getHeight());
              updateDrawableState();
  
              doRefreshProgress(R.id.progress, mProgress, false, false, false);
              doRefreshProgress(R.id.secondaryProgress, mSecondaryProgress, false, false, false);
          }
      }
  
      /**
       * @hide
       */
      @InspectableProperty
      public boolean getMirrorForRtl() {
          return mMirrorForRtl;
      }
  
      /**
       * Applies the progress tints in order of increasing specificity.
       */
      private void applyProgressTints() {
          if (mProgressDrawable != null && mProgressTintInfo != null) {
              applyPrimaryProgressTint();
              applyProgressBackgroundTint();
              applySecondaryProgressTint();
          }
      }
  
      /**
       * Should only be called if we've already verified that mProgressDrawable
       * and mProgressTintInfo are non-null.
       */
      private void applyPrimaryProgressTint() {
          if (mProgressTintInfo.mHasProgressTint
                  || mProgressTintInfo.mHasProgressTintMode) {
              final Drawable target = getTintTarget(R.id.progress, true);
              if (target != null) {
                  if (mProgressTintInfo.mHasProgressTint) {
                      target.setTintList(mProgressTintInfo.mProgressTintList);
                  }
                  if (mProgressTintInfo.mHasProgressTintMode) {
                      target.setTintBlendMode(mProgressTintInfo.mProgressBlendMode);
                  }
  
                  // The drawable (or one of its children) may not have been
                  // stateful before applying the tint, so let's try again.
                  if (target.isStateful()) {
                      target.setState(getDrawableState());
                  }
              }
          }
      }
  
      /**
       * Should only be called if we've already verified that mProgressDrawable
       * and mProgressTintInfo are non-null.
       */
      private void applyProgressBackgroundTint() {
          if (mProgressTintInfo.mHasProgressBackgroundTint
                  || mProgressTintInfo.mHasProgressBackgroundTintMode) {
              final Drawable target = getTintTarget(R.id.background, false);
              if (target != null) {
                  if (mProgressTintInfo.mHasProgressBackgroundTint) {
                      target.setTintList(mProgressTintInfo.mProgressBackgroundTintList);
                  }
                  if (mProgressTintInfo.mHasProgressBackgroundTintMode) {
                      target.setTintBlendMode(mProgressTintInfo.mProgressBackgroundBlendMode);
                  }
  
                  // The drawable (or one of its children) may not have been
                  // stateful before applying the tint, so let's try again.
                  if (target.isStateful()) {
                      target.setState(getDrawableState());
                  }
              }
          }
      }
  
      /**
       * Should only be called if we've already verified that mProgressDrawable
       * and mProgressTintInfo are non-null.
       */
      private void applySecondaryProgressTint() {
          if (mProgressTintInfo.mHasSecondaryProgressTint
                  || mProgressTintInfo.mHasSecondaryProgressTintMode) {
              final Drawable target = getTintTarget(R.id.secondaryProgress, false);
              if (target != null) {
                  if (mProgressTintInfo.mHasSecondaryProgressTint) {
                      target.setTintList(mProgressTintInfo.mSecondaryProgressTintList);
                  }
                  if (mProgressTintInfo.mHasSecondaryProgressTintMode) {
                      target.setTintBlendMode(mProgressTintInfo.mSecondaryProgressBlendMode);
                  }
  
                  // The drawable (or one of its children) may not have been
                  // stateful before applying the tint, so let's try again.
                  if (target.isStateful()) {
                      target.setState(getDrawableState());
                  }
              }
          }
      }
  
      /**
       * Applies a tint to the progress indicator, if one exists, or to the
       * entire progress drawable otherwise. Does not modify the current tint
       * mode, which is {@link PorterDuff.Mode#SRC_IN} by default.
       * <p>
       * The progress indicator should be specified as a layer with
       * id {@link android.R.id#progress} in a {@link LayerDrawable}
       * used as the progress drawable.
       * <p>
       * Subsequent calls to {@link #setProgressDrawable(Drawable)} will
       * automatically mutate the drawable and apply the specified tint and
       * tint mode using
       * {@link Drawable#setTintList(ColorStateList)}.
       *
       * @param tint the tint to apply, may be {@code null} to clear tint
       *
       * @attr ref android.R.styleable#ProgressBar_progressTint
       * @see #getProgressTintList()
       * @see Drawable#setTintList(ColorStateList)
       */
      @RemotableViewMethod
      public void setProgressTintList(@Nullable ColorStateList tint) {
          if (mProgressTintInfo == null) {
              mProgressTintInfo = new ProgressTintInfo();
          }
          mProgressTintInfo.mProgressTintList = tint;
          mProgressTintInfo.mHasProgressTint = true;
  
          if (mProgressDrawable != null) {
              applyPrimaryProgressTint();
          }
      }
  
      /**
       * Returns the tint applied to the progress drawable, if specified.
       *
       * @return the tint applied to the progress drawable
       * @attr ref android.R.styleable#ProgressBar_progressTint
       * @see #setProgressTintList(ColorStateList)
       */
      @InspectableProperty(name = "progressTint")
      @Nullable
      public ColorStateList getProgressTintList() {
          return mProgressTintInfo != null ? mProgressTintInfo.mProgressTintList : null;
      }
  
      /**
       * Specifies the blending mode used to apply the tint specified by
       * {@link #setProgressTintList(ColorStateList)}} to the progress
       * indicator. The default mode is {@link PorterDuff.Mode#SRC_IN}.
       *
       * @param tintMode the blending mode used to apply the tint, may be
       *                 {@code null} to clear tint
       * @attr ref android.R.styleable#ProgressBar_progressTintMode
       * @see #getProgressTintMode()
       * @see Drawable#setTintMode(PorterDuff.Mode)
       */
      public void setProgressTintMode(@Nullable PorterDuff.Mode tintMode) {
          setProgressTintBlendMode(tintMode != null ? BlendMode.fromValue(tintMode.nativeInt) : null);
      }
  
      /**
       * Specifies the blending mode used to apply the tint specified by
       * {@link #setProgressTintList(ColorStateList)}} to the progress
       * indicator. The default mode is {@link PorterDuff.Mode#SRC_IN}.
       *
       * @param blendMode the blending mode used to apply the tint, may be
       *                 {@code null} to clear tint
       * @attr ref android.R.styleable#ProgressBar_progressTintMode
       * @see #getProgressTintMode()
       * @see Drawable#setTintBlendMode(BlendMode)
       */
      public void setProgressTintBlendMode(@Nullable BlendMode blendMode) {
          if (mProgressTintInfo == null) {
              mProgressTintInfo = new ProgressTintInfo();
          }
          mProgressTintInfo.mProgressBlendMode = blendMode;
          mProgressTintInfo.mHasProgressTintMode = true;
  
          if (mProgressDrawable != null) {
              applyPrimaryProgressTint();
          }
      }
  
      /**
       * Returns the blending mode used to apply the tint to the progress
       * drawable, if specified.
       *
       * @return the blending mode used to apply the tint to the progress
       *         drawable
       * @attr ref android.R.styleable#ProgressBar_progressTintMode
       * @see #setProgressTintMode(PorterDuff.Mode)
       */
      @InspectableProperty
      @Nullable
      public PorterDuff.Mode getProgressTintMode() {
          BlendMode mode = getProgressTintBlendMode();
          return mode != null ? BlendMode.blendModeToPorterDuffMode(mode) : null;
      }
  
      /**
       * Returns the blending mode used to apply the tint to the progress
       * drawable, if specified.
       *
       * @return the blending mode used to apply the tint to the progress
       *         drawable
       * @attr ref android.R.styleable#ProgressBar_progressTintMode
       * @see #setProgressTintBlendMode(BlendMode)
       */
      @InspectableProperty(attributeId = android.R.styleable.ProgressBar_progressTintMode)
      @Nullable
      public BlendMode getProgressTintBlendMode() {
          return mProgressTintInfo != null ? mProgressTintInfo.mProgressBlendMode : null;
      }
  
      /**
       * Applies a tint to the progress background, if one exists. Does not
       * modify the current tint mode, which is
       * {@link PorterDuff.Mode#SRC_ATOP} by default.
       * <p>
       * The progress background must be specified as a layer with
       * id {@link android.R.id#background} in a {@link LayerDrawable}
       * used as the progress drawable.
       * <p>
       * Subsequent calls to {@link #setProgressDrawable(Drawable)} where the
       * drawable contains a progress background will automatically mutate the
       * drawable and apply the specified tint and tint mode using
       * {@link Drawable#setTintList(ColorStateList)}.
       *
       * @param tint the tint to apply, may be {@code null} to clear tint
       *
       * @attr ref android.R.styleable#ProgressBar_progressBackgroundTint
       * @see #getProgressBackgroundTintList()
       * @see Drawable#setTintList(ColorStateList)
       */
      @RemotableViewMethod
      public void setProgressBackgroundTintList(@Nullable ColorStateList tint) {
          if (mProgressTintInfo == null) {
              mProgressTintInfo = new ProgressTintInfo();
          }
          mProgressTintInfo.mProgressBackgroundTintList = tint;
          mProgressTintInfo.mHasProgressBackgroundTint = true;
  
          if (mProgressDrawable != null) {
              applyProgressBackgroundTint();
          }
      }
  
      /**
       * Returns the tint applied to the progress background, if specified.
       *
       * @return the tint applied to the progress background
       * @attr ref android.R.styleable#ProgressBar_progressBackgroundTint
       * @see #setProgressBackgroundTintList(ColorStateList)
       */
      @InspectableProperty(name = "progressBackgroundTint")
      @Nullable
      public ColorStateList getProgressBackgroundTintList() {
          return mProgressTintInfo != null ? mProgressTintInfo.mProgressBackgroundTintList : null;
      }
  
      /**
       * Specifies the blending mode used to apply the tint specified by
       * {@link #setProgressBackgroundTintList(ColorStateList)}} to the progress
       * background. The default mode is {@link PorterDuff.Mode#SRC_IN}.
       *
       * @param tintMode the blending mode used to apply the tint, may be
       *                 {@code null} to clear tint
       * @attr ref android.R.styleable#ProgressBar_progressBackgroundTintMode
       * @see #setProgressBackgroundTintList(ColorStateList)
       * @see Drawable#setTintMode(PorterDuff.Mode)
       */
      public void setProgressBackgroundTintMode(@Nullable PorterDuff.Mode tintMode) {
          setProgressBackgroundTintBlendMode(tintMode != null
                  ? BlendMode.fromValue(tintMode.nativeInt) : null);
      }
  
      /**
       * Specifies the blending mode used to apply the tint specified by
       * {@link #setProgressBackgroundTintList(ColorStateList)}} to the progress
       * background. The default mode is {@link BlendMode#SRC_IN}.
       *
       * @param blendMode the blending mode used to apply the tint, may be
       *                 {@code null} to clear tint
       * @attr ref android.R.styleable#ProgressBar_progressBackgroundTintMode
       * @see #setProgressBackgroundTintList(ColorStateList)
       * @see Drawable#setTintBlendMode(BlendMode)
       */
      public void setProgressBackgroundTintBlendMode(@Nullable BlendMode blendMode) {
          if (mProgressTintInfo == null) {
              mProgressTintInfo = new ProgressTintInfo();
          }
          mProgressTintInfo.mProgressBackgroundBlendMode = blendMode;
          mProgressTintInfo.mHasProgressBackgroundTintMode = true;
  
          if (mProgressDrawable != null) {
              applyProgressBackgroundTint();
          }
      }
  
      /**
       * @return the blending mode used to apply the tint to the progress
       *         background
       * @attr ref android.R.styleable#ProgressBar_progressBackgroundTintMode
       * @see #setProgressBackgroundTintMode(PorterDuff.Mode)
       */
      @InspectableProperty
      @Nullable
      public PorterDuff.Mode getProgressBackgroundTintMode() {
          BlendMode mode = getProgressBackgroundTintBlendMode();
          return mode != null ? BlendMode.blendModeToPorterDuffMode(mode) : null;
      }
  
      /**
       * @return the blending mode used to apply the tint to the progress
       *         background
       * @attr ref android.R.styleable#ProgressBar_progressBackgroundTintMode
       * @see #setProgressBackgroundTintBlendMode(BlendMode)
       */
      @InspectableProperty(attributeId = R.styleable.ProgressBar_progressBackgroundTintMode)
      @Nullable
      public BlendMode getProgressBackgroundTintBlendMode() {
          return mProgressTintInfo != null ? mProgressTintInfo.mProgressBackgroundBlendMode : null;
      }
  
      /**
       * Applies a tint to the secondary progress indicator, if one exists.
       * Does not modify the current tint mode, which is
       * {@link PorterDuff.Mode#SRC_ATOP} by default.
       * <p>
       * The secondary progress indicator must be specified as a layer with
       * id {@link android.R.id#secondaryProgress} in a {@link LayerDrawable}
       * used as the progress drawable.
       * <p>
       * Subsequent calls to {@link #setProgressDrawable(Drawable)} where the
       * drawable contains a secondary progress indicator will automatically
       * mutate the drawable and apply the specified tint and tint mode using
       * {@link Drawable#setTintList(ColorStateList)}.
       *
       * @param tint the tint to apply, may be {@code null} to clear tint
       *
       * @attr ref android.R.styleable#ProgressBar_secondaryProgressTint
       * @see #getSecondaryProgressTintList()
       * @see Drawable#setTintList(ColorStateList)
       */
      public void setSecondaryProgressTintList(@Nullable ColorStateList tint) {
          if (mProgressTintInfo == null) {
              mProgressTintInfo = new ProgressTintInfo();
          }
          mProgressTintInfo.mSecondaryProgressTintList = tint;
          mProgressTintInfo.mHasSecondaryProgressTint = true;
  
          if (mProgressDrawable != null) {
              applySecondaryProgressTint();
          }
      }
  
      /**
       * Returns the tint applied to the secondary progress drawable, if
       * specified.
       *
       * @return the tint applied to the secondary progress drawable
       * @attr ref android.R.styleable#ProgressBar_secondaryProgressTint
       * @see #setSecondaryProgressTintList(ColorStateList)
       */
      @InspectableProperty(name = "secondaryProgressTint")
      @Nullable
      public ColorStateList getSecondaryProgressTintList() {
          return mProgressTintInfo != null ? mProgressTintInfo.mSecondaryProgressTintList : null;
      }
  
      /**
       * Specifies the blending mode used to apply the tint specified by
       * {@link #setSecondaryProgressTintList(ColorStateList)}} to the secondary
       * progress indicator. The default mode is
       * {@link PorterDuff.Mode#SRC_ATOP}.
       *
       * @param tintMode the blending mode used to apply the tint, may be
       *                 {@code null} to clear tint
       * @attr ref android.R.styleable#ProgressBar_secondaryProgressTintMode
       * @see #setSecondaryProgressTintList(ColorStateList)
       * @see Drawable#setTintMode(PorterDuff.Mode)
       */
      public void setSecondaryProgressTintMode(@Nullable PorterDuff.Mode tintMode) {
          setSecondaryProgressTintBlendMode(tintMode != null
                  ? BlendMode.fromValue(tintMode.nativeInt) : null);
      }
  
      /**
       * Specifies the blending mode used to apply the tint specified by
       * {@link #setSecondaryProgressTintList(ColorStateList)}} to the secondary
       * progress indicator. The default mode is
       * {@link PorterDuff.Mode#SRC_ATOP}.
       *
       * @param blendMode the blending mode used to apply the tint, may be
       *                 {@code null} to clear tint
       * @attr ref android.R.styleable#ProgressBar_secondaryProgressTintMode
       * @see #setSecondaryProgressTintList(ColorStateList)
       * @see Drawable#setTintBlendMode(BlendMode)
       */
      public void setSecondaryProgressTintBlendMode(@Nullable BlendMode blendMode) {
          if (mProgressTintInfo == null) {
              mProgressTintInfo = new ProgressTintInfo();
          }
          mProgressTintInfo.mSecondaryProgressBlendMode = blendMode;
          mProgressTintInfo.mHasSecondaryProgressTintMode = true;
  
          if (mProgressDrawable != null) {
              applySecondaryProgressTint();
          }
      }
  
      /**
       * Returns the blending mode used to apply the tint to the secondary
       * progress drawable, if specified.
       *
       * @return the blending mode used to apply the tint to the secondary
       *         progress drawable
       * @attr ref android.R.styleable#ProgressBar_secondaryProgressTintMode
       * @see #setSecondaryProgressTintMode(PorterDuff.Mode)
       */
      @InspectableProperty
      @Nullable
      public PorterDuff.Mode getSecondaryProgressTintMode() {
          BlendMode mode = getSecondaryProgressTintBlendMode();
          return mode != null ? BlendMode.blendModeToPorterDuffMode(mode) : null;
      }
  
          /**
       * Returns the blending mode used to apply the tint to the secondary
       * progress drawable, if specified.
       *
       * @return the blending mode used to apply the tint to the secondary
       *         progress drawable
       * @attr ref android.R.styleable#ProgressBar_secondaryProgressTintMode
       * @see #setSecondaryProgressTintBlendMode(BlendMode)
       */
      @InspectableProperty(attributeId = android.R.styleable.ProgressBar_secondaryProgressTintMode)
      @Nullable
      public BlendMode getSecondaryProgressTintBlendMode() {
          return mProgressTintInfo != null ? mProgressTintInfo.mSecondaryProgressBlendMode : null;
      }
  
      /**
       * Returns the drawable to which a tint or tint mode should be applied.
       *
       * @param layerId id of the layer to modify
       * @param shouldFallback whether the base drawable should be returned
       *                       if the id does not exist
       * @return the drawable to modify
       */
      @Nullable
      private Drawable getTintTarget(int layerId, boolean shouldFallback) {
          Drawable layer = null;
  
          final Drawable d = mProgressDrawable;
          if (d != null) {
              mProgressDrawable = d.mutate();
  
              if (d instanceof LayerDrawable) {
                  layer = ((LayerDrawable) d).findDrawableByLayerId(layerId);
              }
  
              if (shouldFallback && layer == null) {
                  layer = d;
              }
          }
  
          return layer;
      }
  
      /**
       * Define the tileable drawable used to draw the progress bar in
       * progress mode.
       * <p>
       * If the drawable is a BitmapDrawable or contains BitmapDrawables, a
       * tiled copy will be generated for display as a progress bar.
       *
       * @param d the new drawable
       * @see #getProgressDrawable()
       * @see #setIndeterminate(boolean)
       */
      public void setProgressDrawableTiled(Drawable d) {
          if (d != null) {
              d = tileify(d, false);
          }
  
          setProgressDrawable(d);
      }
  
      /**
       * Returns the drawable currently used to draw the progress bar. This will be
       * either {@link #getProgressDrawable()} or {@link #getIndeterminateDrawable()}
       * depending on whether the progress bar is in determinate or indeterminate mode.
       *
       * @return the drawable currently used to draw the progress bar
       */
      @Nullable
      public Drawable getCurrentDrawable() {
          return mCurrentDrawable;
      }
  
      @Override
      protected boolean verifyDrawable(@NonNull 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();
      }
  
      /**
       * @hide
       */
      @Override
      public void onResolveDrawables(int layoutDirection) {
          final Drawable d = mCurrentDrawable;
          if (d != null) {
              d.setLayoutDirection(layoutDirection);
          }
          if (mIndeterminateDrawable != null) {
              mIndeterminateDrawable.setLayoutDirection(layoutDirection);
          }
          if (mProgressDrawable != null) {
              mProgressDrawable.setLayoutDirection(layoutDirection);
          }
      }
  
      @Override
      public void postInvalidate() {
          if (!mNoInvalidate) {
              super.postInvalidate();
          }
      }
  
      private class RefreshProgressRunnable implements Runnable {
          public void run() {
              synchronized (ProgressBar.this) {
                  final int count = mRefreshData.size();
                  for (int i = 0; i < count; i++) {
                      final RefreshData rd = mRefreshData.get(i);
                      doRefreshProgress(rd.id, rd.progress, rd.fromUser, true, rd.animate);
                      rd.recycle();
                  }
                  mRefreshData.clear();
                  mRefreshIsPosted = false;
              }
          }
      }
  
      private static class RefreshData {
          private static final int POOL_MAX = 24;
          private static final SynchronizedPool<RefreshData> sPool =
                  new SynchronizedPool<RefreshData>(POOL_MAX);
  
          public int id;
          public int progress;
          public boolean fromUser;
          public boolean animate;
  
          public static RefreshData obtain(int id, int progress, boolean fromUser, boolean animate) {
              RefreshData rd = sPool.acquire();
              if (rd == null) {
                  rd = new RefreshData();
              }
              rd.id = id;
              rd.progress = progress;
              rd.fromUser = fromUser;
              rd.animate = animate;
              return rd;
          }
  
          public void recycle() {
              sPool.release(this);
          }
      }
  
      private synchronized void doRefreshProgress(int id, int progress, boolean fromUser,
              boolean callBackToApp, boolean animate) {
          int range = mMax - mMin;
          final float scale = range > 0 ? (progress - mMin) / (float) range : 0;
          final boolean isPrimary = id == R.id.progress;
  
          if (isPrimary && animate) {
              final ObjectAnimator animator = ObjectAnimator.ofFloat(this, VISUAL_PROGRESS, scale);
              animator.setAutoCancel(true);
              animator.setDuration(PROGRESS_ANIM_DURATION);
              animator.setInterpolator(PROGRESS_ANIM_INTERPOLATOR);
              animator.start();
          } else {
              setVisualProgress(id, scale);
          }
  
          if (isPrimary && callBackToApp) {
              onProgressRefresh(scale, fromUser, progress);
          }
      }
  
      void onProgressRefresh(float scale, boolean fromUser, int progress) {
          if (AccessibilityManager.getInstance(mContext).isEnabled()) {
              scheduleAccessibilityEventSender();
          }
      }
  
      /**
       * Sets the visual state of a progress indicator.
       *
       * @param id the identifier of the progress indicator
       * @param progress the visual progress in the range [0...1]
       */
      private void setVisualProgress(int id, float progress) {
          mVisualProgress = progress;
  
          Drawable d = mCurrentDrawable;
  
          if (d instanceof LayerDrawable) {
              d = ((LayerDrawable) d).findDrawableByLayerId(id);
              if (d == null) {
                  // If we can't find the requested layer, fall back to setting
                  // the level of the entire drawable. This will break if
                  // progress is set on multiple elements, but the theme-default
                  // drawable will always have all layer IDs present.
                  d = mCurrentDrawable;
              }
          }
  
          if (d != null) {
              final int level = (int) (progress * MAX_LEVEL);
              d.setLevel(level);
          } else {
              invalidate();
          }
  
          onVisualProgressChanged(id, progress);
      }
  
      /**
       * Called when the visual state of a progress indicator changes.
       *
       * @param id the identifier of the progress indicator
       * @param progress the visual progress in the range [0...1]
       */
      void onVisualProgressChanged(int id, float progress) {
          // Stub method.
      }
  
      @UnsupportedAppUsage
      private synchronized void refreshProgress(int id, int progress, boolean fromUser,
              boolean animate) {
          if (mUiThreadId == Thread.currentThread().getId()) {
              doRefreshProgress(id, progress, fromUser, true, animate);
          } else {
              if (mRefreshProgressRunnable == null) {
                  mRefreshProgressRunnable = new RefreshProgressRunnable();
              }
  
              final RefreshData rd = RefreshData.obtain(id, progress, fromUser, animate);
              mRefreshData.add(rd);
              if (mAttached && !mRefreshIsPosted) {
                  post(mRefreshProgressRunnable);
                  mRefreshIsPosted = true;
              }
          }
      }
  
      /**
       * Sets the current progress to the specified value. Does not do anything
       * if the progress bar is in indeterminate mode.
       * <p>
       * This method will immediately update the visual position of the progress
       * indicator. To animate the visual position to the target value, use
       * {@link #setProgress(int, boolean)}}.
       *
       * @param progress the new progress, between {@link #getMin()} and {@link #getMax()}
       *
       * @see #setIndeterminate(boolean)
       * @see #isIndeterminate()
       * @see #getProgress()
       * @see #incrementProgressBy(int)
       */
      @android.view.RemotableViewMethod
      public synchronized void setProgress(int progress) {
          setProgressInternal(progress, false, false);
      }
  
      /**
       * Sets the current progress to the specified value, optionally animating
       * the visual position between the current and target values.
       * <p>
       * Animation does not affect the result of {@link #getProgress()}, which
       * will return the target value immediately after this method is called.
       *
       * @param progress the new progress value, between {@link #getMin()} and {@link #getMax()}
       * @param animate {@code true} to animate between the current and target
       *                values or {@code false} to not animate
       */
      public void setProgress(int progress, boolean animate) {
          setProgressInternal(progress, false, animate);
      }
  
      @android.view.RemotableViewMethod
      @UnsupportedAppUsage
      synchronized boolean setProgressInternal(int progress, boolean fromUser, boolean animate) {
          if (mIndeterminate) {
              // Not applicable.
              return false;
          }
  
          progress = MathUtils.constrain(progress, mMin, mMax);
  
          if (progress == mProgress) {
              // No change from current.
              return false;
          }
  
          mProgress = progress;
          refreshProgress(R.id.progress, mProgress, fromUser, animate);
          return true;
      }
  
      /**
       * <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 {@link #getMin()} and
       * {@link #getMax()}
       * @see #setIndeterminate(boolean)
       * @see #isIndeterminate()
       * @see #getSecondaryProgress()
       * @see #incrementSecondaryProgressBy(int)
       */
      @android.view.RemotableViewMethod
      public synchronized void setSecondaryProgress(int secondaryProgress) {
          if (mIndeterminate) {
              return;
          }
  
          if (secondaryProgress < mMin) {
              secondaryProgress = mMin;
          }
  
          if (secondaryProgress > mMax) {
              secondaryProgress = mMax;
          }
  
          if (secondaryProgress != mSecondaryProgress) {
              mSecondaryProgress = secondaryProgress;
              refreshProgress(R.id.secondaryProgress, mSecondaryProgress, false, 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 {@link #getMin()} and {@link #getMax()}
       *
       * @see #setIndeterminate(boolean)
       * @see #isIndeterminate()
       * @see #setProgress(int)
       * @see #setMax(int)
       * @see #getMax()
       */
      @ViewDebug.ExportedProperty(category = "progress")
      @InspectableProperty
      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 {@link #getMin()} and {@link #getMax()}
       *
       * @see #setIndeterminate(boolean)
       * @see #isIndeterminate()
       * @see #setSecondaryProgress(int)
       * @see #setMax(int)
       * @see #getMax()
       */
      @ViewDebug.ExportedProperty(category = "progress")
      @InspectableProperty
      public synchronized int getSecondaryProgress() {
          return mIndeterminate ? 0 : mSecondaryProgress;
      }
  
      /**
       * <p>Return the lower limit of this progress bar's range.</p>
       *
       * @return a positive integer
       *
       * @see #setMin(int)
       * @see #getProgress()
       * @see #getSecondaryProgress()
       */
      @ViewDebug.ExportedProperty(category = "progress")
      @InspectableProperty
      public synchronized int getMin() {
          return mMin;
      }
  
      /**
       * <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")
      @InspectableProperty
      public synchronized int getMax() {
          return mMax;
      }
  
      /**
       * <p>Set the lower range of the progress bar to <tt>min</tt>.</p>
       *
       * @param min the lower range of this progress bar
       *
       * @see #getMin()
       * @see #setProgress(int)
       * @see #setSecondaryProgress(int)
       */
      @android.view.RemotableViewMethod
      public synchronized void setMin(int min) {
          if (mMaxInitialized) {
              if (min > mMax) {
                  min = mMax;
              }
          }
          mMinInitialized = true;
          if (mMaxInitialized && min != mMin) {
              mMin = min;
              postInvalidate();
  
              if (mProgress < min) {
                  mProgress = min;
              }
              refreshProgress(R.id.progress, mProgress, false, false);
          } else {
              mMin = min;
          }
      }
  
      /**
       * <p>Set the upper range of the progress bar <tt>max</tt>.</p>
       *
       * @param max the upper range of this progress bar
       *
       * @see #getMax()
       * @see #setProgress(int)
       * @see #setSecondaryProgress(int)
       */
      @android.view.RemotableViewMethod
      public synchronized void setMax(int max) {
          if (mMinInitialized) {
              if (max < mMin) {
                  max = mMin;
              }
          }
          mMaxInitialized = true;
          if (mMinInitialized && max != mMax) {
              mMax = max;
              postInvalidate();
  
              if (mProgress > max) {
                  mProgress = max;
              }
              refreshProgress(R.id.progress, mProgress, false, false);
          } else {
              mMax = max;
          }
      }
  
      /**
       * <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>
       */
      @UnsupportedAppUsage
      void startAnimation() {
          if (getVisibility() != VISIBLE || getWindowVisibility() != VISIBLE) {
              return;
          }
  
          if (mIndeterminateDrawable instanceof Animatable) {
              mShouldStartAnimationDrawable = true;
              mHasAnimation = false;
          } else {
              mHasAnimation = true;
  
              if (mInterpolator == null) {
                  mInterpolator = new LinearInterpolator();
              }
  
              if (mTransformation == null) {
                  mTransformation = new Transformation();
              } else {
                  mTransformation.clear();
              }
  
              if (mAnimation == null) {
                  mAnimation = new AlphaAnimation(0.0f, 1.0f);
              } else {
                  mAnimation.reset();
              }
  
              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>
       */
      @UnsupportedAppUsage
      void stopAnimation() {
          mHasAnimation = false;
          if (mIndeterminateDrawable instanceof Animatable) {
              ((Animatable) mIndeterminateDrawable).stop();
              mShouldStartAnimationDrawable = false;
          }
          postInvalidate();
      }
  
      /**
       * Sets the acceleration curve for the indeterminate animation.
       *
       * <p>The interpolator is loaded as a resource from the specified context. Defaults to a linear
       * interpolation.
       *
       * <p>The interpolator only affects the indeterminate animation if the
       * {@link #setIndeterminateDrawable(Drawable) supplied indeterminate drawable} does not
       * implement {@link Animatable}.
       *
       * <p>This call must be made before the indeterminate animation starts for it to have an affect.
       *
       * @param context The application environment
       * @param resID The resource identifier of the interpolator to load
       * @attr ref android.R.styleable#ProgressBar_interpolator
       * @see #setInterpolator(Interpolator)
       * @see #getInterpolator()
       */
      public void setInterpolator(Context context, @InterpolatorRes int resID) {
          setInterpolator(AnimationUtils.loadInterpolator(context, resID));
      }
  
      /**
       * Sets the acceleration curve for the indeterminate animation.
       * Defaults to a linear interpolation.
       *
       * <p>The interpolator only affects the indeterminate animation if the
       * {@link #setIndeterminateDrawable(Drawable) supplied indeterminate drawable} does not
       * implement {@link Animatable}.
       *
       * <p>This call must be made before the indeterminate animation starts for it to have
       * an affect.
       *
       * @param interpolator The interpolator which defines the acceleration curve
       * @attr ref android.R.styleable#ProgressBar_interpolator
       * @see #setInterpolator(Context, int)
       * @see #getInterpolator()
       */
      public void setInterpolator(Interpolator interpolator) {
          mInterpolator = interpolator;
      }
  
      /**
       * Gets the acceleration curve type for the indeterminate animation.
       *
       * @return the {@link Interpolator} associated to this animation
       * @attr ref android.R.styleable#ProgressBar_interpolator
       * @see #setInterpolator(Context, int)
       * @see #setInterpolator(Interpolator)
       */
      @InspectableProperty
      public Interpolator getInterpolator() {
          return mInterpolator;
      }
  
      @Override
      public void onVisibilityAggregated(boolean isVisible) {
          super.onVisibilityAggregated(isVisible);
  
          if (isVisible != mAggregatedIsVisible) {
              mAggregatedIsVisible = isVisible;
  
              if (mIndeterminate) {
                  // let's be nice with the UI thread
                  if (isVisible) {
                      startAnimation();
                  } else {
                      stopAnimation();
                  }
              }
  
              if (mCurrentDrawable != null) {
                  mCurrentDrawable.setVisible(isVisible, false);
              }
          }
      }
  
      @Override
      public void invalidateDrawable(@NonNull Drawable dr) {
          if (!mInDrawing) {
              if (verifyDrawable(dr)) {
                  final Rect dirty = dr.getBounds();
                  final int scrollX = mScrollX + mPaddingLeft;
                  final int scrollY = mScrollY + mPaddingTop;
  
                  invalidate(dirty.left + scrollX, dirty.top + scrollY,
                          dirty.right + scrollX, dirty.bottom + scrollY);
              } else {
                  super.invalidateDrawable(dr);
              }
          }
      }
  
      @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.
          // Subtract out padding for the purposes of the calculations below.
          w -= mPaddingRight + mPaddingLeft;
          h -= mPaddingTop + mPaddingBottom;
  
          int right = w;
          int bottom = h;
          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;
                      }
                  }
              }
              if (isLayoutRtl() && mMirrorForRtl) {
                  int tempLeft = left;
                  left = w - right;
                  right = w - tempLeft;
              }
              mIndeterminateDrawable.setBounds(left, top, right, bottom);
          }
  
          if (mProgressDrawable != null) {
              mProgressDrawable.setBounds(0, 0, right, bottom);
          }
      }
  
      @Override
      protected synchronized void onDraw(Canvas canvas) {
          super.onDraw(canvas);
  
          drawTrack(canvas);
      }
  
      /**
       * Draws the progress bar track.
       */
      void drawTrack(Canvas canvas) {
          final Drawable d = mCurrentDrawable;
          if (d != null) {
              // Translate canvas so a indeterminate circular progress bar with padding
              // rotates properly in its animation
              final int saveCount = canvas.save();
  
              if (isLayoutRtl() && mMirrorForRtl) {
                  canvas.translate(getWidth() - mPaddingRight, mPaddingTop);
                  canvas.scale(-1.0f, 1.0f);
              } else {
                  canvas.translate(mPaddingLeft, mPaddingTop);
              }
  
              final long time = getDrawingTime();
              if (mHasAnimation) {
                  mAnimation.getTransformation(time, mTransformation);
                  final float scale = mTransformation.getAlpha();
                  try {
                      mInDrawing = true;
                      d.setLevel((int) (scale * MAX_LEVEL));
                  } finally {
                      mInDrawing = false;
                  }
                  postInvalidateOnAnimation();
              }
  
              d.draw(canvas);
              canvas.restoreToCount(saveCount);
  
              if (mShouldStartAnimationDrawable && d instanceof Animatable) {
                  ((Animatable) d).start();
                  mShouldStartAnimationDrawable = false;
              }
          }
      }
  
      @Override
      protected synchronized void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
          int dw = 0;
          int dh = 0;
  
          final Drawable d = mCurrentDrawable;
          if (d != null) {
              dw = Math.max(mMinWidth, Math.min(mMaxWidth, d.getIntrinsicWidth()));
              dh = Math.max(mMinHeight, Math.min(mMaxHeight, d.getIntrinsicHeight()));
          }
  
          updateDrawableState();
  
          dw += mPaddingLeft + mPaddingRight;
          dh += mPaddingTop + mPaddingBottom;
  
          final int measuredWidth = resolveSizeAndState(dw, widthMeasureSpec, 0);
          final int measuredHeight = resolveSizeAndState(dh, heightMeasureSpec, 0);
          setMeasuredDimension(measuredWidth, measuredHeight);
      }
  
      @Override
      protected void drawableStateChanged() {
          super.drawableStateChanged();
          updateDrawableState();
      }
  
      private void updateDrawableState() {
          final int[] state = getDrawableState();
          boolean changed = false;
  
          final Drawable progressDrawable = mProgressDrawable;
          if (progressDrawable != null && progressDrawable.isStateful()) {
              changed |= progressDrawable.setState(state);
          }
  
          final Drawable indeterminateDrawable = mIndeterminateDrawable;
          if (indeterminateDrawable != null && indeterminateDrawable.isStateful()) {
              changed |= indeterminateDrawable.setState(state);
          }
  
          if (changed) {
              invalidate();
          }
      }
  
      @Override
      public void drawableHotspotChanged(float x, float y) {
          super.drawableHotspotChanged(x, y);
  
          if (mProgressDrawable != null) {
              mProgressDrawable.setHotspot(x, y);
          }
  
          if (mIndeterminateDrawable != null) {
              mIndeterminateDrawable.setHotspot(x, y);
          }
      }
  
      static class SavedState extends BaseSavedState {
          int progress;
          int secondaryProgress;
  
          /**
           * Constructor called from {@link ProgressBar#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 @android.annotation.NonNull 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();
          }
          if (mRefreshData != null) {
              synchronized (this) {
                  final int count = mRefreshData.size();
                  for (int i = 0; i < count; i++) {
                      final RefreshData rd = mRefreshData.get(i);
                      doRefreshProgress(rd.id, rd.progress, rd.fromUser, true, rd.animate);
                      rd.recycle();
                  }
                  mRefreshData.clear();
              }
          }
          mAttached = true;
      }
  
      @Override
      protected void onDetachedFromWindow() {
          if (mIndeterminate) {
              stopAnimation();
          }
          if (mRefreshProgressRunnable != null) {
              removeCallbacks(mRefreshProgressRunnable);
              mRefreshIsPosted = false;
          }
          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();
          mAttached = false;
      }
  
      @Override
      public CharSequence getAccessibilityClassName() {
          return ProgressBar.class.getName();
      }
  
      /** @hide */
      @Override
      public void onInitializeAccessibilityEventInternal(AccessibilityEvent event) {
          super.onInitializeAccessibilityEventInternal(event);
          event.setItemCount(mMax - mMin);
          event.setCurrentItemIndex(mProgress);
      }
  
      /** @hide */
      @Override
      public void onInitializeAccessibilityNodeInfoInternal(AccessibilityNodeInfo info) {
          super.onInitializeAccessibilityNodeInfoInternal(info);
  
          if (!isIndeterminate()) {
              AccessibilityNodeInfo.RangeInfo rangeInfo = AccessibilityNodeInfo.RangeInfo.obtain(
                      AccessibilityNodeInfo.RangeInfo.RANGE_TYPE_INT, getMin(), getMax(),
                      getProgress());
              info.setRangeInfo(rangeInfo);
          }
      }
  
      /**
       * 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);
      }
  
      /** @hide */
      @Override
      protected void encodeProperties(@NonNull ViewHierarchyEncoder stream) {
          super.encodeProperties(stream);
  
          stream.addProperty("progress:max", getMax());
          stream.addProperty("progress:progress", getProgress());
          stream.addProperty("progress:secondaryProgress", getSecondaryProgress());
          stream.addProperty("progress:indeterminate", isIndeterminate());
      }
  
      /**
       * Returns whether the ProgressBar is animating or not. This is essentially the same
       * as whether the ProgressBar is {@link #isIndeterminate() indeterminate} and visible,
       * as indeterminate ProgressBars are always animating, and non-indeterminate
       * ProgressBars are not animating.
       *
       * @return true if the ProgressBar is animating, false otherwise.
       */
      public boolean isAnimating() {
          return isIndeterminate() && getWindowVisibility() == VISIBLE && isShown();
      }
  
      /**
       * Command for sending an accessibility event.
       */
      private class AccessibilityEventSender implements Runnable {
          public void run() {
              sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_SELECTED);
          }
      }
  
      private static class ProgressTintInfo {
          ColorStateList mIndeterminateTintList;
          BlendMode mIndeterminateBlendMode;
          boolean mHasIndeterminateTint;
          boolean mHasIndeterminateTintMode;
  
          ColorStateList mProgressTintList;
          BlendMode mProgressBlendMode;
          boolean mHasProgressTint;
          boolean mHasProgressTintMode;
  
          ColorStateList mProgressBackgroundTintList;
          BlendMode mProgressBackgroundBlendMode;
          boolean mHasProgressBackgroundTint;
          boolean mHasProgressBackgroundTintMode;
  
          ColorStateList mSecondaryProgressTintList;
          BlendMode mSecondaryProgressBlendMode;
          boolean mHasSecondaryProgressTint;
          boolean mHasSecondaryProgressTintMode;
      }
  
      /**
       * Property wrapper around the visual state of the {@code progress} functionality
       * handled by the {@link ProgressBar#setProgress(int, boolean)} method. This does
       * not correspond directly to the actual progress -- only the visual state.
       */
      private final FloatProperty<ProgressBar> VISUAL_PROGRESS =
              new FloatProperty<ProgressBar>("visual_progress") {
                  @Override
                  public void setValue(ProgressBar object, float value) {
                      object.setVisualProgress(R.id.progress, value);
                      object.mVisualProgress = value;
                  }
  
                  @Override
                  public Float get(ProgressBar object) {
                      return object.mVisualProgress;
                  }
              };
  }
  