1 /*
2  * Copyright (C) 2014 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.android.wallpaper.widget;
18 
19 import android.content.Context;
20 import android.content.res.Resources;
21 import android.graphics.Canvas;
22 import android.graphics.Color;
23 import android.graphics.ColorFilter;
24 import android.graphics.Paint;
25 import android.graphics.Paint.Style;
26 import android.graphics.Path;
27 import android.graphics.PixelFormat;
28 import android.graphics.Rect;
29 import android.graphics.RectF;
30 import android.graphics.drawable.Animatable;
31 import android.graphics.drawable.Drawable;
32 import android.util.DisplayMetrics;
33 import android.view.View;
34 import android.view.animation.Animation;
35 import android.view.animation.Interpolator;
36 import android.view.animation.LinearInterpolator;
37 import android.view.animation.Transformation;
38 
39 import java.lang.annotation.Retention;
40 import java.lang.annotation.RetentionPolicy;
41 import java.util.ArrayList;
42 
43 import androidx.annotation.IntDef;
44 import androidx.annotation.NonNull;
45 import androidx.interpolator.view.animation.FastOutSlowInInterpolator;
46 
47 /**
48  * Fancy progress indicator for Material theme.
49  * <p>
50  * Copied from //frameworks/support/v4/java/android/support/v4/widget.
51  */
52 public class MaterialProgressDrawable extends Drawable implements Animatable {
53     // Maps to ProgressBar.Large style
54     public static final int LARGE = 0;
55     // Maps to ProgressBar default style
56     public static final int DEFAULT = 1;
57     private static final Interpolator LINEAR_INTERPOLATOR = new LinearInterpolator();
58     private static final Interpolator MATERIAL_INTERPOLATOR = new FastOutSlowInInterpolator();
59     private static final float FULL_ROTATION = 1080.0f;
60     // Maps to ProgressBar default style
61     private static final int CIRCLE_DIAMETER = 40;
62     private static final float CENTER_RADIUS = 8.75f; //should add up to 10 when + stroke_width
63     private static final float STROKE_WIDTH = 2.5f;
64     // Maps to ProgressBar.Large style
65     private static final int CIRCLE_DIAMETER_LARGE = 56;
66     private static final float CENTER_RADIUS_LARGE = 12.5f;
67     private static final float STROKE_WIDTH_LARGE = 3f;
68     /**
69      * The value in the linear interpolator for animating the drawable at which
70      * the color transition should start
71      */
72     private static final float COLOR_START_DELAY_OFFSET = 0.75f;
73     private static final float END_TRIM_START_DELAY_OFFSET = 0.5f;
74     private static final float START_TRIM_DURATION_OFFSET = 0.5f;
75     /**
76      * The duration of a single progress spin in milliseconds.
77      */
78     private static final int ANIMATION_DURATION = 1332;
79     /**
80      * The number of points in the progress "star".
81      */
82     private static final float NUM_POINTS = 5f;
83     /**
84      * Layout info for the arrowhead in dp
85      */
86     private static final int ARROW_WIDTH = 10;
87     private static final int ARROW_HEIGHT = 5;
88     private static final float ARROW_OFFSET_ANGLE = 5;
89     /**
90      * Layout info for the arrowhead for the large spinner in dp
91      */
92     private static final int ARROW_WIDTH_LARGE = 12;
93     private static final int ARROW_HEIGHT_LARGE = 6;
94     private static final float MAX_PROGRESS_ARC = .8f;
95     private final int[] colors = new int[]{
96             Color.BLACK
97     };
98     /**
99      * The list of animators operating on this drawable.
100      */
101     private final ArrayList<Animation> mAnimators = new ArrayList<Animation>();
102     /**
103      * The indicator ring, used to manage animation state.
104      */
105     private final Ring mRing;
106     private final Callback mCallback = new Callback() {
107         @Override
108         public void invalidateDrawable(Drawable d) {
109             invalidateSelf();
110         }
111 
112         @Override
113         public void scheduleDrawable(Drawable d, Runnable what, long when) {
114             scheduleSelf(what, when);
115         }
116 
117         @Override
118         public void unscheduleDrawable(Drawable d, Runnable what) {
119             unscheduleSelf(what);
120         }
121     };
122     boolean mFinishing;
123     /**
124      * Canvas rotation in degrees.
125      */
126     private float mRotation;
127     private Resources mResources;
128     private View mParent;
129     private Animation mAnimation;
130     private float mRotationCount;
131     private double mWidth;
132     private double mHeight;
133 
MaterialProgressDrawable(Context context, View parent)134     public MaterialProgressDrawable(Context context, View parent) {
135         mParent = parent;
136         mResources = context.getResources();
137 
138         mRing = new Ring(mCallback);
139         mRing.setColors(colors);
140 
141         updateSizes(DEFAULT);
142         setupAnimators();
143     }
144 
setSizeParameters(double progressCircleWidth, double progressCircleHeight, double centerRadius, double strokeWidth, float arrowWidth, float arrowHeight)145     private void setSizeParameters(double progressCircleWidth, double progressCircleHeight,
146                                    double centerRadius, double strokeWidth, float arrowWidth, float arrowHeight) {
147         final Ring ring = mRing;
148         final DisplayMetrics metrics = mResources.getDisplayMetrics();
149         final float screenDensity = metrics.density;
150 
151         mWidth = progressCircleWidth * screenDensity;
152         mHeight = progressCircleHeight * screenDensity;
153         ring.setStrokeWidth((float) strokeWidth * screenDensity);
154         ring.setCenterRadius(centerRadius * screenDensity);
155         ring.setColorIndex(0);
156         ring.setArrowDimensions(arrowWidth * screenDensity, arrowHeight * screenDensity);
157         ring.setInsets((int) mWidth, (int) mHeight);
158     }
159 
160     /**
161      * Set the overall size for the progress spinner. This updates the radius
162      * and stroke width of the ring.
163      *
164      * @param size One of {@link MaterialProgressDrawable.LARGE} or
165      *             {@link MaterialProgressDrawable.DEFAULT}
166      */
updateSizes(@rogressDrawableSize int size)167     public void updateSizes(@ProgressDrawableSize int size) {
168         if (size == LARGE) {
169             setSizeParameters(CIRCLE_DIAMETER_LARGE, CIRCLE_DIAMETER_LARGE, CENTER_RADIUS_LARGE,
170                     STROKE_WIDTH_LARGE, ARROW_WIDTH_LARGE, ARROW_HEIGHT_LARGE);
171         } else {
172             setSizeParameters(CIRCLE_DIAMETER, CIRCLE_DIAMETER, CENTER_RADIUS, STROKE_WIDTH,
173                     ARROW_WIDTH, ARROW_HEIGHT);
174         }
175     }
176 
177     /**
178      * @param show Set to true to display the arrowhead on the progress spinner.
179      */
showArrow(boolean show)180     public void showArrow(boolean show) {
181         mRing.setShowArrow(show);
182     }
183 
184     /**
185      * @param scale Set the scale of the arrowhead for the spinner.
186      */
setArrowScale(float scale)187     public void setArrowScale(float scale) {
188         mRing.setArrowScale(scale);
189     }
190 
191     /**
192      * Set the start and end trim for the progress spinner arc.
193      *
194      * @param startAngle start angle
195      * @param endAngle   end angle
196      */
setStartEndTrim(float startAngle, float endAngle)197     public void setStartEndTrim(float startAngle, float endAngle) {
198         mRing.setStartTrim(startAngle);
199         mRing.setEndTrim(endAngle);
200     }
201 
202     /**
203      * Set the amount of rotation to apply to the progress spinner.
204      *
205      * @param rotation Rotation is from [0..1]
206      */
setProgressRotation(float rotation)207     public void setProgressRotation(float rotation) {
208         mRing.setRotation(rotation);
209     }
210 
211     /**
212      * Update the background color of the circle image view.
213      */
setBackgroundColor(int color)214     public void setBackgroundColor(int color) {
215         mRing.setBackgroundColor(color);
216     }
217 
218     /**
219      * Set the colors used in the progress animation from color resources.
220      * The first color will also be the color of the bar that grows in response
221      * to a user swipe gesture.
222      *
223      * @param colors
224      */
setColorSchemeColors(int... colors)225     public void setColorSchemeColors(int... colors) {
226         mRing.setColors(colors);
227         mRing.setColorIndex(0);
228     }
229 
230     @Override
getIntrinsicHeight()231     public int getIntrinsicHeight() {
232         return (int) mHeight;
233     }
234 
235     @Override
getIntrinsicWidth()236     public int getIntrinsicWidth() {
237         return (int) mWidth;
238     }
239 
240     @Override
draw(Canvas c)241     public void draw(Canvas c) {
242         final Rect bounds = getBounds();
243         final int saveCount = c.save();
244         c.rotate(mRotation, bounds.exactCenterX(), bounds.exactCenterY());
245         mRing.draw(c, bounds);
246         c.restoreToCount(saveCount);
247     }
248 
getAlpha()249     public int getAlpha() {
250         return mRing.getAlpha();
251     }
252 
253     @Override
setAlpha(int alpha)254     public void setAlpha(int alpha) {
255         mRing.setAlpha(alpha);
256     }
257 
258     @Override
setColorFilter(ColorFilter colorFilter)259     public void setColorFilter(ColorFilter colorFilter) {
260         mRing.setColorFilter(colorFilter);
261     }
262 
263     @SuppressWarnings("unused")
getRotation()264     private float getRotation() {
265         return mRotation;
266     }
267 
268     @SuppressWarnings("unused")
setRotation(float rotation)269     void setRotation(float rotation) {
270         mRotation = rotation;
271         invalidateSelf();
272     }
273 
274     @Override
getOpacity()275     public int getOpacity() {
276         return PixelFormat.TRANSLUCENT;
277     }
278 
279     @Override
isRunning()280     public boolean isRunning() {
281         final ArrayList<Animation> animators = mAnimators;
282         final int n = animators.size();
283         for (int i = 0; i < n; i++) {
284             final Animation animator = animators.get(i);
285             if (animator.hasStarted() && !animator.hasEnded()) {
286                 return true;
287             }
288         }
289         return false;
290     }
291 
292     @Override
start()293     public void start() {
294         mAnimation.reset();
295         mRing.storeOriginals();
296         // Already showing some part of the ring
297         if (mRing.getEndTrim() != mRing.getStartTrim()) {
298             mFinishing = true;
299             mAnimation.setDuration(ANIMATION_DURATION / 2);
300             mParent.startAnimation(mAnimation);
301         } else {
302             mRing.setColorIndex(0);
303             mRing.resetOriginals();
304             mAnimation.setDuration(ANIMATION_DURATION);
305             mParent.startAnimation(mAnimation);
306         }
307     }
308 
309     @Override
stop()310     public void stop() {
311         mParent.clearAnimation();
312         setRotation(0);
313         mRing.setShowArrow(false);
314         mRing.setColorIndex(0);
315         mRing.resetOriginals();
316     }
317 
getMinProgressArc(Ring ring)318     private float getMinProgressArc(Ring ring) {
319         return (float) Math.toRadians(
320                 ring.getStrokeWidth() / (2 * Math.PI * ring.getCenterRadius()));
321     }
322 
323     // Adapted from ArgbEvaluator.java
evaluateColorChange(float fraction, int startValue, int endValue)324     private int evaluateColorChange(float fraction, int startValue, int endValue) {
325         int startInt = (Integer) startValue;
326         int startA = (startInt >> 24) & 0xff;
327         int startR = (startInt >> 16) & 0xff;
328         int startG = (startInt >> 8) & 0xff;
329         int startB = startInt & 0xff;
330 
331         int endInt = (Integer) endValue;
332         int endA = (endInt >> 24) & 0xff;
333         int endR = (endInt >> 16) & 0xff;
334         int endG = (endInt >> 8) & 0xff;
335         int endB = endInt & 0xff;
336 
337         return (int) ((startA + (int) (fraction * (endA - startA))) << 24)
338                 | (int) ((startR + (int) (fraction * (endR - startR))) << 16)
339                 | (int) ((startG + (int) (fraction * (endG - startG))) << 8)
340                 | (int) ((startB + (int) (fraction * (endB - startB))));
341     }
342 
343     /**
344      * Update the ring color if this is within the last 25% of the animation.
345      * The new ring color will be a translation from the starting ring color to
346      * the next color.
347      */
updateRingColor(float interpolatedTime, Ring ring)348     private void updateRingColor(float interpolatedTime, Ring ring) {
349         if (interpolatedTime > COLOR_START_DELAY_OFFSET) {
350             // scale the interpolatedTime so that the full
351             // transformation from 0 - 1 takes place in the
352             // remaining time
353             ring.setColor(evaluateColorChange((interpolatedTime - COLOR_START_DELAY_OFFSET)
354                             / (1.0f - COLOR_START_DELAY_OFFSET), ring.getStartingColor(),
355                     ring.getNextColor()));
356         }
357     }
358 
applyFinishTranslation(float interpolatedTime, Ring ring)359     private void applyFinishTranslation(float interpolatedTime, Ring ring) {
360         // shrink back down and complete a full rotation before
361         // starting other circles
362         // Rotation goes between [0..1].
363         updateRingColor(interpolatedTime, ring);
364         float targetRotation = (float) (Math.floor(ring.getStartingRotation() / MAX_PROGRESS_ARC)
365                 + 1f);
366         final float minProgressArc = getMinProgressArc(ring);
367         final float startTrim = ring.getStartingStartTrim()
368                 + (ring.getStartingEndTrim() - minProgressArc - ring.getStartingStartTrim())
369                 * interpolatedTime;
370         ring.setStartTrim(startTrim);
371         ring.setEndTrim(ring.getStartingEndTrim());
372         final float rotation = ring.getStartingRotation()
373                 + ((targetRotation - ring.getStartingRotation()) * interpolatedTime);
374         ring.setRotation(rotation);
375     }
376 
setupAnimators()377     private void setupAnimators() {
378         final Ring ring = mRing;
379         final Animation animation = new Animation() {
380             @Override
381             public void applyTransformation(float interpolatedTime, Transformation t) {
382                 if (mFinishing) {
383                     applyFinishTranslation(interpolatedTime, ring);
384                 } else {
385                     // The minProgressArc is calculated from 0 to create an
386                     // angle that matches the stroke width.
387                     final float minProgressArc = getMinProgressArc(ring);
388                     final float startingEndTrim = ring.getStartingEndTrim();
389                     final float startingTrim = ring.getStartingStartTrim();
390                     final float startingRotation = ring.getStartingRotation();
391 
392                     updateRingColor(interpolatedTime, ring);
393 
394                     // Moving the start trim only occurs in the first 50% of a
395                     // single ring animation
396                     if (interpolatedTime <= START_TRIM_DURATION_OFFSET) {
397                         // scale the interpolatedTime so that the full
398                         // transformation from 0 - 1 takes place in the
399                         // remaining time
400                         final float scaledTime = (interpolatedTime)
401                                 / (1.0f - START_TRIM_DURATION_OFFSET);
402                         final float startTrim = startingTrim
403                                 + ((MAX_PROGRESS_ARC - minProgressArc) * MATERIAL_INTERPOLATOR
404                                 .getInterpolation(scaledTime));
405                         ring.setStartTrim(startTrim);
406                     }
407 
408                     // Moving the end trim starts after 50% of a single ring
409                     // animation completes
410                     if (interpolatedTime > END_TRIM_START_DELAY_OFFSET) {
411                         // scale the interpolatedTime so that the full
412                         // transformation from 0 - 1 takes place in the
413                         // remaining time
414                         final float minArc = MAX_PROGRESS_ARC - minProgressArc;
415                         float scaledTime = (interpolatedTime - START_TRIM_DURATION_OFFSET)
416                                 / (1.0f - START_TRIM_DURATION_OFFSET);
417                         final float endTrim = startingEndTrim
418                                 + (minArc * MATERIAL_INTERPOLATOR.getInterpolation(scaledTime));
419                         ring.setEndTrim(endTrim);
420                     }
421 
422                     final float rotation = startingRotation + (0.25f * interpolatedTime);
423                     ring.setRotation(rotation);
424 
425                     float groupRotation = ((FULL_ROTATION / NUM_POINTS) * interpolatedTime)
426                             + (FULL_ROTATION * (mRotationCount / NUM_POINTS));
427                     setRotation(groupRotation);
428                 }
429             }
430         };
431         animation.setRepeatCount(Animation.INFINITE);
432         animation.setRepeatMode(Animation.RESTART);
433         animation.setInterpolator(LINEAR_INTERPOLATOR);
434         animation.setAnimationListener(new Animation.AnimationListener() {
435 
436             @Override
437             public void onAnimationStart(Animation animation) {
438                 mRotationCount = 0;
439             }
440 
441             @Override
442             public void onAnimationEnd(Animation animation) {
443                 // do nothing
444             }
445 
446             @Override
447             public void onAnimationRepeat(Animation animation) {
448                 ring.storeOriginals();
449                 ring.goToNextColor();
450                 ring.setStartTrim(ring.getEndTrim());
451                 if (mFinishing) {
452                     // finished closing the last ring from the swipe gesture; go
453                     // into progress mode
454                     mFinishing = false;
455                     animation.setDuration(ANIMATION_DURATION);
456                     ring.setShowArrow(false);
457                 } else {
458                     mRotationCount = (mRotationCount + 1) % (NUM_POINTS);
459                 }
460             }
461         });
462         mAnimation = animation;
463     }
464 
465     /**
466      * Progress drawable size.
467      */
468     @Retention(RetentionPolicy.CLASS)
469     @IntDef({LARGE, DEFAULT})
470     public @interface ProgressDrawableSize {
471     }
472 
473     private static class Ring {
474         private final RectF mTempBounds = new RectF();
475         private final Paint mPaint = new Paint();
476         private final Paint mArrowPaint = new Paint();
477 
478         private final Callback mCallback;
479         private final Paint mCirclePaint = new Paint(Paint.ANTI_ALIAS_FLAG);
480         private float mStartTrim = 0.0f;
481         private float mEndTrim = 0.0f;
482         private float mRotation = 0.0f;
483         private float mStrokeWidth = 5.0f;
484         private float mStrokeInset = 2.5f;
485         private int[] mColors;
486         // mColorIndex represents the offset into the available mColors that the
487         // progress circle should currently display. As the progress circle is
488         // animating, the mColorIndex moves by one to the next available color.
489         private int mColorIndex;
490         private float mStartingStartTrim;
491         private float mStartingEndTrim;
492         private float mStartingRotation;
493         private boolean mShowArrow;
494         private Path mArrow;
495         private float mArrowScale;
496         private double mRingCenterRadius;
497         private int mArrowWidth;
498         private int mArrowHeight;
499         private int mAlpha;
500         private int mBackgroundColor;
501         private int mCurrentColor;
502 
Ring(Callback callback)503         public Ring(Callback callback) {
504             mCallback = callback;
505 
506             mPaint.setStrokeCap(Paint.Cap.SQUARE);
507             mPaint.setAntiAlias(true);
508             mPaint.setStyle(Style.STROKE);
509 
510             mArrowPaint.setStyle(Paint.Style.FILL);
511             mArrowPaint.setAntiAlias(true);
512         }
513 
setBackgroundColor(int color)514         public void setBackgroundColor(int color) {
515             mBackgroundColor = color;
516         }
517 
518         /**
519          * Set the dimensions of the arrowhead.
520          *
521          * @param width  Width of the hypotenuse of the arrow head
522          * @param height Height of the arrow point
523          */
setArrowDimensions(float width, float height)524         public void setArrowDimensions(float width, float height) {
525             mArrowWidth = (int) width;
526             mArrowHeight = (int) height;
527         }
528 
529         /**
530          * Draw the progress spinner
531          */
draw(Canvas c, Rect bounds)532         public void draw(Canvas c, Rect bounds) {
533             final RectF arcBounds = mTempBounds;
534             arcBounds.set(bounds);
535             arcBounds.inset(mStrokeInset, mStrokeInset);
536 
537             final float startAngle = (mStartTrim + mRotation) * 360;
538             final float endAngle = (mEndTrim + mRotation) * 360;
539             float sweepAngle = endAngle - startAngle;
540 
541             mPaint.setColor(mCurrentColor);
542             c.drawArc(arcBounds, startAngle, sweepAngle, false, mPaint);
543 
544             drawTriangle(c, startAngle, sweepAngle, bounds);
545 
546             if (mAlpha < 255) {
547                 mCirclePaint.setColor(mBackgroundColor);
548                 mCirclePaint.setAlpha(255 - mAlpha);
549                 c.drawCircle(bounds.exactCenterX(), bounds.exactCenterY(), bounds.width() / 2,
550                         mCirclePaint);
551             }
552         }
553 
drawTriangle(Canvas c, float startAngle, float sweepAngle, Rect bounds)554         private void drawTriangle(Canvas c, float startAngle, float sweepAngle, Rect bounds) {
555             if (mShowArrow) {
556                 if (mArrow == null) {
557                     mArrow = new android.graphics.Path();
558                     mArrow.setFillType(android.graphics.Path.FillType.EVEN_ODD);
559                 } else {
560                     mArrow.reset();
561                 }
562 
563                 // Adjust the position of the triangle so that it is inset as
564                 // much as the arc, but also centered on the arc.
565                 float inset = (int) mStrokeInset / 2 * mArrowScale;
566                 float x = (float) (mRingCenterRadius * Math.cos(0) + bounds.exactCenterX());
567                 float y = (float) (mRingCenterRadius * Math.sin(0) + bounds.exactCenterY());
568 
569                 // Update the path each time. This works around an issue in SKIA
570                 // where concatenating a rotation matrix to a scale matrix
571                 // ignored a starting negative rotation. This appears to have
572                 // been fixed as of API 21.
573                 mArrow.moveTo(0, 0);
574                 mArrow.lineTo(mArrowWidth * mArrowScale, 0);
575                 mArrow.lineTo((mArrowWidth * mArrowScale / 2), (mArrowHeight
576                         * mArrowScale));
577                 mArrow.offset(x - inset, y);
578                 mArrow.close();
579                 // draw a triangle
580                 mArrowPaint.setColor(mCurrentColor);
581                 c.rotate(startAngle + sweepAngle - ARROW_OFFSET_ANGLE, bounds.exactCenterX(),
582                         bounds.exactCenterY());
583                 c.drawPath(mArrow, mArrowPaint);
584             }
585         }
586 
587         /**
588          * Set the colors the progress spinner alternates between.
589          *
590          * @param colors Array of integers describing the colors. Must be non-<code>null</code>.
591          */
setColors(@onNull int[] colors)592         public void setColors(@NonNull int[] colors) {
593             mColors = colors;
594             // if colors are reset, make sure to reset the color index as well
595             setColorIndex(0);
596         }
597 
598         /**
599          * Set the absolute color of the progress spinner. This is should only
600          * be used when animating between current and next color when the
601          * spinner is rotating.
602          *
603          * @param color int describing the color.
604          */
setColor(int color)605         public void setColor(int color) {
606             mCurrentColor = color;
607         }
608 
609         /**
610          * @param index Index into the color array of the color to display in
611          *              the progress spinner.
612          */
setColorIndex(int index)613         public void setColorIndex(int index) {
614             mColorIndex = index;
615             mCurrentColor = mColors[mColorIndex];
616         }
617 
618         /**
619          * @return int describing the next color the progress spinner should use when drawing.
620          */
getNextColor()621         public int getNextColor() {
622             return mColors[getNextColorIndex()];
623         }
624 
getNextColorIndex()625         private int getNextColorIndex() {
626             return (mColorIndex + 1) % (mColors.length);
627         }
628 
629         /**
630          * Proceed to the next available ring color. This will automatically
631          * wrap back to the beginning of colors.
632          */
goToNextColor()633         public void goToNextColor() {
634             setColorIndex(getNextColorIndex());
635         }
636 
setColorFilter(ColorFilter filter)637         public void setColorFilter(ColorFilter filter) {
638             mPaint.setColorFilter(filter);
639             invalidateSelf();
640         }
641 
642         /**
643          * @return Current alpha of the progress spinner and arrowhead.
644          */
getAlpha()645         public int getAlpha() {
646             return mAlpha;
647         }
648 
649         /**
650          * @param alpha Set the alpha of the progress spinner and associated arrowhead.
651          */
setAlpha(int alpha)652         public void setAlpha(int alpha) {
653             mAlpha = alpha;
654         }
655 
656         @SuppressWarnings("unused")
getStrokeWidth()657         public float getStrokeWidth() {
658             return mStrokeWidth;
659         }
660 
661         /**
662          * @param strokeWidth Set the stroke width of the progress spinner in pixels.
663          */
setStrokeWidth(float strokeWidth)664         public void setStrokeWidth(float strokeWidth) {
665             mStrokeWidth = strokeWidth;
666             mPaint.setStrokeWidth(strokeWidth);
667             invalidateSelf();
668         }
669 
670         @SuppressWarnings("unused")
getStartTrim()671         public float getStartTrim() {
672             return mStartTrim;
673         }
674 
675         @SuppressWarnings("unused")
setStartTrim(float startTrim)676         public void setStartTrim(float startTrim) {
677             mStartTrim = startTrim;
678             invalidateSelf();
679         }
680 
getStartingStartTrim()681         public float getStartingStartTrim() {
682             return mStartingStartTrim;
683         }
684 
getStartingEndTrim()685         public float getStartingEndTrim() {
686             return mStartingEndTrim;
687         }
688 
getStartingColor()689         public int getStartingColor() {
690             return mColors[mColorIndex];
691         }
692 
693         @SuppressWarnings("unused")
getEndTrim()694         public float getEndTrim() {
695             return mEndTrim;
696         }
697 
698         @SuppressWarnings("unused")
setEndTrim(float endTrim)699         public void setEndTrim(float endTrim) {
700             mEndTrim = endTrim;
701             invalidateSelf();
702         }
703 
704         @SuppressWarnings("unused")
getRotation()705         public float getRotation() {
706             return mRotation;
707         }
708 
709         @SuppressWarnings("unused")
setRotation(float rotation)710         public void setRotation(float rotation) {
711             mRotation = rotation;
712             invalidateSelf();
713         }
714 
setInsets(int width, int height)715         public void setInsets(int width, int height) {
716             final float minEdge = (float) Math.min(width, height);
717             float insets;
718             if (mRingCenterRadius <= 0 || minEdge < 0) {
719                 insets = (float) Math.ceil(mStrokeWidth / 2.0f);
720             } else {
721                 insets = (float) (minEdge / 2.0f - mRingCenterRadius);
722             }
723             mStrokeInset = insets;
724         }
725 
726         @SuppressWarnings("unused")
getInsets()727         public float getInsets() {
728             return mStrokeInset;
729         }
730 
getCenterRadius()731         public double getCenterRadius() {
732             return mRingCenterRadius;
733         }
734 
735         /**
736          * @param centerRadius Inner radius in px of the circle the progress
737          *                     spinner arc traces.
738          */
setCenterRadius(double centerRadius)739         public void setCenterRadius(double centerRadius) {
740             mRingCenterRadius = centerRadius;
741         }
742 
743         /**
744          * @param show Set to true to show the arrow head on the progress spinner.
745          */
setShowArrow(boolean show)746         public void setShowArrow(boolean show) {
747             if (mShowArrow != show) {
748                 mShowArrow = show;
749                 invalidateSelf();
750             }
751         }
752 
753         /**
754          * @param scale Set the scale of the arrowhead for the spinner.
755          */
setArrowScale(float scale)756         public void setArrowScale(float scale) {
757             if (scale != mArrowScale) {
758                 mArrowScale = scale;
759                 invalidateSelf();
760             }
761         }
762 
763         /**
764          * @return The amount the progress spinner is currently rotated, between [0..1].
765          */
getStartingRotation()766         public float getStartingRotation() {
767             return mStartingRotation;
768         }
769 
770         /**
771          * If the start / end trim are offset to begin with, store them so that
772          * animation starts from that offset.
773          */
storeOriginals()774         public void storeOriginals() {
775             mStartingStartTrim = mStartTrim;
776             mStartingEndTrim = mEndTrim;
777             mStartingRotation = mRotation;
778         }
779 
780         /**
781          * Reset the progress spinner to default rotation, start and end angles.
782          */
resetOriginals()783         public void resetOriginals() {
784             mStartingStartTrim = 0;
785             mStartingEndTrim = 0;
786             mStartingRotation = 0;
787             setStartTrim(0);
788             setEndTrim(0);
789             setRotation(0);
790         }
791 
invalidateSelf()792         private void invalidateSelf() {
793             mCallback.invalidateDrawable(null);
794         }
795     }
796 }
797