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