1/*
2 * Copyright (C) 2006 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package android.widget;
18
19import android.animation.ObjectAnimator;
20import android.annotation.InterpolatorRes;
21import android.annotation.NonNull;
22import android.annotation.Nullable;
23import android.content.Context;
24import android.content.res.ColorStateList;
25import android.content.res.TypedArray;
26import android.graphics.Bitmap;
27import android.graphics.Canvas;
28import android.graphics.PorterDuff;
29import android.graphics.Rect;
30import android.graphics.Shader;
31import android.graphics.drawable.Animatable;
32import android.graphics.drawable.AnimationDrawable;
33import android.graphics.drawable.BitmapDrawable;
34import android.graphics.drawable.ClipDrawable;
35import android.graphics.drawable.Drawable;
36import android.graphics.drawable.LayerDrawable;
37import android.graphics.drawable.StateListDrawable;
38import android.graphics.drawable.shapes.RoundRectShape;
39import android.graphics.drawable.shapes.Shape;
40import android.os.Parcel;
41import android.os.Parcelable;
42import android.util.AttributeSet;
43import android.util.FloatProperty;
44import android.util.MathUtils;
45import android.util.Pools.SynchronizedPool;
46import android.view.Gravity;
47import android.view.RemotableViewMethod;
48import android.view.View;
49import android.view.ViewDebug;
50import android.view.ViewHierarchyEncoder;
51import android.view.accessibility.AccessibilityEvent;
52import android.view.accessibility.AccessibilityManager;
53import android.view.accessibility.AccessibilityNodeInfo;
54import android.view.animation.AlphaAnimation;
55import android.view.animation.Animation;
56import android.view.animation.AnimationUtils;
57import android.view.animation.DecelerateInterpolator;
58import android.view.animation.Interpolator;
59import android.view.animation.LinearInterpolator;
60import android.view.animation.Transformation;
61import android.widget.RemoteViews.RemoteView;
62import com.android.internal.R;
63
64import java.util.ArrayList;
65
66/**
67 * <p>
68 * Visual indicator of progress in some operation.  Displays a bar to the user
69 * representing how far the operation has progressed; the application can
70 * change the amount of progress (modifying the length of the bar) as it moves
71 * forward.  There is also a secondary progress displayable on a progress bar
72 * which is useful for displaying intermediate progress, such as the buffer
73 * level during a streaming playback progress bar.
74 * </p>
75 *
76 * <p>
77 * A progress bar can also be made indeterminate. In indeterminate mode, the
78 * progress bar shows a cyclic animation without an indication of progress. This mode is used by
79 * applications when the length of the task is unknown. The indeterminate progress bar can be either
80 * a spinning wheel or a horizontal bar.
81 * </p>
82 *
83 * <p>The following code example shows how a progress bar can be used from
84 * a worker thread to update the user interface to notify the user of progress:
85 * </p>
86 *
87 * <pre>
88 * public class MyActivity extends Activity {
89 *     private static final int PROGRESS = 0x1;
90 *
91 *     private ProgressBar mProgress;
92 *     private int mProgressStatus = 0;
93 *
94 *     private Handler mHandler = new Handler();
95 *
96 *     protected void onCreate(Bundle icicle) {
97 *         super.onCreate(icicle);
98 *
99 *         setContentView(R.layout.progressbar_activity);
100 *
101 *         mProgress = (ProgressBar) findViewById(R.id.progress_bar);
102 *
103 *         // Start lengthy operation in a background thread
104 *         new Thread(new Runnable() {
105 *             public void run() {
106 *                 while (mProgressStatus &lt; 100) {
107 *                     mProgressStatus = doWork();
108 *
109 *                     // Update the progress bar
110 *                     mHandler.post(new Runnable() {
111 *                         public void run() {
112 *                             mProgress.setProgress(mProgressStatus);
113 *                         }
114 *                     });
115 *                 }
116 *             }
117 *         }).start();
118 *     }
119 * }</pre>
120 *
121 * <p>To add a progress bar to a layout file, you can use the {@code <ProgressBar>} element.
122 * By default, the progress bar is a spinning wheel (an indeterminate indicator). To change to a
123 * horizontal progress bar, apply the {@link android.R.style#Widget_ProgressBar_Horizontal
124 * Widget.ProgressBar.Horizontal} style, like so:</p>
125 *
126 * <pre>
127 * &lt;ProgressBar
128 *     style="@android:style/Widget.ProgressBar.Horizontal"
129 *     ... /&gt;</pre>
130 *
131 * <p>If you will use the progress bar to show real progress, you must use the horizontal bar. You
132 * can then increment the  progress with {@link #incrementProgressBy incrementProgressBy()} or
133 * {@link #setProgress setProgress()}. By default, the progress bar is full when it reaches 100. If
134 * necessary, you can adjust the maximum value (the value for a full bar) using the {@link
135 * android.R.styleable#ProgressBar_max android:max} attribute. Other attributes available are listed
136 * below.</p>
137 *
138 * <p>Another common style to apply to the progress bar is {@link
139 * android.R.style#Widget_ProgressBar_Small Widget.ProgressBar.Small}, which shows a smaller
140 * version of the spinning wheel&mdash;useful when waiting for content to load.
141 * For example, you can insert this kind of progress bar into your default layout for
142 * a view that will be populated by some content fetched from the Internet&mdash;the spinning wheel
143 * appears immediately and when your application receives the content, it replaces the progress bar
144 * with the loaded content. For example:</p>
145 *
146 * <pre>
147 * &lt;LinearLayout
148 *     android:orientation="horizontal"
149 *     ... &gt;
150 *     &lt;ProgressBar
151 *         android:layout_width="wrap_content"
152 *         android:layout_height="wrap_content"
153 *         style="@android:style/Widget.ProgressBar.Small"
154 *         android:layout_marginRight="5dp" /&gt;
155 *     &lt;TextView
156 *         android:layout_width="wrap_content"
157 *         android:layout_height="wrap_content"
158 *         android:text="@string/loading" /&gt;
159 * &lt;/LinearLayout&gt;</pre>
160 *
161 * <p>Other progress bar styles provided by the system include:</p>
162 * <ul>
163 * <li>{@link android.R.style#Widget_ProgressBar_Horizontal Widget.ProgressBar.Horizontal}</li>
164 * <li>{@link android.R.style#Widget_ProgressBar_Small Widget.ProgressBar.Small}</li>
165 * <li>{@link android.R.style#Widget_ProgressBar_Large Widget.ProgressBar.Large}</li>
166 * <li>{@link android.R.style#Widget_ProgressBar_Inverse Widget.ProgressBar.Inverse}</li>
167 * <li>{@link android.R.style#Widget_ProgressBar_Small_Inverse
168 * Widget.ProgressBar.Small.Inverse}</li>
169 * <li>{@link android.R.style#Widget_ProgressBar_Large_Inverse
170 * Widget.ProgressBar.Large.Inverse}</li>
171 * </ul>
172 * <p>The "inverse" styles provide an inverse color scheme for the spinner, which may be necessary
173 * if your application uses a light colored theme (a white background).</p>
174 *
175 * <p><strong>XML attributes</b></strong>
176 * <p>
177 * See {@link android.R.styleable#ProgressBar ProgressBar Attributes},
178 * {@link android.R.styleable#View View Attributes}
179 * </p>
180 *
181 * @attr ref android.R.styleable#ProgressBar_animationResolution
182 * @attr ref android.R.styleable#ProgressBar_indeterminate
183 * @attr ref android.R.styleable#ProgressBar_indeterminateBehavior
184 * @attr ref android.R.styleable#ProgressBar_indeterminateDrawable
185 * @attr ref android.R.styleable#ProgressBar_indeterminateDuration
186 * @attr ref android.R.styleable#ProgressBar_indeterminateOnly
187 * @attr ref android.R.styleable#ProgressBar_interpolator
188 * @attr ref android.R.styleable#ProgressBar_max
189 * @attr ref android.R.styleable#ProgressBar_maxHeight
190 * @attr ref android.R.styleable#ProgressBar_maxWidth
191 * @attr ref android.R.styleable#ProgressBar_minHeight
192 * @attr ref android.R.styleable#ProgressBar_minWidth
193 * @attr ref android.R.styleable#ProgressBar_mirrorForRtl
194 * @attr ref android.R.styleable#ProgressBar_progress
195 * @attr ref android.R.styleable#ProgressBar_progressDrawable
196 * @attr ref android.R.styleable#ProgressBar_secondaryProgress
197 */
198@RemoteView
199public class ProgressBar extends View {
200
201    private static final int MAX_LEVEL = 10000;
202    private static final int TIMEOUT_SEND_ACCESSIBILITY_EVENT = 200;
203
204    /** Interpolator used for smooth progress animations. */
205    private static final DecelerateInterpolator PROGRESS_ANIM_INTERPOLATOR =
206            new DecelerateInterpolator();
207
208    /** Duration of smooth progress animations. */
209    private static final int PROGRESS_ANIM_DURATION = 80;
210
211    int mMinWidth;
212    int mMaxWidth;
213    int mMinHeight;
214    int mMaxHeight;
215
216    private int mProgress;
217    private int mSecondaryProgress;
218    private int mMax;
219
220    private int mBehavior;
221    private int mDuration;
222    private boolean mIndeterminate;
223    private boolean mOnlyIndeterminate;
224    private Transformation mTransformation;
225    private AlphaAnimation mAnimation;
226    private boolean mHasAnimation;
227
228    private Drawable mIndeterminateDrawable;
229    private Drawable mProgressDrawable;
230    private Drawable mCurrentDrawable;
231    private ProgressTintInfo mProgressTintInfo;
232
233    Bitmap mSampleTile;
234    private boolean mNoInvalidate;
235    private Interpolator mInterpolator;
236    private RefreshProgressRunnable mRefreshProgressRunnable;
237    private long mUiThreadId;
238    private boolean mShouldStartAnimationDrawable;
239
240    private boolean mInDrawing;
241    private boolean mAttached;
242    private boolean mRefreshIsPosted;
243
244    /** Value used to track progress animation, in the range [0...1]. */
245    private float mVisualProgress;
246
247    boolean mMirrorForRtl = false;
248
249    private boolean mAggregatedIsVisible;
250
251    private final ArrayList<RefreshData> mRefreshData = new ArrayList<RefreshData>();
252
253    private AccessibilityEventSender mAccessibilityEventSender;
254
255    /**
256     * Create a new progress bar with range 0...100 and initial progress of 0.
257     * @param context the application environment
258     */
259    public ProgressBar(Context context) {
260        this(context, null);
261    }
262
263    public ProgressBar(Context context, AttributeSet attrs) {
264        this(context, attrs, com.android.internal.R.attr.progressBarStyle);
265    }
266
267    public ProgressBar(Context context, AttributeSet attrs, int defStyleAttr) {
268        this(context, attrs, defStyleAttr, 0);
269    }
270
271    public ProgressBar(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
272        super(context, attrs, defStyleAttr, defStyleRes);
273
274        mUiThreadId = Thread.currentThread().getId();
275        initProgressBar();
276
277        final TypedArray a = context.obtainStyledAttributes(
278                attrs, R.styleable.ProgressBar, defStyleAttr, defStyleRes);
279
280        mNoInvalidate = true;
281
282        final Drawable progressDrawable = a.getDrawable(R.styleable.ProgressBar_progressDrawable);
283        if (progressDrawable != null) {
284            // Calling setProgressDrawable can set mMaxHeight, so make sure the
285            // corresponding XML attribute for mMaxHeight is read after calling
286            // this method.
287            if (needsTileify(progressDrawable)) {
288                setProgressDrawableTiled(progressDrawable);
289            } else {
290                setProgressDrawable(progressDrawable);
291            }
292        }
293
294
295        mDuration = a.getInt(R.styleable.ProgressBar_indeterminateDuration, mDuration);
296
297        mMinWidth = a.getDimensionPixelSize(R.styleable.ProgressBar_minWidth, mMinWidth);
298        mMaxWidth = a.getDimensionPixelSize(R.styleable.ProgressBar_maxWidth, mMaxWidth);
299        mMinHeight = a.getDimensionPixelSize(R.styleable.ProgressBar_minHeight, mMinHeight);
300        mMaxHeight = a.getDimensionPixelSize(R.styleable.ProgressBar_maxHeight, mMaxHeight);
301
302        mBehavior = a.getInt(R.styleable.ProgressBar_indeterminateBehavior, mBehavior);
303
304        final int resID = a.getResourceId(
305                com.android.internal.R.styleable.ProgressBar_interpolator,
306                android.R.anim.linear_interpolator); // default to linear interpolator
307        if (resID > 0) {
308            setInterpolator(context, resID);
309        }
310
311        setMax(a.getInt(R.styleable.ProgressBar_max, mMax));
312
313        setProgress(a.getInt(R.styleable.ProgressBar_progress, mProgress));
314
315        setSecondaryProgress(a.getInt(
316                R.styleable.ProgressBar_secondaryProgress, mSecondaryProgress));
317
318        final Drawable indeterminateDrawable = a.getDrawable(
319                R.styleable.ProgressBar_indeterminateDrawable);
320        if (indeterminateDrawable != null) {
321            if (needsTileify(indeterminateDrawable)) {
322                setIndeterminateDrawableTiled(indeterminateDrawable);
323            } else {
324                setIndeterminateDrawable(indeterminateDrawable);
325            }
326        }
327
328        mOnlyIndeterminate = a.getBoolean(
329                R.styleable.ProgressBar_indeterminateOnly, mOnlyIndeterminate);
330
331        mNoInvalidate = false;
332
333        setIndeterminate(mOnlyIndeterminate || a.getBoolean(
334                R.styleable.ProgressBar_indeterminate, mIndeterminate));
335
336        mMirrorForRtl = a.getBoolean(R.styleable.ProgressBar_mirrorForRtl, mMirrorForRtl);
337
338        if (a.hasValue(R.styleable.ProgressBar_progressTintMode)) {
339            if (mProgressTintInfo == null) {
340                mProgressTintInfo = new ProgressTintInfo();
341            }
342            mProgressTintInfo.mProgressTintMode = Drawable.parseTintMode(a.getInt(
343                    R.styleable.ProgressBar_progressTintMode, -1), null);
344            mProgressTintInfo.mHasProgressTintMode = true;
345        }
346
347        if (a.hasValue(R.styleable.ProgressBar_progressTint)) {
348            if (mProgressTintInfo == null) {
349                mProgressTintInfo = new ProgressTintInfo();
350            }
351            mProgressTintInfo.mProgressTintList = a.getColorStateList(
352                    R.styleable.ProgressBar_progressTint);
353            mProgressTintInfo.mHasProgressTint = true;
354        }
355
356        if (a.hasValue(R.styleable.ProgressBar_progressBackgroundTintMode)) {
357            if (mProgressTintInfo == null) {
358                mProgressTintInfo = new ProgressTintInfo();
359            }
360            mProgressTintInfo.mProgressBackgroundTintMode = Drawable.parseTintMode(a.getInt(
361                    R.styleable.ProgressBar_progressBackgroundTintMode, -1), null);
362            mProgressTintInfo.mHasProgressBackgroundTintMode = true;
363        }
364
365        if (a.hasValue(R.styleable.ProgressBar_progressBackgroundTint)) {
366            if (mProgressTintInfo == null) {
367                mProgressTintInfo = new ProgressTintInfo();
368            }
369            mProgressTintInfo.mProgressBackgroundTintList = a.getColorStateList(
370                    R.styleable.ProgressBar_progressBackgroundTint);
371            mProgressTintInfo.mHasProgressBackgroundTint = true;
372        }
373
374        if (a.hasValue(R.styleable.ProgressBar_secondaryProgressTintMode)) {
375            if (mProgressTintInfo == null) {
376                mProgressTintInfo = new ProgressTintInfo();
377            }
378            mProgressTintInfo.mSecondaryProgressTintMode = Drawable.parseTintMode(
379                    a.getInt(R.styleable.ProgressBar_secondaryProgressTintMode, -1), null);
380            mProgressTintInfo.mHasSecondaryProgressTintMode = true;
381        }
382
383        if (a.hasValue(R.styleable.ProgressBar_secondaryProgressTint)) {
384            if (mProgressTintInfo == null) {
385                mProgressTintInfo = new ProgressTintInfo();
386            }
387            mProgressTintInfo.mSecondaryProgressTintList = a.getColorStateList(
388                    R.styleable.ProgressBar_secondaryProgressTint);
389            mProgressTintInfo.mHasSecondaryProgressTint = true;
390        }
391
392        if (a.hasValue(R.styleable.ProgressBar_indeterminateTintMode)) {
393            if (mProgressTintInfo == null) {
394                mProgressTintInfo = new ProgressTintInfo();
395            }
396            mProgressTintInfo.mIndeterminateTintMode = Drawable.parseTintMode(a.getInt(
397                    R.styleable.ProgressBar_indeterminateTintMode, -1), null);
398            mProgressTintInfo.mHasIndeterminateTintMode = true;
399        }
400
401        if (a.hasValue(R.styleable.ProgressBar_indeterminateTint)) {
402            if (mProgressTintInfo == null) {
403                mProgressTintInfo = new ProgressTintInfo();
404            }
405            mProgressTintInfo.mIndeterminateTintList = a.getColorStateList(
406                    R.styleable.ProgressBar_indeterminateTint);
407            mProgressTintInfo.mHasIndeterminateTint = true;
408        }
409
410        a.recycle();
411
412        applyProgressTints();
413        applyIndeterminateTint();
414
415        // If not explicitly specified this view is important for accessibility.
416        if (getImportantForAccessibility() == View.IMPORTANT_FOR_ACCESSIBILITY_AUTO) {
417            setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_YES);
418        }
419    }
420
421    /**
422     * Returns {@code true} if the target drawable needs to be tileified.
423     *
424     * @param dr the drawable to check
425     * @return {@code true} if the target drawable needs to be tileified,
426     *         {@code false} otherwise
427     */
428    private static boolean needsTileify(Drawable dr) {
429        if (dr instanceof LayerDrawable) {
430            final LayerDrawable orig = (LayerDrawable) dr;
431            final int N = orig.getNumberOfLayers();
432            for (int i = 0; i < N; i++) {
433                if (needsTileify(orig.getDrawable(i))) {
434                    return true;
435                }
436            }
437            return false;
438        }
439
440        if (dr instanceof StateListDrawable) {
441            final StateListDrawable in = (StateListDrawable) dr;
442            final int N = in.getStateCount();
443            for (int i = 0; i < N; i++) {
444                if (needsTileify(in.getStateDrawable(i))) {
445                    return true;
446                }
447            }
448            return false;
449        }
450
451        // If there's a bitmap that's not wrapped with a ClipDrawable or
452        // ScaleDrawable, we'll need to wrap it and apply tiling.
453        if (dr instanceof BitmapDrawable) {
454            return true;
455        }
456
457        return false;
458    }
459
460    /**
461     * Converts a drawable to a tiled version of itself. It will recursively
462     * traverse layer and state list drawables.
463     */
464    private Drawable tileify(Drawable drawable, boolean clip) {
465        // TODO: This is a terrible idea that potentially destroys any drawable
466        // that extends any of these classes. We *really* need to remove this.
467
468        if (drawable instanceof LayerDrawable) {
469            final LayerDrawable orig = (LayerDrawable) drawable;
470            final int N = orig.getNumberOfLayers();
471            final Drawable[] outDrawables = new Drawable[N];
472
473            for (int i = 0; i < N; i++) {
474                final int id = orig.getId(i);
475                outDrawables[i] = tileify(orig.getDrawable(i),
476                        (id == R.id.progress || id == R.id.secondaryProgress));
477            }
478
479            final LayerDrawable clone = new LayerDrawable(outDrawables);
480            for (int i = 0; i < N; i++) {
481                clone.setId(i, orig.getId(i));
482                clone.setLayerGravity(i, orig.getLayerGravity(i));
483                clone.setLayerWidth(i, orig.getLayerWidth(i));
484                clone.setLayerHeight(i, orig.getLayerHeight(i));
485                clone.setLayerInsetLeft(i, orig.getLayerInsetLeft(i));
486                clone.setLayerInsetRight(i, orig.getLayerInsetRight(i));
487                clone.setLayerInsetTop(i, orig.getLayerInsetTop(i));
488                clone.setLayerInsetBottom(i, orig.getLayerInsetBottom(i));
489                clone.setLayerInsetStart(i, orig.getLayerInsetStart(i));
490                clone.setLayerInsetEnd(i, orig.getLayerInsetEnd(i));
491            }
492
493            return clone;
494        }
495
496        if (drawable instanceof StateListDrawable) {
497            final StateListDrawable in = (StateListDrawable) drawable;
498            final StateListDrawable out = new StateListDrawable();
499            final int N = in.getStateCount();
500            for (int i = 0; i < N; i++) {
501                out.addState(in.getStateSet(i), tileify(in.getStateDrawable(i), clip));
502            }
503
504            return out;
505        }
506
507        if (drawable instanceof BitmapDrawable) {
508            final BitmapDrawable bitmap = (BitmapDrawable) drawable;
509            final Bitmap tileBitmap = bitmap.getBitmap();
510            if (mSampleTile == null) {
511                mSampleTile = tileBitmap;
512            }
513
514            final BitmapDrawable clone = (BitmapDrawable) bitmap.getConstantState().newDrawable();
515            clone.setTileModeXY(Shader.TileMode.REPEAT, Shader.TileMode.CLAMP);
516
517            if (clip) {
518                return new ClipDrawable(clone, Gravity.LEFT, ClipDrawable.HORIZONTAL);
519            } else {
520                return clone;
521            }
522        }
523
524        return drawable;
525    }
526
527    Shape getDrawableShape() {
528        final float[] roundedCorners = new float[] { 5, 5, 5, 5, 5, 5, 5, 5 };
529        return new RoundRectShape(roundedCorners, null, null);
530    }
531
532    /**
533     * Convert a AnimationDrawable for use as a barberpole animation.
534     * Each frame of the animation is wrapped in a ClipDrawable and
535     * given a tiling BitmapShader.
536     */
537    private Drawable tileifyIndeterminate(Drawable drawable) {
538        if (drawable instanceof AnimationDrawable) {
539            AnimationDrawable background = (AnimationDrawable) drawable;
540            final int N = background.getNumberOfFrames();
541            AnimationDrawable newBg = new AnimationDrawable();
542            newBg.setOneShot(background.isOneShot());
543
544            for (int i = 0; i < N; i++) {
545                Drawable frame = tileify(background.getFrame(i), true);
546                frame.setLevel(10000);
547                newBg.addFrame(frame, background.getDuration(i));
548            }
549            newBg.setLevel(10000);
550            drawable = newBg;
551        }
552        return drawable;
553    }
554
555    /**
556     * <p>
557     * Initialize the progress bar's default values:
558     * </p>
559     * <ul>
560     * <li>progress = 0</li>
561     * <li>max = 100</li>
562     * <li>animation duration = 4000 ms</li>
563     * <li>indeterminate = false</li>
564     * <li>behavior = repeat</li>
565     * </ul>
566     */
567    private void initProgressBar() {
568        mMax = 100;
569        mProgress = 0;
570        mSecondaryProgress = 0;
571        mIndeterminate = false;
572        mOnlyIndeterminate = false;
573        mDuration = 4000;
574        mBehavior = AlphaAnimation.RESTART;
575        mMinWidth = 24;
576        mMaxWidth = 48;
577        mMinHeight = 24;
578        mMaxHeight = 48;
579    }
580
581    /**
582     * <p>Indicate whether this progress bar is in indeterminate mode.</p>
583     *
584     * @return true if the progress bar is in indeterminate mode
585     */
586    @ViewDebug.ExportedProperty(category = "progress")
587    public synchronized boolean isIndeterminate() {
588        return mIndeterminate;
589    }
590
591    /**
592     * <p>Change the indeterminate mode for this progress bar. In indeterminate
593     * mode, the progress is ignored and the progress bar shows an infinite
594     * animation instead.</p>
595     *
596     * If this progress bar's style only supports indeterminate mode (such as the circular
597     * progress bars), then this will be ignored.
598     *
599     * @param indeterminate true to enable the indeterminate mode
600     */
601    @android.view.RemotableViewMethod
602    public synchronized void setIndeterminate(boolean indeterminate) {
603        if ((!mOnlyIndeterminate || !mIndeterminate) && indeterminate != mIndeterminate) {
604            mIndeterminate = indeterminate;
605
606            if (indeterminate) {
607                // swap between indeterminate and regular backgrounds
608                swapCurrentDrawable(mIndeterminateDrawable);
609                startAnimation();
610            } else {
611                swapCurrentDrawable(mProgressDrawable);
612                stopAnimation();
613            }
614        }
615    }
616
617    private void swapCurrentDrawable(Drawable newDrawable) {
618        final Drawable oldDrawable = mCurrentDrawable;
619        mCurrentDrawable = newDrawable;
620
621        if (oldDrawable != mCurrentDrawable) {
622            if (oldDrawable != null) {
623                oldDrawable.setVisible(false, false);
624            }
625            if (mCurrentDrawable != null) {
626                mCurrentDrawable.setVisible(getWindowVisibility() == VISIBLE && isShown(), false);
627            }
628        }
629    }
630
631    /**
632     * <p>Get the drawable used to draw the progress bar in
633     * indeterminate mode.</p>
634     *
635     * @return a {@link android.graphics.drawable.Drawable} instance
636     *
637     * @see #setIndeterminateDrawable(android.graphics.drawable.Drawable)
638     * @see #setIndeterminate(boolean)
639     */
640    public Drawable getIndeterminateDrawable() {
641        return mIndeterminateDrawable;
642    }
643
644    /**
645     * Define the drawable used to draw the progress bar in indeterminate mode.
646     *
647     * @param d the new drawable
648     * @see #getIndeterminateDrawable()
649     * @see #setIndeterminate(boolean)
650     */
651    public void setIndeterminateDrawable(Drawable d) {
652        if (mIndeterminateDrawable != d) {
653            if (mIndeterminateDrawable != null) {
654                mIndeterminateDrawable.setCallback(null);
655                unscheduleDrawable(mIndeterminateDrawable);
656            }
657
658            mIndeterminateDrawable = d;
659
660            if (d != null) {
661                d.setCallback(this);
662                d.setLayoutDirection(getLayoutDirection());
663                if (d.isStateful()) {
664                    d.setState(getDrawableState());
665                }
666                applyIndeterminateTint();
667            }
668
669            if (mIndeterminate) {
670                swapCurrentDrawable(d);
671                postInvalidate();
672            }
673        }
674    }
675
676    /**
677     * Applies a tint to the indeterminate drawable. Does not modify the
678     * current tint mode, which is {@link PorterDuff.Mode#SRC_IN} by default.
679     * <p>
680     * Subsequent calls to {@link #setIndeterminateDrawable(Drawable)} will
681     * automatically mutate the drawable and apply the specified tint and
682     * tint mode using
683     * {@link Drawable#setTintList(ColorStateList)}.
684     *
685     * @param tint the tint to apply, may be {@code null} to clear tint
686     *
687     * @attr ref android.R.styleable#ProgressBar_indeterminateTint
688     * @see #getIndeterminateTintList()
689     * @see Drawable#setTintList(ColorStateList)
690     */
691    @RemotableViewMethod
692    public void setIndeterminateTintList(@Nullable ColorStateList tint) {
693        if (mProgressTintInfo == null) {
694            mProgressTintInfo = new ProgressTintInfo();
695        }
696        mProgressTintInfo.mIndeterminateTintList = tint;
697        mProgressTintInfo.mHasIndeterminateTint = true;
698
699        applyIndeterminateTint();
700    }
701
702    /**
703     * @return the tint applied to the indeterminate drawable
704     * @attr ref android.R.styleable#ProgressBar_indeterminateTint
705     * @see #setIndeterminateTintList(ColorStateList)
706     */
707    @Nullable
708    public ColorStateList getIndeterminateTintList() {
709        return mProgressTintInfo != null ? mProgressTintInfo.mIndeterminateTintList : null;
710    }
711
712    /**
713     * Specifies the blending mode used to apply the tint specified by
714     * {@link #setIndeterminateTintList(ColorStateList)} to the indeterminate
715     * drawable. The default mode is {@link PorterDuff.Mode#SRC_IN}.
716     *
717     * @param tintMode the blending mode used to apply the tint, may be
718     *                 {@code null} to clear tint
719     * @attr ref android.R.styleable#ProgressBar_indeterminateTintMode
720     * @see #setIndeterminateTintList(ColorStateList)
721     * @see Drawable#setTintMode(PorterDuff.Mode)
722     */
723    public void setIndeterminateTintMode(@Nullable PorterDuff.Mode tintMode) {
724        if (mProgressTintInfo == null) {
725            mProgressTintInfo = new ProgressTintInfo();
726        }
727        mProgressTintInfo.mIndeterminateTintMode = tintMode;
728        mProgressTintInfo.mHasIndeterminateTintMode = true;
729
730        applyIndeterminateTint();
731    }
732
733    /**
734     * Returns the blending mode used to apply the tint to the indeterminate
735     * drawable, if specified.
736     *
737     * @return the blending mode used to apply the tint to the indeterminate
738     *         drawable
739     * @attr ref android.R.styleable#ProgressBar_indeterminateTintMode
740     * @see #setIndeterminateTintMode(PorterDuff.Mode)
741     */
742    @Nullable
743    public PorterDuff.Mode getIndeterminateTintMode() {
744        return mProgressTintInfo != null ? mProgressTintInfo.mIndeterminateTintMode : null;
745    }
746
747    private void applyIndeterminateTint() {
748        if (mIndeterminateDrawable != null && mProgressTintInfo != null) {
749            final ProgressTintInfo tintInfo = mProgressTintInfo;
750            if (tintInfo.mHasIndeterminateTint || tintInfo.mHasIndeterminateTintMode) {
751                mIndeterminateDrawable = mIndeterminateDrawable.mutate();
752
753                if (tintInfo.mHasIndeterminateTint) {
754                    mIndeterminateDrawable.setTintList(tintInfo.mIndeterminateTintList);
755                }
756
757                if (tintInfo.mHasIndeterminateTintMode) {
758                    mIndeterminateDrawable.setTintMode(tintInfo.mIndeterminateTintMode);
759                }
760
761                // The drawable (or one of its children) may not have been
762                // stateful before applying the tint, so let's try again.
763                if (mIndeterminateDrawable.isStateful()) {
764                    mIndeterminateDrawable.setState(getDrawableState());
765                }
766            }
767        }
768    }
769
770    /**
771     * Define the tileable drawable used to draw the progress bar in
772     * indeterminate mode.
773     * <p>
774     * If the drawable is a BitmapDrawable or contains BitmapDrawables, a
775     * tiled copy will be generated for display as a progress bar.
776     *
777     * @param d the new drawable
778     * @see #getIndeterminateDrawable()
779     * @see #setIndeterminate(boolean)
780     */
781    public void setIndeterminateDrawableTiled(Drawable d) {
782        if (d != null) {
783            d = tileifyIndeterminate(d);
784        }
785
786        setIndeterminateDrawable(d);
787    }
788
789    /**
790     * <p>Get the drawable used to draw the progress bar in
791     * progress mode.</p>
792     *
793     * @return a {@link android.graphics.drawable.Drawable} instance
794     *
795     * @see #setProgressDrawable(android.graphics.drawable.Drawable)
796     * @see #setIndeterminate(boolean)
797     */
798    public Drawable getProgressDrawable() {
799        return mProgressDrawable;
800    }
801
802    /**
803     * Define the drawable used to draw the progress bar in progress mode.
804     *
805     * @param d the new drawable
806     * @see #getProgressDrawable()
807     * @see #setIndeterminate(boolean)
808     */
809    public void setProgressDrawable(Drawable d) {
810        if (mProgressDrawable != d) {
811            if (mProgressDrawable != null) {
812                mProgressDrawable.setCallback(null);
813                unscheduleDrawable(mProgressDrawable);
814            }
815
816            mProgressDrawable = d;
817
818            if (d != null) {
819                d.setCallback(this);
820                d.setLayoutDirection(getLayoutDirection());
821                if (d.isStateful()) {
822                    d.setState(getDrawableState());
823                }
824
825                // Make sure the ProgressBar is always tall enough
826                int drawableHeight = d.getMinimumHeight();
827                if (mMaxHeight < drawableHeight) {
828                    mMaxHeight = drawableHeight;
829                    requestLayout();
830                }
831
832                applyProgressTints();
833            }
834
835            if (!mIndeterminate) {
836                swapCurrentDrawable(d);
837                postInvalidate();
838            }
839
840            updateDrawableBounds(getWidth(), getHeight());
841            updateDrawableState();
842
843            doRefreshProgress(R.id.progress, mProgress, false, false, false);
844            doRefreshProgress(R.id.secondaryProgress, mSecondaryProgress, false, false, false);
845        }
846    }
847
848    /**
849     * Applies the progress tints in order of increasing specificity.
850     */
851    private void applyProgressTints() {
852        if (mProgressDrawable != null && mProgressTintInfo != null) {
853            applyPrimaryProgressTint();
854            applyProgressBackgroundTint();
855            applySecondaryProgressTint();
856        }
857    }
858
859    /**
860     * Should only be called if we've already verified that mProgressDrawable
861     * and mProgressTintInfo are non-null.
862     */
863    private void applyPrimaryProgressTint() {
864        if (mProgressTintInfo.mHasProgressTint
865                || mProgressTintInfo.mHasProgressTintMode) {
866            final Drawable target = getTintTarget(R.id.progress, true);
867            if (target != null) {
868                if (mProgressTintInfo.mHasProgressTint) {
869                    target.setTintList(mProgressTintInfo.mProgressTintList);
870                }
871                if (mProgressTintInfo.mHasProgressTintMode) {
872                    target.setTintMode(mProgressTintInfo.mProgressTintMode);
873                }
874
875                // The drawable (or one of its children) may not have been
876                // stateful before applying the tint, so let's try again.
877                if (target.isStateful()) {
878                    target.setState(getDrawableState());
879                }
880            }
881        }
882    }
883
884    /**
885     * Should only be called if we've already verified that mProgressDrawable
886     * and mProgressTintInfo are non-null.
887     */
888    private void applyProgressBackgroundTint() {
889        if (mProgressTintInfo.mHasProgressBackgroundTint
890                || mProgressTintInfo.mHasProgressBackgroundTintMode) {
891            final Drawable target = getTintTarget(R.id.background, false);
892            if (target != null) {
893                if (mProgressTintInfo.mHasProgressBackgroundTint) {
894                    target.setTintList(mProgressTintInfo.mProgressBackgroundTintList);
895                }
896                if (mProgressTintInfo.mHasProgressBackgroundTintMode) {
897                    target.setTintMode(mProgressTintInfo.mProgressBackgroundTintMode);
898                }
899
900                // The drawable (or one of its children) may not have been
901                // stateful before applying the tint, so let's try again.
902                if (target.isStateful()) {
903                    target.setState(getDrawableState());
904                }
905            }
906        }
907    }
908
909    /**
910     * Should only be called if we've already verified that mProgressDrawable
911     * and mProgressTintInfo are non-null.
912     */
913    private void applySecondaryProgressTint() {
914        if (mProgressTintInfo.mHasSecondaryProgressTint
915                || mProgressTintInfo.mHasSecondaryProgressTintMode) {
916            final Drawable target = getTintTarget(R.id.secondaryProgress, false);
917            if (target != null) {
918                if (mProgressTintInfo.mHasSecondaryProgressTint) {
919                    target.setTintList(mProgressTintInfo.mSecondaryProgressTintList);
920                }
921                if (mProgressTintInfo.mHasSecondaryProgressTintMode) {
922                    target.setTintMode(mProgressTintInfo.mSecondaryProgressTintMode);
923                }
924
925                // The drawable (or one of its children) may not have been
926                // stateful before applying the tint, so let's try again.
927                if (target.isStateful()) {
928                    target.setState(getDrawableState());
929                }
930            }
931        }
932    }
933
934    /**
935     * Applies a tint to the progress indicator, if one exists, or to the
936     * entire progress drawable otherwise. Does not modify the current tint
937     * mode, which is {@link PorterDuff.Mode#SRC_IN} by default.
938     * <p>
939     * The progress indicator should be specified as a layer with
940     * id {@link android.R.id#progress} in a {@link LayerDrawable}
941     * used as the progress drawable.
942     * <p>
943     * Subsequent calls to {@link #setProgressDrawable(Drawable)} will
944     * automatically mutate the drawable and apply the specified tint and
945     * tint mode using
946     * {@link Drawable#setTintList(ColorStateList)}.
947     *
948     * @param tint the tint to apply, may be {@code null} to clear tint
949     *
950     * @attr ref android.R.styleable#ProgressBar_progressTint
951     * @see #getProgressTintList()
952     * @see Drawable#setTintList(ColorStateList)
953     */
954    @RemotableViewMethod
955    public void setProgressTintList(@Nullable ColorStateList tint) {
956        if (mProgressTintInfo == null) {
957            mProgressTintInfo = new ProgressTintInfo();
958        }
959        mProgressTintInfo.mProgressTintList = tint;
960        mProgressTintInfo.mHasProgressTint = true;
961
962        if (mProgressDrawable != null) {
963            applyPrimaryProgressTint();
964        }
965    }
966
967    /**
968     * Returns the tint applied to the progress drawable, if specified.
969     *
970     * @return the tint applied to the progress drawable
971     * @attr ref android.R.styleable#ProgressBar_progressTint
972     * @see #setProgressTintList(ColorStateList)
973     */
974    @Nullable
975    public ColorStateList getProgressTintList() {
976        return mProgressTintInfo != null ? mProgressTintInfo.mProgressTintList : null;
977    }
978
979    /**
980     * Specifies the blending mode used to apply the tint specified by
981     * {@link #setProgressTintList(ColorStateList)}} to the progress
982     * indicator. The default mode is {@link PorterDuff.Mode#SRC_IN}.
983     *
984     * @param tintMode the blending mode used to apply the tint, may be
985     *                 {@code null} to clear tint
986     * @attr ref android.R.styleable#ProgressBar_progressTintMode
987     * @see #getProgressTintMode()
988     * @see Drawable#setTintMode(PorterDuff.Mode)
989     */
990    public void setProgressTintMode(@Nullable PorterDuff.Mode tintMode) {
991        if (mProgressTintInfo == null) {
992            mProgressTintInfo = new ProgressTintInfo();
993        }
994        mProgressTintInfo.mProgressTintMode = tintMode;
995        mProgressTintInfo.mHasProgressTintMode = true;
996
997        if (mProgressDrawable != null) {
998            applyPrimaryProgressTint();
999        }
1000    }
1001
1002    /**
1003     * Returns the blending mode used to apply the tint to the progress
1004     * drawable, if specified.
1005     *
1006     * @return the blending mode used to apply the tint to the progress
1007     *         drawable
1008     * @attr ref android.R.styleable#ProgressBar_progressTintMode
1009     * @see #setProgressTintMode(PorterDuff.Mode)
1010     */
1011    @Nullable
1012    public PorterDuff.Mode getProgressTintMode() {
1013        return mProgressTintInfo != null ? mProgressTintInfo.mProgressTintMode : null;
1014    }
1015
1016    /**
1017     * Applies a tint to the progress background, if one exists. Does not
1018     * modify the current tint mode, which is
1019     * {@link PorterDuff.Mode#SRC_ATOP} by default.
1020     * <p>
1021     * The progress background must be specified as a layer with
1022     * id {@link android.R.id#background} in a {@link LayerDrawable}
1023     * used as the progress drawable.
1024     * <p>
1025     * Subsequent calls to {@link #setProgressDrawable(Drawable)} where the
1026     * drawable contains a progress background will automatically mutate the
1027     * drawable and apply the specified tint and tint mode using
1028     * {@link Drawable#setTintList(ColorStateList)}.
1029     *
1030     * @param tint the tint to apply, may be {@code null} to clear tint
1031     *
1032     * @attr ref android.R.styleable#ProgressBar_progressBackgroundTint
1033     * @see #getProgressBackgroundTintList()
1034     * @see Drawable#setTintList(ColorStateList)
1035     */
1036    @RemotableViewMethod
1037    public void setProgressBackgroundTintList(@Nullable ColorStateList tint) {
1038        if (mProgressTintInfo == null) {
1039            mProgressTintInfo = new ProgressTintInfo();
1040        }
1041        mProgressTintInfo.mProgressBackgroundTintList = tint;
1042        mProgressTintInfo.mHasProgressBackgroundTint = true;
1043
1044        if (mProgressDrawable != null) {
1045            applyProgressBackgroundTint();
1046        }
1047    }
1048
1049    /**
1050     * Returns the tint applied to the progress background, if specified.
1051     *
1052     * @return the tint applied to the progress background
1053     * @attr ref android.R.styleable#ProgressBar_progressBackgroundTint
1054     * @see #setProgressBackgroundTintList(ColorStateList)
1055     */
1056    @Nullable
1057    public ColorStateList getProgressBackgroundTintList() {
1058        return mProgressTintInfo != null ? mProgressTintInfo.mProgressBackgroundTintList : null;
1059    }
1060
1061    /**
1062     * Specifies the blending mode used to apply the tint specified by
1063     * {@link #setProgressBackgroundTintList(ColorStateList)}} to the progress
1064     * background. The default mode is {@link PorterDuff.Mode#SRC_IN}.
1065     *
1066     * @param tintMode the blending mode used to apply the tint, may be
1067     *                 {@code null} to clear tint
1068     * @attr ref android.R.styleable#ProgressBar_progressBackgroundTintMode
1069     * @see #setProgressBackgroundTintList(ColorStateList)
1070     * @see Drawable#setTintMode(PorterDuff.Mode)
1071     */
1072    public void setProgressBackgroundTintMode(@Nullable PorterDuff.Mode tintMode) {
1073        if (mProgressTintInfo == null) {
1074            mProgressTintInfo = new ProgressTintInfo();
1075        }
1076        mProgressTintInfo.mProgressBackgroundTintMode = tintMode;
1077        mProgressTintInfo.mHasProgressBackgroundTintMode = true;
1078
1079        if (mProgressDrawable != null) {
1080            applyProgressBackgroundTint();
1081        }
1082    }
1083
1084    /**
1085     * @return the blending mode used to apply the tint to the progress
1086     *         background
1087     * @attr ref android.R.styleable#ProgressBar_progressBackgroundTintMode
1088     * @see #setProgressBackgroundTintMode(PorterDuff.Mode)
1089     */
1090    @Nullable
1091    public PorterDuff.Mode getProgressBackgroundTintMode() {
1092        return mProgressTintInfo != null ? mProgressTintInfo.mProgressBackgroundTintMode : null;
1093    }
1094
1095    /**
1096     * Applies a tint to the secondary progress indicator, if one exists.
1097     * Does not modify the current tint mode, which is
1098     * {@link PorterDuff.Mode#SRC_ATOP} by default.
1099     * <p>
1100     * The secondary progress indicator must be specified as a layer with
1101     * id {@link android.R.id#secondaryProgress} in a {@link LayerDrawable}
1102     * used as the progress drawable.
1103     * <p>
1104     * Subsequent calls to {@link #setProgressDrawable(Drawable)} where the
1105     * drawable contains a secondary progress indicator will automatically
1106     * mutate the drawable and apply the specified tint and tint mode using
1107     * {@link Drawable#setTintList(ColorStateList)}.
1108     *
1109     * @param tint the tint to apply, may be {@code null} to clear tint
1110     *
1111     * @attr ref android.R.styleable#ProgressBar_secondaryProgressTint
1112     * @see #getSecondaryProgressTintList()
1113     * @see Drawable#setTintList(ColorStateList)
1114     */
1115    public void setSecondaryProgressTintList(@Nullable ColorStateList tint) {
1116        if (mProgressTintInfo == null) {
1117            mProgressTintInfo = new ProgressTintInfo();
1118        }
1119        mProgressTintInfo.mSecondaryProgressTintList = tint;
1120        mProgressTintInfo.mHasSecondaryProgressTint = true;
1121
1122        if (mProgressDrawable != null) {
1123            applySecondaryProgressTint();
1124        }
1125    }
1126
1127    /**
1128     * Returns the tint applied to the secondary progress drawable, if
1129     * specified.
1130     *
1131     * @return the tint applied to the secondary progress drawable
1132     * @attr ref android.R.styleable#ProgressBar_secondaryProgressTint
1133     * @see #setSecondaryProgressTintList(ColorStateList)
1134     */
1135    @Nullable
1136    public ColorStateList getSecondaryProgressTintList() {
1137        return mProgressTintInfo != null ? mProgressTintInfo.mSecondaryProgressTintList : null;
1138    }
1139
1140    /**
1141     * Specifies the blending mode used to apply the tint specified by
1142     * {@link #setSecondaryProgressTintList(ColorStateList)}} to the secondary
1143     * progress indicator. The default mode is
1144     * {@link PorterDuff.Mode#SRC_ATOP}.
1145     *
1146     * @param tintMode the blending mode used to apply the tint, may be
1147     *                 {@code null} to clear tint
1148     * @attr ref android.R.styleable#ProgressBar_secondaryProgressTintMode
1149     * @see #setSecondaryProgressTintList(ColorStateList)
1150     * @see Drawable#setTintMode(PorterDuff.Mode)
1151     */
1152    public void setSecondaryProgressTintMode(@Nullable PorterDuff.Mode tintMode) {
1153        if (mProgressTintInfo == null) {
1154            mProgressTintInfo = new ProgressTintInfo();
1155        }
1156        mProgressTintInfo.mSecondaryProgressTintMode = tintMode;
1157        mProgressTintInfo.mHasSecondaryProgressTintMode = true;
1158
1159        if (mProgressDrawable != null) {
1160            applySecondaryProgressTint();
1161        }
1162    }
1163
1164    /**
1165     * Returns the blending mode used to apply the tint to the secondary
1166     * progress drawable, if specified.
1167     *
1168     * @return the blending mode used to apply the tint to the secondary
1169     *         progress drawable
1170     * @attr ref android.R.styleable#ProgressBar_secondaryProgressTintMode
1171     * @see #setSecondaryProgressTintMode(PorterDuff.Mode)
1172     */
1173    @Nullable
1174    public PorterDuff.Mode getSecondaryProgressTintMode() {
1175        return mProgressTintInfo != null ? mProgressTintInfo.mSecondaryProgressTintMode : null;
1176    }
1177
1178    /**
1179     * Returns the drawable to which a tint or tint mode should be applied.
1180     *
1181     * @param layerId id of the layer to modify
1182     * @param shouldFallback whether the base drawable should be returned
1183     *                       if the id does not exist
1184     * @return the drawable to modify
1185     */
1186    @Nullable
1187    private Drawable getTintTarget(int layerId, boolean shouldFallback) {
1188        Drawable layer = null;
1189
1190        final Drawable d = mProgressDrawable;
1191        if (d != null) {
1192            mProgressDrawable = d.mutate();
1193
1194            if (d instanceof LayerDrawable) {
1195                layer = ((LayerDrawable) d).findDrawableByLayerId(layerId);
1196            }
1197
1198            if (shouldFallback && layer == null) {
1199                layer = d;
1200            }
1201        }
1202
1203        return layer;
1204    }
1205
1206    /**
1207     * Define the tileable drawable used to draw the progress bar in
1208     * progress mode.
1209     * <p>
1210     * If the drawable is a BitmapDrawable or contains BitmapDrawables, a
1211     * tiled copy will be generated for display as a progress bar.
1212     *
1213     * @param d the new drawable
1214     * @see #getProgressDrawable()
1215     * @see #setIndeterminate(boolean)
1216     */
1217    public void setProgressDrawableTiled(Drawable d) {
1218        if (d != null) {
1219            d = tileify(d, false);
1220        }
1221
1222        setProgressDrawable(d);
1223    }
1224
1225    /**
1226     * @return The drawable currently used to draw the progress bar
1227     */
1228    Drawable getCurrentDrawable() {
1229        return mCurrentDrawable;
1230    }
1231
1232    @Override
1233    protected boolean verifyDrawable(@NonNull Drawable who) {
1234        return who == mProgressDrawable || who == mIndeterminateDrawable
1235                || super.verifyDrawable(who);
1236    }
1237
1238    @Override
1239    public void jumpDrawablesToCurrentState() {
1240        super.jumpDrawablesToCurrentState();
1241        if (mProgressDrawable != null) mProgressDrawable.jumpToCurrentState();
1242        if (mIndeterminateDrawable != null) mIndeterminateDrawable.jumpToCurrentState();
1243    }
1244
1245    /**
1246     * @hide
1247     */
1248    @Override
1249    public void onResolveDrawables(int layoutDirection) {
1250        final Drawable d = mCurrentDrawable;
1251        if (d != null) {
1252            d.setLayoutDirection(layoutDirection);
1253        }
1254        if (mIndeterminateDrawable != null) {
1255            mIndeterminateDrawable.setLayoutDirection(layoutDirection);
1256        }
1257        if (mProgressDrawable != null) {
1258            mProgressDrawable.setLayoutDirection(layoutDirection);
1259        }
1260    }
1261
1262    @Override
1263    public void postInvalidate() {
1264        if (!mNoInvalidate) {
1265            super.postInvalidate();
1266        }
1267    }
1268
1269    private class RefreshProgressRunnable implements Runnable {
1270        public void run() {
1271            synchronized (ProgressBar.this) {
1272                final int count = mRefreshData.size();
1273                for (int i = 0; i < count; i++) {
1274                    final RefreshData rd = mRefreshData.get(i);
1275                    doRefreshProgress(rd.id, rd.progress, rd.fromUser, true, rd.animate);
1276                    rd.recycle();
1277                }
1278                mRefreshData.clear();
1279                mRefreshIsPosted = false;
1280            }
1281        }
1282    }
1283
1284    private static class RefreshData {
1285        private static final int POOL_MAX = 24;
1286        private static final SynchronizedPool<RefreshData> sPool =
1287                new SynchronizedPool<RefreshData>(POOL_MAX);
1288
1289        public int id;
1290        public int progress;
1291        public boolean fromUser;
1292        public boolean animate;
1293
1294        public static RefreshData obtain(int id, int progress, boolean fromUser, boolean animate) {
1295            RefreshData rd = sPool.acquire();
1296            if (rd == null) {
1297                rd = new RefreshData();
1298            }
1299            rd.id = id;
1300            rd.progress = progress;
1301            rd.fromUser = fromUser;
1302            rd.animate = animate;
1303            return rd;
1304        }
1305
1306        public void recycle() {
1307            sPool.release(this);
1308        }
1309    }
1310
1311    private synchronized void doRefreshProgress(int id, int progress, boolean fromUser,
1312            boolean callBackToApp, boolean animate) {
1313        final float scale = mMax > 0 ? progress / (float) mMax : 0;
1314        final boolean isPrimary = id == R.id.progress;
1315
1316        if (isPrimary && animate) {
1317            final ObjectAnimator animator = ObjectAnimator.ofFloat(this, VISUAL_PROGRESS, scale);
1318            animator.setAutoCancel(true);
1319            animator.setDuration(PROGRESS_ANIM_DURATION);
1320            animator.setInterpolator(PROGRESS_ANIM_INTERPOLATOR);
1321            animator.start();
1322        } else {
1323            setVisualProgress(id, scale);
1324        }
1325
1326        if (isPrimary && callBackToApp) {
1327            onProgressRefresh(scale, fromUser, progress);
1328        }
1329    }
1330
1331    void onProgressRefresh(float scale, boolean fromUser, int progress) {
1332        if (AccessibilityManager.getInstance(mContext).isEnabled()) {
1333            scheduleAccessibilityEventSender();
1334        }
1335    }
1336
1337    /**
1338     * Sets the visual state of a progress indicator.
1339     *
1340     * @param id the identifier of the progress indicator
1341     * @param progress the visual progress in the range [0...1]
1342     */
1343    private void setVisualProgress(int id, float progress) {
1344        mVisualProgress = progress;
1345
1346        Drawable d = mCurrentDrawable;
1347
1348        if (d instanceof LayerDrawable) {
1349            d = ((LayerDrawable) d).findDrawableByLayerId(id);
1350            if (d == null) {
1351                // If we can't find the requested layer, fall back to setting
1352                // the level of the entire drawable. This will break if
1353                // progress is set on multiple elements, but the theme-default
1354                // drawable will always have all layer IDs present.
1355                d = mCurrentDrawable;
1356            }
1357        }
1358
1359        if (d != null) {
1360            final int level = (int) (progress * MAX_LEVEL);
1361            d.setLevel(level);
1362        } else {
1363            invalidate();
1364        }
1365
1366        onVisualProgressChanged(id, progress);
1367    }
1368
1369    /**
1370     * Called when the visual state of a progress indicator changes.
1371     *
1372     * @param id the identifier of the progress indicator
1373     * @param progress the visual progress in the range [0...1]
1374     */
1375    void onVisualProgressChanged(int id, float progress) {
1376        // Stub method.
1377    }
1378
1379    private synchronized void refreshProgress(int id, int progress, boolean fromUser,
1380            boolean animate) {
1381        if (mUiThreadId == Thread.currentThread().getId()) {
1382            doRefreshProgress(id, progress, fromUser, true, animate);
1383        } else {
1384            if (mRefreshProgressRunnable == null) {
1385                mRefreshProgressRunnable = new RefreshProgressRunnable();
1386            }
1387
1388            final RefreshData rd = RefreshData.obtain(id, progress, fromUser, animate);
1389            mRefreshData.add(rd);
1390            if (mAttached && !mRefreshIsPosted) {
1391                post(mRefreshProgressRunnable);
1392                mRefreshIsPosted = true;
1393            }
1394        }
1395    }
1396
1397    /**
1398     * Sets the current progress to the specified value. Does not do anything
1399     * if the progress bar is in indeterminate mode.
1400     * <p>
1401     * This method will immediately update the visual position of the progress
1402     * indicator. To animate the visual position to the target value, use
1403     * {@link #setProgress(int, boolean)}}.
1404     *
1405     * @param progress the new progress, between 0 and {@link #getMax()}
1406     *
1407     * @see #setIndeterminate(boolean)
1408     * @see #isIndeterminate()
1409     * @see #getProgress()
1410     * @see #incrementProgressBy(int)
1411     */
1412    @android.view.RemotableViewMethod
1413    public synchronized void setProgress(int progress) {
1414        setProgressInternal(progress, false, false);
1415    }
1416
1417    /**
1418     * Sets the current progress to the specified value, optionally animating
1419     * the visual position between the current and target values.
1420     * <p>
1421     * Animation does not affect the result of {@link #getProgress()}, which
1422     * will return the target value immediately after this method is called.
1423     *
1424     * @param progress the new progress value, between 0 and {@link #getMax()}
1425     * @param animate {@code true} to animate between the current and target
1426     *                values or {@code false} to not animate
1427     */
1428    public void setProgress(int progress, boolean animate) {
1429        setProgressInternal(progress, false, animate);
1430    }
1431
1432    @android.view.RemotableViewMethod
1433    synchronized boolean setProgressInternal(int progress, boolean fromUser, boolean animate) {
1434        if (mIndeterminate) {
1435            // Not applicable.
1436            return false;
1437        }
1438
1439        progress = MathUtils.constrain(progress, 0, mMax);
1440
1441        if (progress == mProgress) {
1442            // No change from current.
1443            return false;
1444        }
1445
1446        mProgress = progress;
1447        refreshProgress(R.id.progress, mProgress, fromUser, animate);
1448        return true;
1449    }
1450
1451    /**
1452     * <p>
1453     * Set the current secondary progress to the specified value. Does not do
1454     * anything if the progress bar is in indeterminate mode.
1455     * </p>
1456     *
1457     * @param secondaryProgress the new secondary progress, between 0 and {@link #getMax()}
1458     * @see #setIndeterminate(boolean)
1459     * @see #isIndeterminate()
1460     * @see #getSecondaryProgress()
1461     * @see #incrementSecondaryProgressBy(int)
1462     */
1463    @android.view.RemotableViewMethod
1464    public synchronized void setSecondaryProgress(int secondaryProgress) {
1465        if (mIndeterminate) {
1466            return;
1467        }
1468
1469        if (secondaryProgress < 0) {
1470            secondaryProgress = 0;
1471        }
1472
1473        if (secondaryProgress > mMax) {
1474            secondaryProgress = mMax;
1475        }
1476
1477        if (secondaryProgress != mSecondaryProgress) {
1478            mSecondaryProgress = secondaryProgress;
1479            refreshProgress(R.id.secondaryProgress, mSecondaryProgress, false, false);
1480        }
1481    }
1482
1483    /**
1484     * <p>Get the progress bar's current level of progress. Return 0 when the
1485     * progress bar is in indeterminate mode.</p>
1486     *
1487     * @return the current progress, between 0 and {@link #getMax()}
1488     *
1489     * @see #setIndeterminate(boolean)
1490     * @see #isIndeterminate()
1491     * @see #setProgress(int)
1492     * @see #setMax(int)
1493     * @see #getMax()
1494     */
1495    @ViewDebug.ExportedProperty(category = "progress")
1496    public synchronized int getProgress() {
1497        return mIndeterminate ? 0 : mProgress;
1498    }
1499
1500    /**
1501     * <p>Get the progress bar's current level of secondary progress. Return 0 when the
1502     * progress bar is in indeterminate mode.</p>
1503     *
1504     * @return the current secondary progress, between 0 and {@link #getMax()}
1505     *
1506     * @see #setIndeterminate(boolean)
1507     * @see #isIndeterminate()
1508     * @see #setSecondaryProgress(int)
1509     * @see #setMax(int)
1510     * @see #getMax()
1511     */
1512    @ViewDebug.ExportedProperty(category = "progress")
1513    public synchronized int getSecondaryProgress() {
1514        return mIndeterminate ? 0 : mSecondaryProgress;
1515    }
1516
1517    /**
1518     * <p>Return the upper limit of this progress bar's range.</p>
1519     *
1520     * @return a positive integer
1521     *
1522     * @see #setMax(int)
1523     * @see #getProgress()
1524     * @see #getSecondaryProgress()
1525     */
1526    @ViewDebug.ExportedProperty(category = "progress")
1527    public synchronized int getMax() {
1528        return mMax;
1529    }
1530
1531    /**
1532     * <p>Set the range of the progress bar to 0...<tt>max</tt>.</p>
1533     *
1534     * @param max the upper range of this progress bar
1535     *
1536     * @see #getMax()
1537     * @see #setProgress(int)
1538     * @see #setSecondaryProgress(int)
1539     */
1540    @android.view.RemotableViewMethod
1541    public synchronized void setMax(int max) {
1542        if (max < 0) {
1543            max = 0;
1544        }
1545        if (max != mMax) {
1546            mMax = max;
1547            postInvalidate();
1548
1549            if (mProgress > max) {
1550                mProgress = max;
1551            }
1552            refreshProgress(R.id.progress, mProgress, false, false);
1553        }
1554    }
1555
1556    /**
1557     * <p>Increase the progress bar's progress by the specified amount.</p>
1558     *
1559     * @param diff the amount by which the progress must be increased
1560     *
1561     * @see #setProgress(int)
1562     */
1563    public synchronized final void incrementProgressBy(int diff) {
1564        setProgress(mProgress + diff);
1565    }
1566
1567    /**
1568     * <p>Increase the progress bar's secondary progress by the specified amount.</p>
1569     *
1570     * @param diff the amount by which the secondary progress must be increased
1571     *
1572     * @see #setSecondaryProgress(int)
1573     */
1574    public synchronized final void incrementSecondaryProgressBy(int diff) {
1575        setSecondaryProgress(mSecondaryProgress + diff);
1576    }
1577
1578    /**
1579     * <p>Start the indeterminate progress animation.</p>
1580     */
1581    void startAnimation() {
1582        if (getVisibility() != VISIBLE || getWindowVisibility() != VISIBLE) {
1583            return;
1584        }
1585
1586        if (mIndeterminateDrawable instanceof Animatable) {
1587            mShouldStartAnimationDrawable = true;
1588            mHasAnimation = false;
1589        } else {
1590            mHasAnimation = true;
1591
1592            if (mInterpolator == null) {
1593                mInterpolator = new LinearInterpolator();
1594            }
1595
1596            if (mTransformation == null) {
1597                mTransformation = new Transformation();
1598            } else {
1599                mTransformation.clear();
1600            }
1601
1602            if (mAnimation == null) {
1603                mAnimation = new AlphaAnimation(0.0f, 1.0f);
1604            } else {
1605                mAnimation.reset();
1606            }
1607
1608            mAnimation.setRepeatMode(mBehavior);
1609            mAnimation.setRepeatCount(Animation.INFINITE);
1610            mAnimation.setDuration(mDuration);
1611            mAnimation.setInterpolator(mInterpolator);
1612            mAnimation.setStartTime(Animation.START_ON_FIRST_FRAME);
1613        }
1614        postInvalidate();
1615    }
1616
1617    /**
1618     * <p>Stop the indeterminate progress animation.</p>
1619     */
1620    void stopAnimation() {
1621        mHasAnimation = false;
1622        if (mIndeterminateDrawable instanceof Animatable) {
1623            ((Animatable) mIndeterminateDrawable).stop();
1624            mShouldStartAnimationDrawable = false;
1625        }
1626        postInvalidate();
1627    }
1628
1629    /**
1630     * Sets the acceleration curve for the indeterminate animation.
1631     * The interpolator is loaded as a resource from the specified context.
1632     *
1633     * @param context The application environment
1634     * @param resID The resource identifier of the interpolator to load
1635     */
1636    public void setInterpolator(Context context, @InterpolatorRes int resID) {
1637        setInterpolator(AnimationUtils.loadInterpolator(context, resID));
1638    }
1639
1640    /**
1641     * Sets the acceleration curve for the indeterminate animation.
1642     * Defaults to a linear interpolation.
1643     *
1644     * @param interpolator The interpolator which defines the acceleration curve
1645     */
1646    public void setInterpolator(Interpolator interpolator) {
1647        mInterpolator = interpolator;
1648    }
1649
1650    /**
1651     * Gets the acceleration curve type for the indeterminate animation.
1652     *
1653     * @return the {@link Interpolator} associated to this animation
1654     */
1655    public Interpolator getInterpolator() {
1656        return mInterpolator;
1657    }
1658
1659    @Override
1660    public void onVisibilityAggregated(boolean isVisible) {
1661        super.onVisibilityAggregated(isVisible);
1662
1663        if (isVisible != mAggregatedIsVisible) {
1664            mAggregatedIsVisible = isVisible;
1665
1666            if (mIndeterminate) {
1667                // let's be nice with the UI thread
1668                if (isVisible) {
1669                    startAnimation();
1670                } else {
1671                    stopAnimation();
1672                }
1673            }
1674
1675            if (mCurrentDrawable != null) {
1676                mCurrentDrawable.setVisible(isVisible, false);
1677            }
1678        }
1679    }
1680
1681    @Override
1682    public void invalidateDrawable(@NonNull Drawable dr) {
1683        if (!mInDrawing) {
1684            if (verifyDrawable(dr)) {
1685                final Rect dirty = dr.getBounds();
1686                final int scrollX = mScrollX + mPaddingLeft;
1687                final int scrollY = mScrollY + mPaddingTop;
1688
1689                invalidate(dirty.left + scrollX, dirty.top + scrollY,
1690                        dirty.right + scrollX, dirty.bottom + scrollY);
1691            } else {
1692                super.invalidateDrawable(dr);
1693            }
1694        }
1695    }
1696
1697    @Override
1698    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
1699        updateDrawableBounds(w, h);
1700    }
1701
1702    private void updateDrawableBounds(int w, int h) {
1703        // onDraw will translate the canvas so we draw starting at 0,0.
1704        // Subtract out padding for the purposes of the calculations below.
1705        w -= mPaddingRight + mPaddingLeft;
1706        h -= mPaddingTop + mPaddingBottom;
1707
1708        int right = w;
1709        int bottom = h;
1710        int top = 0;
1711        int left = 0;
1712
1713        if (mIndeterminateDrawable != null) {
1714            // Aspect ratio logic does not apply to AnimationDrawables
1715            if (mOnlyIndeterminate && !(mIndeterminateDrawable instanceof AnimationDrawable)) {
1716                // Maintain aspect ratio. Certain kinds of animated drawables
1717                // get very confused otherwise.
1718                final int intrinsicWidth = mIndeterminateDrawable.getIntrinsicWidth();
1719                final int intrinsicHeight = mIndeterminateDrawable.getIntrinsicHeight();
1720                final float intrinsicAspect = (float) intrinsicWidth / intrinsicHeight;
1721                final float boundAspect = (float) w / h;
1722                if (intrinsicAspect != boundAspect) {
1723                    if (boundAspect > intrinsicAspect) {
1724                        // New width is larger. Make it smaller to match height.
1725                        final int width = (int) (h * intrinsicAspect);
1726                        left = (w - width) / 2;
1727                        right = left + width;
1728                    } else {
1729                        // New height is larger. Make it smaller to match width.
1730                        final int height = (int) (w * (1 / intrinsicAspect));
1731                        top = (h - height) / 2;
1732                        bottom = top + height;
1733                    }
1734                }
1735            }
1736            if (isLayoutRtl() && mMirrorForRtl) {
1737                int tempLeft = left;
1738                left = w - right;
1739                right = w - tempLeft;
1740            }
1741            mIndeterminateDrawable.setBounds(left, top, right, bottom);
1742        }
1743
1744        if (mProgressDrawable != null) {
1745            mProgressDrawable.setBounds(0, 0, right, bottom);
1746        }
1747    }
1748
1749    @Override
1750    protected synchronized void onDraw(Canvas canvas) {
1751        super.onDraw(canvas);
1752
1753        drawTrack(canvas);
1754    }
1755
1756    /**
1757     * Draws the progress bar track.
1758     */
1759    void drawTrack(Canvas canvas) {
1760        final Drawable d = mCurrentDrawable;
1761        if (d != null) {
1762            // Translate canvas so a indeterminate circular progress bar with padding
1763            // rotates properly in its animation
1764            final int saveCount = canvas.save();
1765
1766            if (isLayoutRtl() && mMirrorForRtl) {
1767                canvas.translate(getWidth() - mPaddingRight, mPaddingTop);
1768                canvas.scale(-1.0f, 1.0f);
1769            } else {
1770                canvas.translate(mPaddingLeft, mPaddingTop);
1771            }
1772
1773            final long time = getDrawingTime();
1774            if (mHasAnimation) {
1775                mAnimation.getTransformation(time, mTransformation);
1776                final float scale = mTransformation.getAlpha();
1777                try {
1778                    mInDrawing = true;
1779                    d.setLevel((int) (scale * MAX_LEVEL));
1780                } finally {
1781                    mInDrawing = false;
1782                }
1783                postInvalidateOnAnimation();
1784            }
1785
1786            d.draw(canvas);
1787            canvas.restoreToCount(saveCount);
1788
1789            if (mShouldStartAnimationDrawable && d instanceof Animatable) {
1790                ((Animatable) d).start();
1791                mShouldStartAnimationDrawable = false;
1792            }
1793        }
1794    }
1795
1796    @Override
1797    protected synchronized void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
1798        int dw = 0;
1799        int dh = 0;
1800
1801        final Drawable d = mCurrentDrawable;
1802        if (d != null) {
1803            dw = Math.max(mMinWidth, Math.min(mMaxWidth, d.getIntrinsicWidth()));
1804            dh = Math.max(mMinHeight, Math.min(mMaxHeight, d.getIntrinsicHeight()));
1805        }
1806
1807        updateDrawableState();
1808
1809        dw += mPaddingLeft + mPaddingRight;
1810        dh += mPaddingTop + mPaddingBottom;
1811
1812        final int measuredWidth = resolveSizeAndState(dw, widthMeasureSpec, 0);
1813        final int measuredHeight = resolveSizeAndState(dh, heightMeasureSpec, 0);
1814        setMeasuredDimension(measuredWidth, measuredHeight);
1815    }
1816
1817    @Override
1818    protected void drawableStateChanged() {
1819        super.drawableStateChanged();
1820        updateDrawableState();
1821    }
1822
1823    private void updateDrawableState() {
1824        final int[] state = getDrawableState();
1825        boolean changed = false;
1826
1827        final Drawable progressDrawable = mProgressDrawable;
1828        if (progressDrawable != null && progressDrawable.isStateful()) {
1829            changed |= progressDrawable.setState(state);
1830        }
1831
1832        final Drawable indeterminateDrawable = mIndeterminateDrawable;
1833        if (indeterminateDrawable != null && indeterminateDrawable.isStateful()) {
1834            changed |= indeterminateDrawable.setState(state);
1835        }
1836
1837        if (changed) {
1838            invalidate();
1839        }
1840    }
1841
1842    @Override
1843    public void drawableHotspotChanged(float x, float y) {
1844        super.drawableHotspotChanged(x, y);
1845
1846        if (mProgressDrawable != null) {
1847            mProgressDrawable.setHotspot(x, y);
1848        }
1849
1850        if (mIndeterminateDrawable != null) {
1851            mIndeterminateDrawable.setHotspot(x, y);
1852        }
1853    }
1854
1855    static class SavedState extends BaseSavedState {
1856        int progress;
1857        int secondaryProgress;
1858
1859        /**
1860         * Constructor called from {@link ProgressBar#onSaveInstanceState()}
1861         */
1862        SavedState(Parcelable superState) {
1863            super(superState);
1864        }
1865
1866        /**
1867         * Constructor called from {@link #CREATOR}
1868         */
1869        private SavedState(Parcel in) {
1870            super(in);
1871            progress = in.readInt();
1872            secondaryProgress = in.readInt();
1873        }
1874
1875        @Override
1876        public void writeToParcel(Parcel out, int flags) {
1877            super.writeToParcel(out, flags);
1878            out.writeInt(progress);
1879            out.writeInt(secondaryProgress);
1880        }
1881
1882        public static final Parcelable.Creator<SavedState> CREATOR
1883                = new Parcelable.Creator<SavedState>() {
1884            public SavedState createFromParcel(Parcel in) {
1885                return new SavedState(in);
1886            }
1887
1888            public SavedState[] newArray(int size) {
1889                return new SavedState[size];
1890            }
1891        };
1892    }
1893
1894    @Override
1895    public Parcelable onSaveInstanceState() {
1896        // Force our ancestor class to save its state
1897        Parcelable superState = super.onSaveInstanceState();
1898        SavedState ss = new SavedState(superState);
1899
1900        ss.progress = mProgress;
1901        ss.secondaryProgress = mSecondaryProgress;
1902
1903        return ss;
1904    }
1905
1906    @Override
1907    public void onRestoreInstanceState(Parcelable state) {
1908        SavedState ss = (SavedState) state;
1909        super.onRestoreInstanceState(ss.getSuperState());
1910
1911        setProgress(ss.progress);
1912        setSecondaryProgress(ss.secondaryProgress);
1913    }
1914
1915    @Override
1916    protected void onAttachedToWindow() {
1917        super.onAttachedToWindow();
1918        if (mIndeterminate) {
1919            startAnimation();
1920        }
1921        if (mRefreshData != null) {
1922            synchronized (this) {
1923                final int count = mRefreshData.size();
1924                for (int i = 0; i < count; i++) {
1925                    final RefreshData rd = mRefreshData.get(i);
1926                    doRefreshProgress(rd.id, rd.progress, rd.fromUser, true, rd.animate);
1927                    rd.recycle();
1928                }
1929                mRefreshData.clear();
1930            }
1931        }
1932        mAttached = true;
1933    }
1934
1935    @Override
1936    protected void onDetachedFromWindow() {
1937        if (mIndeterminate) {
1938            stopAnimation();
1939        }
1940        if (mRefreshProgressRunnable != null) {
1941            removeCallbacks(mRefreshProgressRunnable);
1942            mRefreshIsPosted = false;
1943        }
1944        if (mAccessibilityEventSender != null) {
1945            removeCallbacks(mAccessibilityEventSender);
1946        }
1947        // This should come after stopAnimation(), otherwise an invalidate message remains in the
1948        // queue, which can prevent the entire view hierarchy from being GC'ed during a rotation
1949        super.onDetachedFromWindow();
1950        mAttached = false;
1951    }
1952
1953    @Override
1954    public CharSequence getAccessibilityClassName() {
1955        return ProgressBar.class.getName();
1956    }
1957
1958    /** @hide */
1959    @Override
1960    public void onInitializeAccessibilityEventInternal(AccessibilityEvent event) {
1961        super.onInitializeAccessibilityEventInternal(event);
1962        event.setItemCount(mMax);
1963        event.setCurrentItemIndex(mProgress);
1964    }
1965
1966    /** @hide */
1967    @Override
1968    public void onInitializeAccessibilityNodeInfoInternal(AccessibilityNodeInfo info) {
1969        super.onInitializeAccessibilityNodeInfoInternal(info);
1970
1971        if (!isIndeterminate()) {
1972            AccessibilityNodeInfo.RangeInfo rangeInfo = AccessibilityNodeInfo.RangeInfo.obtain(
1973                    AccessibilityNodeInfo.RangeInfo.RANGE_TYPE_INT, 0, getMax(), getProgress());
1974            info.setRangeInfo(rangeInfo);
1975        }
1976    }
1977
1978    /**
1979     * Schedule a command for sending an accessibility event.
1980     * </br>
1981     * Note: A command is used to ensure that accessibility events
1982     *       are sent at most one in a given time frame to save
1983     *       system resources while the progress changes quickly.
1984     */
1985    private void scheduleAccessibilityEventSender() {
1986        if (mAccessibilityEventSender == null) {
1987            mAccessibilityEventSender = new AccessibilityEventSender();
1988        } else {
1989            removeCallbacks(mAccessibilityEventSender);
1990        }
1991        postDelayed(mAccessibilityEventSender, TIMEOUT_SEND_ACCESSIBILITY_EVENT);
1992    }
1993
1994    /** @hide */
1995    @Override
1996    protected void encodeProperties(@NonNull ViewHierarchyEncoder stream) {
1997        super.encodeProperties(stream);
1998
1999        stream.addProperty("progress:max", getMax());
2000        stream.addProperty("progress:progress", getProgress());
2001        stream.addProperty("progress:secondaryProgress", getSecondaryProgress());
2002        stream.addProperty("progress:indeterminate", isIndeterminate());
2003    }
2004
2005    /**
2006     * Command for sending an accessibility event.
2007     */
2008    private class AccessibilityEventSender implements Runnable {
2009        public void run() {
2010            sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_SELECTED);
2011        }
2012    }
2013
2014    private static class ProgressTintInfo {
2015        ColorStateList mIndeterminateTintList;
2016        PorterDuff.Mode mIndeterminateTintMode;
2017        boolean mHasIndeterminateTint;
2018        boolean mHasIndeterminateTintMode;
2019
2020        ColorStateList mProgressTintList;
2021        PorterDuff.Mode mProgressTintMode;
2022        boolean mHasProgressTint;
2023        boolean mHasProgressTintMode;
2024
2025        ColorStateList mProgressBackgroundTintList;
2026        PorterDuff.Mode mProgressBackgroundTintMode;
2027        boolean mHasProgressBackgroundTint;
2028        boolean mHasProgressBackgroundTintMode;
2029
2030        ColorStateList mSecondaryProgressTintList;
2031        PorterDuff.Mode mSecondaryProgressTintMode;
2032        boolean mHasSecondaryProgressTint;
2033        boolean mHasSecondaryProgressTintMode;
2034    }
2035
2036    /**
2037     * Property wrapper around the visual state of the {@code progress} functionality
2038     * handled by the {@link ProgressBar#setProgress(int, boolean)} method. This does
2039     * not correspond directly to the actual progress -- only the visual state.
2040     */
2041    private final FloatProperty<ProgressBar> VISUAL_PROGRESS =
2042            new FloatProperty<ProgressBar>("visual_progress") {
2043                @Override
2044                public void setValue(ProgressBar object, float value) {
2045                    object.setVisualProgress(R.id.progress, value);
2046                    object.mVisualProgress = value;
2047                }
2048
2049                @Override
2050                public Float get(ProgressBar object) {
2051                    return object.mVisualProgress;
2052                }
2053            };
2054}
2055