1c02d4acadccd19ec60c3f522c97bfd0922c2cf2
[pub/Android/ownCloud.git] / actionbarsherlock / src / com / actionbarsherlock / internal / widget / IcsProgressBar.java
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 com.actionbarsherlock.internal.widget;
18
19 import android.content.Context;
20 import android.content.res.TypedArray;
21 import android.graphics.Bitmap;
22 import android.graphics.BitmapShader;
23 import android.graphics.Canvas;
24 import android.graphics.Rect;
25 import android.graphics.Shader;
26 import android.graphics.drawable.Animatable;
27 import android.graphics.drawable.AnimationDrawable;
28 import android.graphics.drawable.BitmapDrawable;
29 import android.graphics.drawable.ClipDrawable;
30 import android.graphics.drawable.Drawable;
31 import android.graphics.drawable.LayerDrawable;
32 import android.graphics.drawable.ShapeDrawable;
33 import android.graphics.drawable.shapes.RoundRectShape;
34 import android.graphics.drawable.shapes.Shape;
35 import android.os.Build;
36 import android.os.Parcel;
37 import android.os.Parcelable;
38 import android.os.SystemClock;
39 import android.util.AttributeSet;
40 import android.view.Gravity;
41 import android.view.View;
42 import android.view.ViewDebug;
43 import android.view.accessibility.AccessibilityEvent;
44 import android.view.accessibility.AccessibilityManager;
45 import android.view.animation.AlphaAnimation;
46 import android.view.animation.Animation;
47 import android.view.animation.AnimationUtils;
48 import android.view.animation.Interpolator;
49 import android.view.animation.LinearInterpolator;
50 import android.view.animation.Transformation;
51 import android.widget.RemoteViews.RemoteView;
52
53
54 /**
55 * <p>
56 * Visual indicator of progress in some operation. Displays a bar to the user
57 * representing how far the operation has progressed; the application can
58 * change the amount of progress (modifying the length of the bar) as it moves
59 * forward. There is also a secondary progress displayable on a progress bar
60 * which is useful for displaying intermediate progress, such as the buffer
61 * level during a streaming playback progress bar.
62 * </p>
63 *
64 * <p>
65 * A progress bar can also be made indeterminate. In indeterminate mode, the
66 * progress bar shows a cyclic animation without an indication of progress. This mode is used by
67 * applications when the length of the task is unknown. The indeterminate progress bar can be either
68 * a spinning wheel or a horizontal bar.
69 * </p>
70 *
71 * <p>The following code example shows how a progress bar can be used from
72 * a worker thread to update the user interface to notify the user of progress:
73 * </p>
74 *
75 * <pre>
76 * public class MyActivity extends Activity {
77 * private static final int PROGRESS = 0x1;
78 *
79 * private ProgressBar mProgress;
80 * private int mProgressStatus = 0;
81 *
82 * private Handler mHandler = new Handler();
83 *
84 * protected void onCreate(Bundle icicle) {
85 * super.onCreate(icicle);
86 *
87 * setContentView(R.layout.progressbar_activity);
88 *
89 * mProgress = (ProgressBar) findViewById(R.id.progress_bar);
90 *
91 * // Start lengthy operation in a background thread
92 * new Thread(new Runnable() {
93 * public void run() {
94 * while (mProgressStatus &lt; 100) {
95 * mProgressStatus = doWork();
96 *
97 * // Update the progress bar
98 * mHandler.post(new Runnable() {
99 * public void run() {
100 * mProgress.setProgress(mProgressStatus);
101 * }
102 * });
103 * }
104 * }
105 * }).start();
106 * }
107 * }</pre>
108 *
109 * <p>To add a progress bar to a layout file, you can use the {@code &lt;ProgressBar&gt;} element.
110 * By default, the progress bar is a spinning wheel (an indeterminate indicator). To change to a
111 * horizontal progress bar, apply the {@link android.R.style#Widget_ProgressBar_Horizontal
112 * Widget.ProgressBar.Horizontal} style, like so:</p>
113 *
114 * <pre>
115 * &lt;ProgressBar
116 * style="@android:style/Widget.ProgressBar.Horizontal"
117 * ... /&gt;</pre>
118 *
119 * <p>If you will use the progress bar to show real progress, you must use the horizontal bar. You
120 * can then increment the progress with {@link #incrementProgressBy incrementProgressBy()} or
121 * {@link #setProgress setProgress()}. By default, the progress bar is full when it reaches 100. If
122 * necessary, you can adjust the maximum value (the value for a full bar) using the {@link
123 * android.R.styleable#ProgressBar_max android:max} attribute. Other attributes available are listed
124 * below.</p>
125 *
126 * <p>Another common style to apply to the progress bar is {@link
127 * android.R.style#Widget_ProgressBar_Small Widget.ProgressBar.Small}, which shows a smaller
128 * version of the spinning wheel&mdash;useful when waiting for content to load.
129 * For example, you can insert this kind of progress bar into your default layout for
130 * a view that will be populated by some content fetched from the Internet&mdash;the spinning wheel
131 * appears immediately and when your application receives the content, it replaces the progress bar
132 * with the loaded content. For example:</p>
133 *
134 * <pre>
135 * &lt;LinearLayout
136 * android:orientation="horizontal"
137 * ... &gt;
138 * &lt;ProgressBar
139 * android:layout_width="wrap_content"
140 * android:layout_height="wrap_content"
141 * style="@android:style/Widget.ProgressBar.Small"
142 * android:layout_marginRight="5dp" /&gt;
143 * &lt;TextView
144 * android:layout_width="wrap_content"
145 * android:layout_height="wrap_content"
146 * android:text="@string/loading" /&gt;
147 * &lt;/LinearLayout&gt;</pre>
148 *
149 * <p>Other progress bar styles provided by the system include:</p>
150 * <ul>
151 * <li>{@link android.R.style#Widget_ProgressBar_Horizontal Widget.ProgressBar.Horizontal}</li>
152 * <li>{@link android.R.style#Widget_ProgressBar_Small Widget.ProgressBar.Small}</li>
153 * <li>{@link android.R.style#Widget_ProgressBar_Large Widget.ProgressBar.Large}</li>
154 * <li>{@link android.R.style#Widget_ProgressBar_Inverse Widget.ProgressBar.Inverse}</li>
155 * <li>{@link android.R.style#Widget_ProgressBar_Small_Inverse
156 * Widget.ProgressBar.Small.Inverse}</li>
157 * <li>{@link android.R.style#Widget_ProgressBar_Large_Inverse
158 * Widget.ProgressBar.Large.Inverse}</li>
159 * </ul>
160 * <p>The "inverse" styles provide an inverse color scheme for the spinner, which may be necessary
161 * if your application uses a light colored theme (a white background).</p>
162 *
163 * <p><strong>XML attributes</b></strong>
164 * <p>
165 * See {@link android.R.styleable#ProgressBar ProgressBar Attributes},
166 * {@link android.R.styleable#View View Attributes}
167 * </p>
168 *
169 * @attr ref android.R.styleable#ProgressBar_animationResolution
170 * @attr ref android.R.styleable#ProgressBar_indeterminate
171 * @attr ref android.R.styleable#ProgressBar_indeterminateBehavior
172 * @attr ref android.R.styleable#ProgressBar_indeterminateDrawable
173 * @attr ref android.R.styleable#ProgressBar_indeterminateDuration
174 * @attr ref android.R.styleable#ProgressBar_indeterminateOnly
175 * @attr ref android.R.styleable#ProgressBar_interpolator
176 * @attr ref android.R.styleable#ProgressBar_max
177 * @attr ref android.R.styleable#ProgressBar_maxHeight
178 * @attr ref android.R.styleable#ProgressBar_maxWidth
179 * @attr ref android.R.styleable#ProgressBar_minHeight
180 * @attr ref android.R.styleable#ProgressBar_minWidth
181 * @attr ref android.R.styleable#ProgressBar_progress
182 * @attr ref android.R.styleable#ProgressBar_progressDrawable
183 * @attr ref android.R.styleable#ProgressBar_secondaryProgress
184 */
185 @RemoteView
186 public class IcsProgressBar extends View {
187 private static final boolean IS_HONEYCOMB = Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB;
188 private static final int MAX_LEVEL = 10000;
189 private static final int ANIMATION_RESOLUTION = 200;
190 private static final int TIMEOUT_SEND_ACCESSIBILITY_EVENT = 200;
191
192 private static final int[] ProgressBar = new int[] {
193 android.R.attr.maxWidth,
194 android.R.attr.maxHeight,
195 android.R.attr.max,
196 android.R.attr.progress,
197 android.R.attr.secondaryProgress,
198 android.R.attr.indeterminate,
199 android.R.attr.indeterminateOnly,
200 android.R.attr.indeterminateDrawable,
201 android.R.attr.progressDrawable,
202 android.R.attr.indeterminateDuration,
203 android.R.attr.indeterminateBehavior,
204 android.R.attr.minWidth,
205 android.R.attr.minHeight,
206 android.R.attr.interpolator,
207 android.R.attr.animationResolution,
208 };
209 private static final int ProgressBar_maxWidth = 0;
210 private static final int ProgressBar_maxHeight = 1;
211 private static final int ProgressBar_max = 2;
212 private static final int ProgressBar_progress = 3;
213 private static final int ProgressBar_secondaryProgress = 4;
214 private static final int ProgressBar_indeterminate = 5;
215 private static final int ProgressBar_indeterminateOnly = 6;
216 private static final int ProgressBar_indeterminateDrawable = 7;
217 private static final int ProgressBar_progressDrawable = 8;
218 private static final int ProgressBar_indeterminateDuration = 9;
219 private static final int ProgressBar_indeterminateBehavior = 10;
220 private static final int ProgressBar_minWidth = 11;
221 private static final int ProgressBar_minHeight = 12;
222 private static final int ProgressBar_interpolator = 13;
223 private static final int ProgressBar_animationResolution = 14;
224
225 int mMinWidth;
226 int mMaxWidth;
227 int mMinHeight;
228 int mMaxHeight;
229
230 private int mProgress;
231 private int mSecondaryProgress;
232 private int mMax;
233
234 private int mBehavior;
235 private int mDuration;
236 private boolean mIndeterminate;
237 private boolean mOnlyIndeterminate;
238 private Transformation mTransformation;
239 private AlphaAnimation mAnimation;
240 private Drawable mIndeterminateDrawable;
241 private int mIndeterminateRealLeft;
242 private int mIndeterminateRealTop;
243 private Drawable mProgressDrawable;
244 private Drawable mCurrentDrawable;
245 Bitmap mSampleTile;
246 private boolean mNoInvalidate;
247 private Interpolator mInterpolator;
248 private RefreshProgressRunnable mRefreshProgressRunnable;
249 private long mUiThreadId;
250 private boolean mShouldStartAnimationDrawable;
251 private long mLastDrawTime;
252
253 private boolean mInDrawing;
254
255 private int mAnimationResolution;
256
257 private AccessibilityManager mAccessibilityManager;
258 private AccessibilityEventSender mAccessibilityEventSender;
259
260 /**
261 * Create a new progress bar with range 0...100 and initial progress of 0.
262 * @param context the application environment
263 */
264 public IcsProgressBar(Context context) {
265 this(context, null);
266 }
267
268 public IcsProgressBar(Context context, AttributeSet attrs) {
269 this(context, attrs, android.R.attr.progressBarStyle);
270 }
271
272 public IcsProgressBar(Context context, AttributeSet attrs, int defStyle) {
273 this(context, attrs, defStyle, 0);
274 }
275
276 /**
277 * @hide
278 */
279 public IcsProgressBar(Context context, AttributeSet attrs, int defStyle, int styleRes) {
280 super(context, attrs, defStyle);
281 mUiThreadId = Thread.currentThread().getId();
282 initProgressBar();
283
284 TypedArray a =
285 context.obtainStyledAttributes(attrs, /*R.styleable.*/ProgressBar, defStyle, styleRes);
286
287 mNoInvalidate = true;
288
289 Drawable drawable = a.getDrawable(/*R.styleable.*/ProgressBar_progressDrawable);
290 if (drawable != null) {
291 drawable = tileify(drawable, false);
292 // Calling this method can set mMaxHeight, make sure the corresponding
293 // XML attribute for mMaxHeight is read after calling this method
294 setProgressDrawable(drawable);
295 }
296
297
298 mDuration = a.getInt(/*R.styleable.*/ProgressBar_indeterminateDuration, mDuration);
299
300 mMinWidth = a.getDimensionPixelSize(/*R.styleable.*/ProgressBar_minWidth, mMinWidth);
301 mMaxWidth = a.getDimensionPixelSize(/*R.styleable.*/ProgressBar_maxWidth, mMaxWidth);
302 mMinHeight = a.getDimensionPixelSize(/*R.styleable.*/ProgressBar_minHeight, mMinHeight);
303 mMaxHeight = a.getDimensionPixelSize(/*R.styleable.*/ProgressBar_maxHeight, mMaxHeight);
304
305 mBehavior = a.getInt(/*R.styleable.*/ProgressBar_indeterminateBehavior, mBehavior);
306
307 final int resID = a.getResourceId(
308 /*com.android.internal.R.styleable.*/ProgressBar_interpolator,
309 android.R.anim.linear_interpolator); // default to linear interpolator
310 if (resID > 0) {
311 setInterpolator(context, resID);
312 }
313
314 setMax(a.getInt(/*R.styleable.*/ProgressBar_max, mMax));
315
316 setProgress(a.getInt(/*R.styleable.*/ProgressBar_progress, mProgress));
317
318 setSecondaryProgress(
319 a.getInt(/*R.styleable.*/ProgressBar_secondaryProgress, mSecondaryProgress));
320
321 drawable = a.getDrawable(/*R.styleable.*/ProgressBar_indeterminateDrawable);
322 if (drawable != null) {
323 drawable = tileifyIndeterminate(drawable);
324 setIndeterminateDrawable(drawable);
325 }
326
327 mOnlyIndeterminate = a.getBoolean(
328 /*R.styleable.*/ProgressBar_indeterminateOnly, mOnlyIndeterminate);
329
330 mNoInvalidate = false;
331
332 setIndeterminate(mOnlyIndeterminate || a.getBoolean(
333 /*R.styleable.*/ProgressBar_indeterminate, mIndeterminate));
334
335 mAnimationResolution = a.getInteger(/*R.styleable.*/ProgressBar_animationResolution,
336 ANIMATION_RESOLUTION);
337
338 a.recycle();
339
340 mAccessibilityManager = (AccessibilityManager)context.getSystemService(Context.ACCESSIBILITY_SERVICE);
341 }
342
343 /**
344 * Converts a drawable to a tiled version of itself. It will recursively
345 * traverse layer and state list drawables.
346 */
347 private Drawable tileify(Drawable drawable, boolean clip) {
348
349 if (drawable instanceof LayerDrawable) {
350 LayerDrawable background = (LayerDrawable) drawable;
351 final int N = background.getNumberOfLayers();
352 Drawable[] outDrawables = new Drawable[N];
353
354 for (int i = 0; i < N; i++) {
355 int id = background.getId(i);
356 outDrawables[i] = tileify(background.getDrawable(i),
357 (id == android.R.id.progress || id == android.R.id.secondaryProgress));
358 }
359
360 LayerDrawable newBg = new LayerDrawable(outDrawables);
361
362 for (int i = 0; i < N; i++) {
363 newBg.setId(i, background.getId(i));
364 }
365
366 return newBg;
367
368 }/* else if (drawable instanceof StateListDrawable) {
369 StateListDrawable in = (StateListDrawable) drawable;
370 StateListDrawable out = new StateListDrawable();
371 int numStates = in.getStateCount();
372 for (int i = 0; i < numStates; i++) {
373 out.addState(in.getStateSet(i), tileify(in.getStateDrawable(i), clip));
374 }
375 return out;
376
377 }*/ else if (drawable instanceof BitmapDrawable) {
378 final Bitmap tileBitmap = ((BitmapDrawable) drawable).getBitmap();
379 if (mSampleTile == null) {
380 mSampleTile = tileBitmap;
381 }
382
383 final ShapeDrawable shapeDrawable = new ShapeDrawable(getDrawableShape());
384
385 final BitmapShader bitmapShader = new BitmapShader(tileBitmap,
386 Shader.TileMode.REPEAT, Shader.TileMode.CLAMP);
387 shapeDrawable.getPaint().setShader(bitmapShader);
388
389 return (clip) ? new ClipDrawable(shapeDrawable, Gravity.LEFT,
390 ClipDrawable.HORIZONTAL) : shapeDrawable;
391 }
392
393 return drawable;
394 }
395
396 Shape getDrawableShape() {
397 final float[] roundedCorners = new float[] { 5, 5, 5, 5, 5, 5, 5, 5 };
398 return new RoundRectShape(roundedCorners, null, null);
399 }
400
401 /**
402 * Convert a AnimationDrawable for use as a barberpole animation.
403 * Each frame of the animation is wrapped in a ClipDrawable and
404 * given a tiling BitmapShader.
405 */
406 private Drawable tileifyIndeterminate(Drawable drawable) {
407 if (drawable instanceof AnimationDrawable) {
408 AnimationDrawable background = (AnimationDrawable) drawable;
409 final int N = background.getNumberOfFrames();
410 AnimationDrawable newBg = new AnimationDrawable();
411 newBg.setOneShot(background.isOneShot());
412
413 for (int i = 0; i < N; i++) {
414 Drawable frame = tileify(background.getFrame(i), true);
415 frame.setLevel(10000);
416 newBg.addFrame(frame, background.getDuration(i));
417 }
418 newBg.setLevel(10000);
419 drawable = newBg;
420 }
421 return drawable;
422 }
423
424 /**
425 * <p>
426 * Initialize the progress bar's default values:
427 * </p>
428 * <ul>
429 * <li>progress = 0</li>
430 * <li>max = 100</li>
431 * <li>animation duration = 4000 ms</li>
432 * <li>indeterminate = false</li>
433 * <li>behavior = repeat</li>
434 * </ul>
435 */
436 private void initProgressBar() {
437 mMax = 100;
438 mProgress = 0;
439 mSecondaryProgress = 0;
440 mIndeterminate = false;
441 mOnlyIndeterminate = false;
442 mDuration = 4000;
443 mBehavior = AlphaAnimation.RESTART;
444 mMinWidth = 24;
445 mMaxWidth = 48;
446 mMinHeight = 24;
447 mMaxHeight = 48;
448 }
449
450 /**
451 * <p>Indicate whether this progress bar is in indeterminate mode.</p>
452 *
453 * @return true if the progress bar is in indeterminate mode
454 */
455 @ViewDebug.ExportedProperty(category = "progress")
456 public synchronized boolean isIndeterminate() {
457 return mIndeterminate;
458 }
459
460 /**
461 * <p>Change the indeterminate mode for this progress bar. In indeterminate
462 * mode, the progress is ignored and the progress bar shows an infinite
463 * animation instead.</p>
464 *
465 * If this progress bar's style only supports indeterminate mode (such as the circular
466 * progress bars), then this will be ignored.
467 *
468 * @param indeterminate true to enable the indeterminate mode
469 */
470 public synchronized void setIndeterminate(boolean indeterminate) {
471 if ((!mOnlyIndeterminate || !mIndeterminate) && indeterminate != mIndeterminate) {
472 mIndeterminate = indeterminate;
473
474 if (indeterminate) {
475 // swap between indeterminate and regular backgrounds
476 mCurrentDrawable = mIndeterminateDrawable;
477 startAnimation();
478 } else {
479 mCurrentDrawable = mProgressDrawable;
480 stopAnimation();
481 }
482 }
483 }
484
485 /**
486 * <p>Get the drawable used to draw the progress bar in
487 * indeterminate mode.</p>
488 *
489 * @return a {@link android.graphics.drawable.Drawable} instance
490 *
491 * @see #setIndeterminateDrawable(android.graphics.drawable.Drawable)
492 * @see #setIndeterminate(boolean)
493 */
494 public Drawable getIndeterminateDrawable() {
495 return mIndeterminateDrawable;
496 }
497
498 /**
499 * <p>Define the drawable used to draw the progress bar in
500 * indeterminate mode.</p>
501 *
502 * @param d the new drawable
503 *
504 * @see #getIndeterminateDrawable()
505 * @see #setIndeterminate(boolean)
506 */
507 public void setIndeterminateDrawable(Drawable d) {
508 if (d != null) {
509 d.setCallback(this);
510 }
511 mIndeterminateDrawable = d;
512 if (mIndeterminate) {
513 mCurrentDrawable = d;
514 postInvalidate();
515 }
516 }
517
518 /**
519 * <p>Get the drawable used to draw the progress bar in
520 * progress mode.</p>
521 *
522 * @return a {@link android.graphics.drawable.Drawable} instance
523 *
524 * @see #setProgressDrawable(android.graphics.drawable.Drawable)
525 * @see #setIndeterminate(boolean)
526 */
527 public Drawable getProgressDrawable() {
528 return mProgressDrawable;
529 }
530
531 /**
532 * <p>Define the drawable used to draw the progress bar in
533 * progress mode.</p>
534 *
535 * @param d the new drawable
536 *
537 * @see #getProgressDrawable()
538 * @see #setIndeterminate(boolean)
539 */
540 public void setProgressDrawable(Drawable d) {
541 boolean needUpdate;
542 if (mProgressDrawable != null && d != mProgressDrawable) {
543 mProgressDrawable.setCallback(null);
544 needUpdate = true;
545 } else {
546 needUpdate = false;
547 }
548
549 if (d != null) {
550 d.setCallback(this);
551
552 // Make sure the ProgressBar is always tall enough
553 int drawableHeight = d.getMinimumHeight();
554 if (mMaxHeight < drawableHeight) {
555 mMaxHeight = drawableHeight;
556 requestLayout();
557 }
558 }
559 mProgressDrawable = d;
560 if (!mIndeterminate) {
561 mCurrentDrawable = d;
562 postInvalidate();
563 }
564
565 if (needUpdate) {
566 updateDrawableBounds(getWidth(), getHeight());
567 updateDrawableState();
568 doRefreshProgress(android.R.id.progress, mProgress, false, false);
569 doRefreshProgress(android.R.id.secondaryProgress, mSecondaryProgress, false, false);
570 }
571 }
572
573 /**
574 * @return The drawable currently used to draw the progress bar
575 */
576 Drawable getCurrentDrawable() {
577 return mCurrentDrawable;
578 }
579
580 @Override
581 protected boolean verifyDrawable(Drawable who) {
582 return who == mProgressDrawable || who == mIndeterminateDrawable
583 || super.verifyDrawable(who);
584 }
585
586 @Override
587 public void jumpDrawablesToCurrentState() {
588 super.jumpDrawablesToCurrentState();
589 if (mProgressDrawable != null) mProgressDrawable.jumpToCurrentState();
590 if (mIndeterminateDrawable != null) mIndeterminateDrawable.jumpToCurrentState();
591 }
592
593 @Override
594 public void postInvalidate() {
595 if (!mNoInvalidate) {
596 super.postInvalidate();
597 }
598 }
599
600 private class RefreshProgressRunnable implements Runnable {
601
602 private int mId;
603 private int mProgress;
604 private boolean mFromUser;
605
606 RefreshProgressRunnable(int id, int progress, boolean fromUser) {
607 mId = id;
608 mProgress = progress;
609 mFromUser = fromUser;
610 }
611
612 public void run() {
613 doRefreshProgress(mId, mProgress, mFromUser, true);
614 // Put ourselves back in the cache when we are done
615 mRefreshProgressRunnable = this;
616 }
617
618 public void setup(int id, int progress, boolean fromUser) {
619 mId = id;
620 mProgress = progress;
621 mFromUser = fromUser;
622 }
623
624 }
625
626 private synchronized void doRefreshProgress(int id, int progress, boolean fromUser,
627 boolean callBackToApp) {
628 float scale = mMax > 0 ? (float) progress / (float) mMax : 0;
629 final Drawable d = mCurrentDrawable;
630 if (d != null) {
631 Drawable progressDrawable = null;
632
633 if (d instanceof LayerDrawable) {
634 progressDrawable = ((LayerDrawable) d).findDrawableByLayerId(id);
635 }
636
637 final int level = (int) (scale * MAX_LEVEL);
638 (progressDrawable != null ? progressDrawable : d).setLevel(level);
639 } else {
640 invalidate();
641 }
642
643 if (callBackToApp && id == android.R.id.progress) {
644 onProgressRefresh(scale, fromUser);
645 }
646 }
647
648 void onProgressRefresh(float scale, boolean fromUser) {
649 if (mAccessibilityManager.isEnabled()) {
650 scheduleAccessibilityEventSender();
651 }
652 }
653
654 private synchronized void refreshProgress(int id, int progress, boolean fromUser) {
655 if (mUiThreadId == Thread.currentThread().getId()) {
656 doRefreshProgress(id, progress, fromUser, true);
657 } else {
658 RefreshProgressRunnable r;
659 if (mRefreshProgressRunnable != null) {
660 // Use cached RefreshProgressRunnable if available
661 r = mRefreshProgressRunnable;
662 // Uncache it
663 mRefreshProgressRunnable = null;
664 r.setup(id, progress, fromUser);
665 } else {
666 // Make a new one
667 r = new RefreshProgressRunnable(id, progress, fromUser);
668 }
669 post(r);
670 }
671 }
672
673 /**
674 * <p>Set the current progress to the specified value. Does not do anything
675 * if the progress bar is in indeterminate mode.</p>
676 *
677 * @param progress the new progress, between 0 and {@link #getMax()}
678 *
679 * @see #setIndeterminate(boolean)
680 * @see #isIndeterminate()
681 * @see #getProgress()
682 * @see #incrementProgressBy(int)
683 */
684 public synchronized void setProgress(int progress) {
685 setProgress(progress, false);
686 }
687
688 synchronized void setProgress(int progress, boolean fromUser) {
689 if (mIndeterminate) {
690 return;
691 }
692
693 if (progress < 0) {
694 progress = 0;
695 }
696
697 if (progress > mMax) {
698 progress = mMax;
699 }
700
701 if (progress != mProgress) {
702 mProgress = progress;
703 refreshProgress(android.R.id.progress, mProgress, fromUser);
704 }
705 }
706
707 /**
708 * <p>
709 * Set the current secondary progress to the specified value. Does not do
710 * anything if the progress bar is in indeterminate mode.
711 * </p>
712 *
713 * @param secondaryProgress the new secondary progress, between 0 and {@link #getMax()}
714 * @see #setIndeterminate(boolean)
715 * @see #isIndeterminate()
716 * @see #getSecondaryProgress()
717 * @see #incrementSecondaryProgressBy(int)
718 */
719 public synchronized void setSecondaryProgress(int secondaryProgress) {
720 if (mIndeterminate) {
721 return;
722 }
723
724 if (secondaryProgress < 0) {
725 secondaryProgress = 0;
726 }
727
728 if (secondaryProgress > mMax) {
729 secondaryProgress = mMax;
730 }
731
732 if (secondaryProgress != mSecondaryProgress) {
733 mSecondaryProgress = secondaryProgress;
734 refreshProgress(android.R.id.secondaryProgress, mSecondaryProgress, false);
735 }
736 }
737
738 /**
739 * <p>Get the progress bar's current level of progress. Return 0 when the
740 * progress bar is in indeterminate mode.</p>
741 *
742 * @return the current progress, between 0 and {@link #getMax()}
743 *
744 * @see #setIndeterminate(boolean)
745 * @see #isIndeterminate()
746 * @see #setProgress(int)
747 * @see #setMax(int)
748 * @see #getMax()
749 */
750 @ViewDebug.ExportedProperty(category = "progress")
751 public synchronized int getProgress() {
752 return mIndeterminate ? 0 : mProgress;
753 }
754
755 /**
756 * <p>Get the progress bar's current level of secondary progress. Return 0 when the
757 * progress bar is in indeterminate mode.</p>
758 *
759 * @return the current secondary progress, between 0 and {@link #getMax()}
760 *
761 * @see #setIndeterminate(boolean)
762 * @see #isIndeterminate()
763 * @see #setSecondaryProgress(int)
764 * @see #setMax(int)
765 * @see #getMax()
766 */
767 @ViewDebug.ExportedProperty(category = "progress")
768 public synchronized int getSecondaryProgress() {
769 return mIndeterminate ? 0 : mSecondaryProgress;
770 }
771
772 /**
773 * <p>Return the upper limit of this progress bar's range.</p>
774 *
775 * @return a positive integer
776 *
777 * @see #setMax(int)
778 * @see #getProgress()
779 * @see #getSecondaryProgress()
780 */
781 @ViewDebug.ExportedProperty(category = "progress")
782 public synchronized int getMax() {
783 return mMax;
784 }
785
786 /**
787 * <p>Set the range of the progress bar to 0...<tt>max</tt>.</p>
788 *
789 * @param max the upper range of this progress bar
790 *
791 * @see #getMax()
792 * @see #setProgress(int)
793 * @see #setSecondaryProgress(int)
794 */
795 public synchronized void setMax(int max) {
796 if (max < 0) {
797 max = 0;
798 }
799 if (max != mMax) {
800 mMax = max;
801 postInvalidate();
802
803 if (mProgress > max) {
804 mProgress = max;
805 }
806 refreshProgress(android.R.id.progress, mProgress, false);
807 }
808 }
809
810 /**
811 * <p>Increase the progress bar's progress by the specified amount.</p>
812 *
813 * @param diff the amount by which the progress must be increased
814 *
815 * @see #setProgress(int)
816 */
817 public synchronized final void incrementProgressBy(int diff) {
818 setProgress(mProgress + diff);
819 }
820
821 /**
822 * <p>Increase the progress bar's secondary progress by the specified amount.</p>
823 *
824 * @param diff the amount by which the secondary progress must be increased
825 *
826 * @see #setSecondaryProgress(int)
827 */
828 public synchronized final void incrementSecondaryProgressBy(int diff) {
829 setSecondaryProgress(mSecondaryProgress + diff);
830 }
831
832 /**
833 * <p>Start the indeterminate progress animation.</p>
834 */
835 void startAnimation() {
836 if (getVisibility() != VISIBLE) {
837 return;
838 }
839
840 if (mIndeterminateDrawable instanceof Animatable) {
841 mShouldStartAnimationDrawable = true;
842 mAnimation = null;
843 } else {
844 if (mInterpolator == null) {
845 mInterpolator = new LinearInterpolator();
846 }
847
848 mTransformation = new Transformation();
849 mAnimation = new AlphaAnimation(0.0f, 1.0f);
850 mAnimation.setRepeatMode(mBehavior);
851 mAnimation.setRepeatCount(Animation.INFINITE);
852 mAnimation.setDuration(mDuration);
853 mAnimation.setInterpolator(mInterpolator);
854 mAnimation.setStartTime(Animation.START_ON_FIRST_FRAME);
855 }
856 postInvalidate();
857 }
858
859 /**
860 * <p>Stop the indeterminate progress animation.</p>
861 */
862 void stopAnimation() {
863 mAnimation = null;
864 mTransformation = null;
865 if (mIndeterminateDrawable instanceof Animatable) {
866 ((Animatable) mIndeterminateDrawable).stop();
867 mShouldStartAnimationDrawable = false;
868 }
869 postInvalidate();
870 }
871
872 /**
873 * Sets the acceleration curve for the indeterminate animation.
874 * The interpolator is loaded as a resource from the specified context.
875 *
876 * @param context The application environment
877 * @param resID The resource identifier of the interpolator to load
878 */
879 public void setInterpolator(Context context, int resID) {
880 setInterpolator(AnimationUtils.loadInterpolator(context, resID));
881 }
882
883 /**
884 * Sets the acceleration curve for the indeterminate animation.
885 * Defaults to a linear interpolation.
886 *
887 * @param interpolator The interpolator which defines the acceleration curve
888 */
889 public void setInterpolator(Interpolator interpolator) {
890 mInterpolator = interpolator;
891 }
892
893 /**
894 * Gets the acceleration curve type for the indeterminate animation.
895 *
896 * @return the {@link Interpolator} associated to this animation
897 */
898 public Interpolator getInterpolator() {
899 return mInterpolator;
900 }
901
902 @Override
903 public void setVisibility(int v) {
904 if (getVisibility() != v) {
905 super.setVisibility(v);
906
907 if (mIndeterminate) {
908 // let's be nice with the UI thread
909 if (v == GONE || v == INVISIBLE) {
910 stopAnimation();
911 } else {
912 startAnimation();
913 }
914 }
915 }
916 }
917
918 @Override
919 protected void onVisibilityChanged(View changedView, int visibility) {
920 super.onVisibilityChanged(changedView, visibility);
921
922 if (mIndeterminate) {
923 // let's be nice with the UI thread
924 if (visibility == GONE || visibility == INVISIBLE) {
925 stopAnimation();
926 } else {
927 startAnimation();
928 }
929 }
930 }
931
932 @Override
933 public void invalidateDrawable(Drawable dr) {
934 if (!mInDrawing) {
935 if (verifyDrawable(dr)) {
936 final Rect dirty = dr.getBounds();
937 final int scrollX = getScrollX() + getPaddingLeft();
938 final int scrollY = getScrollY() + getPaddingTop();
939
940 invalidate(dirty.left + scrollX, dirty.top + scrollY,
941 dirty.right + scrollX, dirty.bottom + scrollY);
942 } else {
943 super.invalidateDrawable(dr);
944 }
945 }
946 }
947
948 /**
949 * @hide
950 *
951 @Override
952 public int getResolvedLayoutDirection(Drawable who) {
953 return (who == mProgressDrawable || who == mIndeterminateDrawable) ?
954 getResolvedLayoutDirection() : super.getResolvedLayoutDirection(who);
955 }
956 */
957
958 @Override
959 protected void onSizeChanged(int w, int h, int oldw, int oldh) {
960 updateDrawableBounds(w, h);
961 }
962
963 private void updateDrawableBounds(int w, int h) {
964 // onDraw will translate the canvas so we draw starting at 0,0
965 int right = w - getPaddingRight() - getPaddingLeft();
966 int bottom = h - getPaddingBottom() - getPaddingTop();
967 int top = 0;
968 int left = 0;
969
970 if (mIndeterminateDrawable != null) {
971 // Aspect ratio logic does not apply to AnimationDrawables
972 if (mOnlyIndeterminate && !(mIndeterminateDrawable instanceof AnimationDrawable)) {
973 // Maintain aspect ratio. Certain kinds of animated drawables
974 // get very confused otherwise.
975 final int intrinsicWidth = mIndeterminateDrawable.getIntrinsicWidth();
976 final int intrinsicHeight = mIndeterminateDrawable.getIntrinsicHeight();
977 final float intrinsicAspect = (float) intrinsicWidth / intrinsicHeight;
978 final float boundAspect = (float) w / h;
979 if (intrinsicAspect != boundAspect) {
980 if (boundAspect > intrinsicAspect) {
981 // New width is larger. Make it smaller to match height.
982 final int width = (int) (h * intrinsicAspect);
983 left = (w - width) / 2;
984 right = left + width;
985 } else {
986 // New height is larger. Make it smaller to match width.
987 final int height = (int) (w * (1 / intrinsicAspect));
988 top = (h - height) / 2;
989 bottom = top + height;
990 }
991 }
992 }
993 mIndeterminateDrawable.setBounds(0, 0, right - left, bottom - top);
994 mIndeterminateRealLeft = left;
995 mIndeterminateRealTop = top;
996 }
997
998 if (mProgressDrawable != null) {
999 mProgressDrawable.setBounds(0, 0, right, bottom);
1000 }
1001 }
1002
1003 @Override
1004 protected synchronized void onDraw(Canvas canvas) {
1005 super.onDraw(canvas);
1006
1007 Drawable d = mCurrentDrawable;
1008 if (d != null) {
1009 // Translate canvas so a indeterminate circular progress bar with padding
1010 // rotates properly in its animation
1011 canvas.save();
1012 canvas.translate(getPaddingLeft() + mIndeterminateRealLeft, getPaddingTop() + mIndeterminateRealTop);
1013 long time = getDrawingTime();
1014 if (mAnimation != null) {
1015 mAnimation.getTransformation(time, mTransformation);
1016 float scale = mTransformation.getAlpha();
1017 try {
1018 mInDrawing = true;
1019 d.setLevel((int) (scale * MAX_LEVEL));
1020 } finally {
1021 mInDrawing = false;
1022 }
1023 if (SystemClock.uptimeMillis() - mLastDrawTime >= mAnimationResolution) {
1024 mLastDrawTime = SystemClock.uptimeMillis();
1025 postInvalidateDelayed(mAnimationResolution);
1026 }
1027 }
1028 d.draw(canvas);
1029 canvas.restore();
1030 if (mShouldStartAnimationDrawable && d instanceof Animatable) {
1031 ((Animatable) d).start();
1032 mShouldStartAnimationDrawable = false;
1033 }
1034 }
1035 }
1036
1037 @Override
1038 protected synchronized void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
1039 Drawable d = mCurrentDrawable;
1040
1041 int dw = 0;
1042 int dh = 0;
1043 if (d != null) {
1044 dw = Math.max(mMinWidth, Math.min(mMaxWidth, d.getIntrinsicWidth()));
1045 dh = Math.max(mMinHeight, Math.min(mMaxHeight, d.getIntrinsicHeight()));
1046 }
1047 updateDrawableState();
1048 dw += getPaddingLeft() + getPaddingRight();
1049 dh += getPaddingTop() + getPaddingBottom();
1050
1051 if (IS_HONEYCOMB) {
1052 setMeasuredDimension(View.resolveSizeAndState(dw, widthMeasureSpec, 0),
1053 View.resolveSizeAndState(dh, heightMeasureSpec, 0));
1054 } else {
1055 setMeasuredDimension(View.resolveSize(dw, widthMeasureSpec),
1056 View.resolveSize(dh, heightMeasureSpec));
1057 }
1058 }
1059
1060 @Override
1061 protected void drawableStateChanged() {
1062 super.drawableStateChanged();
1063 updateDrawableState();
1064 }
1065
1066 private void updateDrawableState() {
1067 int[] state = getDrawableState();
1068
1069 if (mProgressDrawable != null && mProgressDrawable.isStateful()) {
1070 mProgressDrawable.setState(state);
1071 }
1072
1073 if (mIndeterminateDrawable != null && mIndeterminateDrawable.isStateful()) {
1074 mIndeterminateDrawable.setState(state);
1075 }
1076 }
1077
1078 static class SavedState extends BaseSavedState {
1079 int progress;
1080 int secondaryProgress;
1081
1082 /**
1083 * Constructor called from {@link IcsProgressBar#onSaveInstanceState()}
1084 */
1085 SavedState(Parcelable superState) {
1086 super(superState);
1087 }
1088
1089 /**
1090 * Constructor called from {@link #CREATOR}
1091 */
1092 private SavedState(Parcel in) {
1093 super(in);
1094 progress = in.readInt();
1095 secondaryProgress = in.readInt();
1096 }
1097
1098 @Override
1099 public void writeToParcel(Parcel out, int flags) {
1100 super.writeToParcel(out, flags);
1101 out.writeInt(progress);
1102 out.writeInt(secondaryProgress);
1103 }
1104
1105 public static final Parcelable.Creator<SavedState> CREATOR
1106 = new Parcelable.Creator<SavedState>() {
1107 public SavedState createFromParcel(Parcel in) {
1108 return new SavedState(in);
1109 }
1110
1111 public SavedState[] newArray(int size) {
1112 return new SavedState[size];
1113 }
1114 };
1115 }
1116
1117 @Override
1118 public Parcelable onSaveInstanceState() {
1119 // Force our ancestor class to save its state
1120 Parcelable superState = super.onSaveInstanceState();
1121 SavedState ss = new SavedState(superState);
1122
1123 ss.progress = mProgress;
1124 ss.secondaryProgress = mSecondaryProgress;
1125
1126 return ss;
1127 }
1128
1129 @Override
1130 public void onRestoreInstanceState(Parcelable state) {
1131 SavedState ss = (SavedState) state;
1132 super.onRestoreInstanceState(ss.getSuperState());
1133
1134 setProgress(ss.progress);
1135 setSecondaryProgress(ss.secondaryProgress);
1136 }
1137
1138 @Override
1139 protected void onAttachedToWindow() {
1140 super.onAttachedToWindow();
1141 if (mIndeterminate) {
1142 startAnimation();
1143 }
1144 }
1145
1146 @Override
1147 protected void onDetachedFromWindow() {
1148 if (mIndeterminate) {
1149 stopAnimation();
1150 }
1151 if(mRefreshProgressRunnable != null) {
1152 removeCallbacks(mRefreshProgressRunnable);
1153 }
1154 if (mAccessibilityEventSender != null) {
1155 removeCallbacks(mAccessibilityEventSender);
1156 }
1157 // This should come after stopAnimation(), otherwise an invalidate message remains in the
1158 // queue, which can prevent the entire view hierarchy from being GC'ed during a rotation
1159 super.onDetachedFromWindow();
1160 }
1161
1162 @Override
1163 public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
1164 super.onInitializeAccessibilityEvent(event);
1165 event.setItemCount(mMax);
1166 event.setCurrentItemIndex(mProgress);
1167 }
1168
1169 /**
1170 * Schedule a command for sending an accessibility event.
1171 * </br>
1172 * Note: A command is used to ensure that accessibility events
1173 * are sent at most one in a given time frame to save
1174 * system resources while the progress changes quickly.
1175 */
1176 private void scheduleAccessibilityEventSender() {
1177 if (mAccessibilityEventSender == null) {
1178 mAccessibilityEventSender = new AccessibilityEventSender();
1179 } else {
1180 removeCallbacks(mAccessibilityEventSender);
1181 }
1182 postDelayed(mAccessibilityEventSender, TIMEOUT_SEND_ACCESSIBILITY_EVENT);
1183 }
1184
1185 /**
1186 * Command for sending an accessibility event.
1187 */
1188 private class AccessibilityEventSender implements Runnable {
1189 public void run() {
1190 sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_SELECTED);
1191 }
1192 }
1193 }