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