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.graphics.drawable;
18 
19 import android.annotation.ColorInt;
20 import android.annotation.FloatRange;
21 import android.annotation.IntDef;
22 import android.annotation.NonNull;
23 import android.annotation.Nullable;
24 import android.annotation.Px;
25 import android.compat.annotation.UnsupportedAppUsage;
26 import android.content.pm.ActivityInfo.Config;
27 import android.content.res.ColorStateList;
28 import android.content.res.Resources;
29 import android.content.res.Resources.Theme;
30 import android.content.res.TypedArray;
31 import android.graphics.BlendMode;
32 import android.graphics.BlendModeColorFilter;
33 import android.graphics.Canvas;
34 import android.graphics.Color;
35 import android.graphics.ColorFilter;
36 import android.graphics.DashPathEffect;
37 import android.graphics.Insets;
38 import android.graphics.LinearGradient;
39 import android.graphics.Outline;
40 import android.graphics.Paint;
41 import android.graphics.Path;
42 import android.graphics.PixelFormat;
43 import android.graphics.RadialGradient;
44 import android.graphics.Rect;
45 import android.graphics.RectF;
46 import android.graphics.Shader;
47 import android.graphics.SweepGradient;
48 import android.graphics.Xfermode;
49 import android.os.Build;
50 import android.util.AttributeSet;
51 import android.util.DisplayMetrics;
52 import android.util.Log;
53 import android.util.TypedValue;
54 
55 import com.android.internal.R;
56 
57 import org.xmlpull.v1.XmlPullParser;
58 import org.xmlpull.v1.XmlPullParserException;
59 
60 import java.io.IOException;
61 import java.lang.annotation.Retention;
62 import java.lang.annotation.RetentionPolicy;
63 
64 /**
65  * A Drawable with a color gradient for buttons, backgrounds, etc.
66  *
67  * <p>It can be defined in an XML file with the <code>&lt;shape></code> element. For more
68  * information, see the guide to <a
69  * href="{@docRoot}guide/topics/resources/drawable-resource.html">Drawable Resources</a>.</p>
70  *
71  * @attr ref android.R.styleable#GradientDrawable_visible
72  * @attr ref android.R.styleable#GradientDrawable_shape
73  * @attr ref android.R.styleable#GradientDrawable_innerRadiusRatio
74  * @attr ref android.R.styleable#GradientDrawable_innerRadius
75  * @attr ref android.R.styleable#GradientDrawable_thicknessRatio
76  * @attr ref android.R.styleable#GradientDrawable_thickness
77  * @attr ref android.R.styleable#GradientDrawable_useLevel
78  * @attr ref android.R.styleable#GradientDrawableSize_width
79  * @attr ref android.R.styleable#GradientDrawableSize_height
80  * @attr ref android.R.styleable#GradientDrawableGradient_startColor
81  * @attr ref android.R.styleable#GradientDrawableGradient_centerColor
82  * @attr ref android.R.styleable#GradientDrawableGradient_endColor
83  * @attr ref android.R.styleable#GradientDrawableGradient_useLevel
84  * @attr ref android.R.styleable#GradientDrawableGradient_angle
85  * @attr ref android.R.styleable#GradientDrawableGradient_type
86  * @attr ref android.R.styleable#GradientDrawableGradient_centerX
87  * @attr ref android.R.styleable#GradientDrawableGradient_centerY
88  * @attr ref android.R.styleable#GradientDrawableGradient_gradientRadius
89  * @attr ref android.R.styleable#GradientDrawableSolid_color
90  * @attr ref android.R.styleable#GradientDrawableStroke_width
91  * @attr ref android.R.styleable#GradientDrawableStroke_color
92  * @attr ref android.R.styleable#GradientDrawableStroke_dashWidth
93  * @attr ref android.R.styleable#GradientDrawableStroke_dashGap
94  * @attr ref android.R.styleable#GradientDrawablePadding_left
95  * @attr ref android.R.styleable#GradientDrawablePadding_top
96  * @attr ref android.R.styleable#GradientDrawablePadding_right
97  * @attr ref android.R.styleable#GradientDrawablePadding_bottom
98  */
99 public class GradientDrawable extends Drawable {
100 
101     /**
102      * Flag to determine if we should wrap negative gradient angle measurements
103      * for API levels that support it
104      * @hide
105      */
106     public static boolean sWrapNegativeAngleMeasurements = true;
107 
108     /**
109      * Shape is a rectangle, possibly with rounded corners
110      */
111     public static final int RECTANGLE = 0;
112 
113     /**
114      * Shape is an ellipse
115      */
116     public static final int OVAL = 1;
117 
118     /**
119      * Shape is a line
120      */
121     public static final int LINE = 2;
122 
123     /**
124      * Shape is a ring.
125      */
126     public static final int RING = 3;
127 
128     /** @hide */
129     @IntDef({RECTANGLE, OVAL, LINE, RING})
130     @Retention(RetentionPolicy.SOURCE)
131     public @interface Shape {}
132 
133     /**
134      * Gradient is linear (default.)
135      */
136     public static final int LINEAR_GRADIENT = 0;
137 
138     /**
139      * Gradient is circular.
140      */
141     public static final int RADIAL_GRADIENT = 1;
142 
143     /**
144      * Gradient is a sweep.
145      */
146     public static final int SWEEP_GRADIENT  = 2;
147 
148     /** @hide */
149     @IntDef({LINEAR_GRADIENT, RADIAL_GRADIENT, SWEEP_GRADIENT})
150     @Retention(RetentionPolicy.SOURCE)
151     public @interface GradientType {}
152 
153     /** Radius is in pixels. */
154     private static final int RADIUS_TYPE_PIXELS = 0;
155 
156     /** Radius is a fraction of the base size. */
157     private static final int RADIUS_TYPE_FRACTION = 1;
158 
159     /** Radius is a fraction of the bounds size. */
160     private static final int RADIUS_TYPE_FRACTION_PARENT = 2;
161 
162     /** Default orientation for GradientDrawable **/
163     private static final Orientation DEFAULT_ORIENTATION = Orientation.TOP_BOTTOM;
164 
165     /** @hide */
166     @IntDef({RADIUS_TYPE_PIXELS, RADIUS_TYPE_FRACTION, RADIUS_TYPE_FRACTION_PARENT})
167     @Retention(RetentionPolicy.SOURCE)
168     public @interface RadiusType {}
169 
170     private static final float DEFAULT_INNER_RADIUS_RATIO = 3.0f;
171     private static final float DEFAULT_THICKNESS_RATIO = 9.0f;
172 
173     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 124050917)
174     private GradientState mGradientState;
175 
176     @UnsupportedAppUsage
177     private final Paint mFillPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
178     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 124051827)
179     private Rect mPadding;
180     @UnsupportedAppUsage
181     private Paint mStrokePaint;   // optional, set by the caller
182     private ColorFilter mColorFilter;   // optional, set by the caller
183     private BlendModeColorFilter mBlendModeColorFilter;
184     private int mAlpha = 0xFF;  // modified by the caller
185 
186     private final Path mPath = new Path();
187     private final RectF mRect = new RectF();
188 
189     private Paint mLayerPaint;    // internal, used if we use saveLayer()
190     private boolean mGradientIsDirty;
191     private boolean mMutated;
192     private Path mRingPath;
193     private boolean mPathIsDirty = true;
194 
195     /** Current gradient radius, valid when {@link #mGradientIsDirty} is false. */
196     private float mGradientRadius;
197 
198     /**
199      * Controls how the gradient is oriented relative to the drawable's bounds
200      */
201     public enum Orientation {
202         /** draw the gradient from the top to the bottom */
203         TOP_BOTTOM,
204         /** draw the gradient from the top-right to the bottom-left */
205         TR_BL,
206         /** draw the gradient from the right to the left */
207         RIGHT_LEFT,
208         /** draw the gradient from the bottom-right to the top-left */
209         BR_TL,
210         /** draw the gradient from the bottom to the top */
211         BOTTOM_TOP,
212         /** draw the gradient from the bottom-left to the top-right */
213         BL_TR,
214         /** draw the gradient from the left to the right */
215         LEFT_RIGHT,
216         /** draw the gradient from the top-left to the bottom-right */
217         TL_BR,
218     }
219 
GradientDrawable()220     public GradientDrawable() {
221         this(new GradientState(DEFAULT_ORIENTATION, null), null);
222     }
223 
224     /**
225      * Create a new gradient drawable given an orientation and an array
226      * of colors for the gradient.
227      */
GradientDrawable(Orientation orientation, @ColorInt int[] colors)228     public GradientDrawable(Orientation orientation, @ColorInt int[] colors) {
229         this(new GradientState(orientation, colors), null);
230     }
231 
232     @Override
getPadding(Rect padding)233     public boolean getPadding(Rect padding) {
234         if (mPadding != null) {
235             padding.set(mPadding);
236             return true;
237         } else {
238             return super.getPadding(padding);
239         }
240     }
241 
242     /**
243      * Specifies radii for each of the 4 corners. For each corner, the array
244      * contains 2 values, <code>[X_radius, Y_radius]</code>. The corners are
245      * ordered top-left, top-right, bottom-right, bottom-left. This property
246      * is honored only when the shape is of type {@link #RECTANGLE}.
247      * <p>
248      * <strong>Note</strong>: changing this property will affect all instances
249      * of a drawable loaded from a resource. It is recommended to invoke
250      * {@link #mutate()} before changing this property.
251      *
252      * @param radii an array of length >= 8 containing 4 pairs of X and Y
253      *              radius for each corner, specified in pixels
254      *
255      * @see #mutate()
256      * @see #setShape(int)
257      * @see #setCornerRadius(float)
258      */
setCornerRadii(@ullable float[] radii)259     public void setCornerRadii(@Nullable float[] radii) {
260         mGradientState.setCornerRadii(radii);
261         mPathIsDirty = true;
262         invalidateSelf();
263     }
264 
265     /**
266      * Returns the radii for each of the 4 corners. For each corner, the array
267      * contains 2 values, <code>[X_radius, Y_radius]</code>. The corners are
268      * ordered top-left, top-right, bottom-right, bottom-left.
269      * <p>
270      * If the radius was previously set with {@link #setCornerRadius(float)},
271      * or if the corners are not rounded, this method will return {@code null}.
272      *
273      * @return an array containing the radii for each of the 4 corners, or
274      *         {@code null}
275      * @see #setCornerRadii(float[])
276      */
277     @Nullable
getCornerRadii()278     public float[] getCornerRadii() {
279         float[] radii = mGradientState.mRadiusArray;
280         if (radii == null) {
281             return null;
282         }
283         return radii.clone();
284     }
285 
286     /**
287      * Specifies the radius for the corners of the gradient. If this is > 0,
288      * then the drawable is drawn in a round-rectangle, rather than a
289      * rectangle. This property is honored only when the shape is of type
290      * {@link #RECTANGLE}.
291      * <p>
292      * <strong>Note</strong>: changing this property will affect all instances
293      * of a drawable loaded from a resource. It is recommended to invoke
294      * {@link #mutate()} before changing this property.
295      *
296      * @param radius The radius in pixels of the corners of the rectangle shape
297      *
298      * @see #mutate()
299      * @see #setCornerRadii(float[])
300      * @see #setShape(int)
301      */
setCornerRadius(float radius)302     public void setCornerRadius(float radius) {
303         mGradientState.setCornerRadius(radius);
304         mPathIsDirty = true;
305         invalidateSelf();
306     }
307 
308     /**
309      * Returns the radius for the corners of the gradient, that was previously set with
310      * {@link #setCornerRadius(float)}.
311      * <p>
312      * If the radius was previously cleared via passing {@code null}
313      * to {@link #setCornerRadii(float[])}, this method will return 0.
314      *
315      * @return the radius in pixels of the corners of the rectangle shape, or 0
316      * @see #setCornerRadius
317      */
getCornerRadius()318     public float getCornerRadius() {
319         return mGradientState.mRadius;
320     }
321 
322     /**
323      * <p>Set the stroke width and color for the drawable. If width is zero,
324      * then no stroke is drawn.</p>
325      * <p><strong>Note</strong>: changing this property will affect all instances
326      * of a drawable loaded from a resource. It is recommended to invoke
327      * {@link #mutate()} before changing this property.</p>
328      *
329      * @param width The width in pixels of the stroke
330      * @param color The color of the stroke
331      *
332      * @see #mutate()
333      * @see #setStroke(int, int, float, float)
334      */
setStroke(int width, @ColorInt int color)335     public void setStroke(int width, @ColorInt int color) {
336         setStroke(width, color, 0, 0);
337     }
338 
339     /**
340      * <p>Set the stroke width and color state list for the drawable. If width
341      * is zero, then no stroke is drawn.</p>
342      * <p><strong>Note</strong>: changing this property will affect all instances
343      * of a drawable loaded from a resource. It is recommended to invoke
344      * {@link #mutate()} before changing this property.</p>
345      *
346      * @param width The width in pixels of the stroke
347      * @param colorStateList The color state list of the stroke
348      *
349      * @see #mutate()
350      * @see #setStroke(int, ColorStateList, float, float)
351      */
setStroke(int width, ColorStateList colorStateList)352     public void setStroke(int width, ColorStateList colorStateList) {
353         setStroke(width, colorStateList, 0, 0);
354     }
355 
356     /**
357      * <p>Set the stroke width and color for the drawable. If width is zero,
358      * then no stroke is drawn. This method can also be used to dash the stroke.</p>
359      * <p><strong>Note</strong>: changing this property will affect all instances
360      * of a drawable loaded from a resource. It is recommended to invoke
361      * {@link #mutate()} before changing this property.</p>
362      *
363      * @param width The width in pixels of the stroke
364      * @param color The color of the stroke
365      * @param dashWidth The length in pixels of the dashes, set to 0 to disable dashes
366      * @param dashGap The gap in pixels between dashes
367      *
368      * @see #mutate()
369      * @see #setStroke(int, int)
370      */
setStroke(int width, @ColorInt int color, float dashWidth, float dashGap)371     public void setStroke(int width, @ColorInt int color, float dashWidth, float dashGap) {
372         mGradientState.setStroke(width, ColorStateList.valueOf(color), dashWidth, dashGap);
373         setStrokeInternal(width, color, dashWidth, dashGap);
374     }
375 
376     /**
377      * <p>Set the stroke width and color state list for the drawable. If width
378      * is zero, then no stroke is drawn. This method can also be used to dash
379      * the stroke.</p>
380      * <p><strong>Note</strong>: changing this property will affect all instances
381      * of a drawable loaded from a resource. It is recommended to invoke
382      * {@link #mutate()} before changing this property.</p>
383      *
384      * @param width The width in pixels of the stroke
385      * @param colorStateList The color state list of the stroke
386      * @param dashWidth The length in pixels of the dashes, set to 0 to disable dashes
387      * @param dashGap The gap in pixels between dashes
388      *
389      * @see #mutate()
390      * @see #setStroke(int, ColorStateList)
391      */
setStroke( int width, ColorStateList colorStateList, float dashWidth, float dashGap)392     public void setStroke(
393             int width, ColorStateList colorStateList, float dashWidth, float dashGap) {
394         mGradientState.setStroke(width, colorStateList, dashWidth, dashGap);
395         final int color;
396         if (colorStateList == null) {
397             color = Color.TRANSPARENT;
398         } else {
399             final int[] stateSet = getState();
400             color = colorStateList.getColorForState(stateSet, 0);
401         }
402         setStrokeInternal(width, color, dashWidth, dashGap);
403     }
404 
setStrokeInternal(int width, int color, float dashWidth, float dashGap)405     private void setStrokeInternal(int width, int color, float dashWidth, float dashGap) {
406         if (mStrokePaint == null)  {
407             mStrokePaint = new Paint(Paint.ANTI_ALIAS_FLAG);
408             mStrokePaint.setStyle(Paint.Style.STROKE);
409         }
410         mStrokePaint.setStrokeWidth(width);
411         mStrokePaint.setColor(color);
412 
413         DashPathEffect e = null;
414         if (dashWidth > 0) {
415             e = new DashPathEffect(new float[] { dashWidth, dashGap }, 0);
416         }
417         mStrokePaint.setPathEffect(e);
418         mGradientIsDirty = true;
419         invalidateSelf();
420     }
421 
422 
423     /**
424      * <p>Sets the size of the shape drawn by this drawable.</p>
425      * <p><strong>Note</strong>: changing this property will affect all instances
426      * of a drawable loaded from a resource. It is recommended to invoke
427      * {@link #mutate()} before changing this property.</p>
428      *
429      * @param width The width of the shape used by this drawable
430      * @param height The height of the shape used by this drawable
431      *
432      * @see #mutate()
433      * @see #setGradientType(int)
434      */
setSize(int width, int height)435     public void setSize(int width, int height) {
436         mGradientState.setSize(width, height);
437         mPathIsDirty = true;
438         invalidateSelf();
439     }
440 
441     /**
442      * <p>Sets the type of shape used to draw the gradient.</p>
443      * <p><strong>Note</strong>: changing this property will affect all instances
444      * of a drawable loaded from a resource. It is recommended to invoke
445      * {@link #mutate()} before changing this property.</p>
446      *
447      * @param shape The desired shape for this drawable: {@link #LINE},
448      *              {@link #OVAL}, {@link #RECTANGLE} or {@link #RING}
449      *
450      * @see #mutate()
451      */
setShape(@hape int shape)452     public void setShape(@Shape int shape) {
453         mRingPath = null;
454         mPathIsDirty = true;
455         mGradientState.setShape(shape);
456         invalidateSelf();
457     }
458 
459     /**
460      * Returns the type of shape used by this drawable, one of {@link #LINE},
461      * {@link #OVAL}, {@link #RECTANGLE} or {@link #RING}.
462      *
463      * @return the type of shape used by this drawable
464      * @see #setShape(int)
465      */
466     @Shape
getShape()467     public int getShape() {
468         return mGradientState.mShape;
469     }
470 
471     /**
472      * Sets the type of gradient used by this drawable.
473      * <p>
474      * <strong>Note</strong>: changing this property will affect all instances
475      * of a drawable loaded from a resource. It is recommended to invoke
476      * {@link #mutate()} before changing this property.
477      *
478      * @param gradient The type of the gradient: {@link #LINEAR_GRADIENT},
479      *                 {@link #RADIAL_GRADIENT} or {@link #SWEEP_GRADIENT}
480      *
481      * @see #mutate()
482      * @see #getGradientType()
483      */
setGradientType(@radientType int gradient)484     public void setGradientType(@GradientType int gradient) {
485         mGradientState.setGradientType(gradient);
486         mGradientIsDirty = true;
487         invalidateSelf();
488     }
489 
490     /**
491      * Returns the type of gradient used by this drawable, one of
492      * {@link #LINEAR_GRADIENT}, {@link #RADIAL_GRADIENT}, or
493      * {@link #SWEEP_GRADIENT}.
494      *
495      * @return the type of gradient used by this drawable
496      * @see #setGradientType(int)
497      */
498     @GradientType
getGradientType()499     public int getGradientType() {
500         return mGradientState.mGradient;
501     }
502 
503     /**
504      * Sets the position of the center of the gradient as a fraction of the
505      * width and height.
506      * <p>
507      * The default value is (0.5, 0.5).
508      * <p>
509      * <strong>Note</strong>: changing this property will affect all instances
510      * of a drawable loaded from a resource. It is recommended to invoke
511      * {@link #mutate()} before changing this property.
512      *
513      * @param x the X-position of the center of the gradient
514      * @param y the Y-position of the center of the gradient
515      *
516      * @see #mutate()
517      * @see #setGradientType(int)
518      * @see #getGradientCenterX()
519      * @see #getGradientCenterY()
520      */
setGradientCenter(float x, float y)521     public void setGradientCenter(float x, float y) {
522         mGradientState.setGradientCenter(x, y);
523         mGradientIsDirty = true;
524         invalidateSelf();
525     }
526 
527     /**
528      * Returns the X-position of the center of the gradient as a fraction of
529      * the width.
530      *
531      * @return the X-position of the center of the gradient
532      * @see #setGradientCenter(float, float)
533      */
getGradientCenterX()534     public float getGradientCenterX() {
535         return mGradientState.mCenterX;
536     }
537 
538     /**
539      * Returns the Y-position of the center of this gradient as a fraction of
540      * the height.
541      *
542      * @return the Y-position of the center of the gradient
543      * @see #setGradientCenter(float, float)
544      */
getGradientCenterY()545     public float getGradientCenterY() {
546         return mGradientState.mCenterY;
547     }
548 
549     /**
550      * Sets the radius of the gradient. The radius is honored only when the
551      * gradient type is set to {@link #RADIAL_GRADIENT}.
552      * <p>
553      * <strong>Note</strong>: changing this property will affect all instances
554      * of a drawable loaded from a resource. It is recommended to invoke
555      * {@link #mutate()} before changing this property.
556      *
557      * @param gradientRadius the radius of the gradient in pixels
558      *
559      * @see #mutate()
560      * @see #setGradientType(int)
561      * @see #getGradientRadius()
562      */
setGradientRadius(float gradientRadius)563     public void setGradientRadius(float gradientRadius) {
564         mGradientState.setGradientRadius(gradientRadius, TypedValue.COMPLEX_UNIT_PX);
565         mGradientIsDirty = true;
566         invalidateSelf();
567     }
568 
569     /**
570      * Returns the radius of the gradient in pixels. The radius is valid only
571      * when the gradient type is set to {@link #RADIAL_GRADIENT}.
572      *
573      * @return the radius of the gradient in pixels
574      * @see #setGradientRadius(float)
575      */
getGradientRadius()576     public float getGradientRadius() {
577         if (mGradientState.mGradient != RADIAL_GRADIENT) {
578             return 0;
579         }
580 
581         ensureValidRect();
582         return mGradientRadius;
583     }
584 
585     /**
586      * Sets whether this drawable's {@code level} property will be used to
587      * scale the gradient. If a gradient is not used, this property has no
588      * effect.
589      * <p>
590      * Scaling behavior varies based on gradient type:
591      * <ul>
592      *     <li>{@link #LINEAR_GRADIENT} adjusts the ending position along the
593      *         gradient's axis of orientation (see {@link #getOrientation()})
594      *     <li>{@link #RADIAL_GRADIENT} adjusts the outer radius
595      *     <li>{@link #SWEEP_GRADIENT} adjusts the ending angle
596      * <ul>
597      * <p>
598      * The default value for this property is {@code false}.
599      * <p>
600      * <strong>Note</strong>: This property corresponds to the
601      * {@code android:useLevel} attribute on the inner {@code <gradient>}
602      * tag, NOT the {@code android:useLevel} attribute on the outer
603      * {@code <shape>} tag. For example,
604      * <pre>{@code
605      * <shape ...>
606      *     <gradient
607      *         ...
608      *         android:useLevel="true" />
609      * </shape>
610      * }</pre><p>
611      * <strong>Note</strong>: Changing this property will affect all instances
612      * of a drawable loaded from a resource. It is recommended to invoke
613      * {@link #mutate()} before changing this property.
614      *
615      * @param useLevel {@code true} if the gradient should be scaled based on
616      *                 level, {@code false} otherwise
617      *
618      * @see #mutate()
619      * @see #setLevel(int)
620      * @see #getLevel()
621      * @see #getUseLevel()
622      * @attr ref android.R.styleable#GradientDrawableGradient_useLevel
623      */
setUseLevel(boolean useLevel)624     public void setUseLevel(boolean useLevel) {
625         mGradientState.mUseLevel = useLevel;
626         mGradientIsDirty = true;
627         invalidateSelf();
628     }
629 
630     /**
631      * Returns whether this drawable's {@code level} property will be used to
632      * scale the gradient.
633      *
634      * @return {@code true} if the gradient should be scaled based on level,
635      *         {@code false} otherwise
636      * @see #setUseLevel(boolean)
637      * @attr ref android.R.styleable#GradientDrawableGradient_useLevel
638      */
getUseLevel()639     public boolean getUseLevel() {
640         return mGradientState.mUseLevel;
641     }
642 
modulateAlpha(int alpha)643     private int modulateAlpha(int alpha) {
644         int scale = mAlpha + (mAlpha >> 7);
645         return alpha * scale >> 8;
646     }
647 
648     /**
649      * Returns the orientation of the gradient defined in this drawable.
650      *
651      * @return the orientation of the gradient defined in this drawable
652      * @see #setOrientation(Orientation)
653      */
getOrientation()654     public Orientation getOrientation() {
655         return mGradientState.mOrientation;
656     }
657 
658     /**
659      * Sets the orientation of the gradient defined in this drawable.
660      * <p>
661      * <strong>Note</strong>: changing orientation will affect all instances
662      * of a drawable loaded from a resource. It is recommended to invoke
663      * {@link #mutate()} before changing the orientation.
664      *
665      * @param orientation the desired orientation (angle) of the gradient
666      *
667      * @see #mutate()
668      * @see #getOrientation()
669      */
setOrientation(Orientation orientation)670     public void setOrientation(Orientation orientation) {
671         mGradientState.mOrientation = orientation;
672         mGradientIsDirty = true;
673         invalidateSelf();
674     }
675 
676     /**
677      * Sets the colors used to draw the gradient.
678      * <p>
679      * Each color is specified as an ARGB integer and the array must contain at
680      * least 2 colors.
681      * <p>
682      * <strong>Note</strong>: changing colors will affect all instances of a
683      * drawable loaded from a resource. It is recommended to invoke
684      * {@link #mutate()} before changing the colors.
685      *
686      * @param colors an array containing 2 or more ARGB colors
687      * @see #mutate()
688      * @see #setColor(int)
689      */
setColors(@ullable @olorInt int[] colors)690     public void setColors(@Nullable @ColorInt int[] colors) {
691         setColors(colors, null);
692     }
693 
694     /**
695      * Sets the colors and offsets used to draw the gradient.
696      * <p>
697      * Each color is specified as an ARGB integer and the array must contain at
698      * least 2 colors.
699      * <p>
700      * <strong>Note</strong>: changing colors will affect all instances of a
701      * drawable loaded from a resource. It is recommended to invoke
702      * {@link #mutate()} before changing the colors.
703      *
704      * @param colors an array containing 2 or more ARGB colors
705      * @param offsets optional array of floating point parameters representing the positions
706      *                of the colors. Null evenly disperses the colors
707      * @see #mutate()
708      * @see #setColors(int[])
709      */
setColors(@ullable @olorInt int[] colors, @Nullable float[] offsets)710     public void setColors(@Nullable @ColorInt int[] colors, @Nullable float[] offsets) {
711         mGradientState.setGradientColors(colors);
712         mGradientState.mPositions = offsets;
713         mGradientIsDirty = true;
714         invalidateSelf();
715     }
716 
717     /**
718      * Returns the colors used to draw the gradient, or {@code null} if the
719      * gradient is drawn using a single color or no colors.
720      *
721      * @return the colors used to draw the gradient, or {@code null}
722      * @see #setColors(int[] colors)
723      */
724     @Nullable
getColors()725     public int[] getColors() {
726         if (mGradientState.mGradientColors == null) {
727             return null;
728         } else {
729             int[] colors = new int[mGradientState.mGradientColors.length];
730             for (int i = 0; i < mGradientState.mGradientColors.length; i++) {
731                 if (mGradientState.mGradientColors[i] != null) {
732                     colors[i] = mGradientState.mGradientColors[i].getDefaultColor();
733                 }
734             }
735             return colors;
736         }
737     }
738 
739     @Override
draw(Canvas canvas)740     public void draw(Canvas canvas) {
741         if (!ensureValidRect()) {
742             // nothing to draw
743             return;
744         }
745 
746         // remember the alpha values, in case we temporarily overwrite them
747         // when we modulate them with mAlpha
748         final int prevFillAlpha = mFillPaint.getAlpha();
749         final int prevStrokeAlpha = mStrokePaint != null ? mStrokePaint.getAlpha() : 0;
750         // compute the modulate alpha values
751         final int currFillAlpha = modulateAlpha(prevFillAlpha);
752         final int currStrokeAlpha = modulateAlpha(prevStrokeAlpha);
753 
754         final boolean haveStroke = currStrokeAlpha > 0 && mStrokePaint != null &&
755                 mStrokePaint.getStrokeWidth() > 0;
756         final boolean haveFill = currFillAlpha > 0;
757         final GradientState st = mGradientState;
758         final ColorFilter colorFilter = mColorFilter != null ? mColorFilter : mBlendModeColorFilter;
759 
760         /*  we need a layer iff we're drawing both a fill and stroke, and the
761             stroke is non-opaque, and our shapetype actually supports
762             fill+stroke. Otherwise we can just draw the stroke (if any) on top
763             of the fill (if any) without worrying about blending artifacts.
764          */
765         final boolean useLayer = haveStroke && haveFill && st.mShape != LINE &&
766                  currStrokeAlpha < 255 && (mAlpha < 255 || colorFilter != null);
767 
768         /*  Drawing with a layer is slower than direct drawing, but it
769             allows us to apply paint effects like alpha and colorfilter to
770             the result of multiple separate draws. In our case, if the user
771             asks for a non-opaque alpha value (via setAlpha), and we're
772             stroking, then we need to apply the alpha AFTER we've drawn
773             both the fill and the stroke.
774         */
775         if (useLayer) {
776             if (mLayerPaint == null) {
777                 mLayerPaint = new Paint();
778             }
779             mLayerPaint.setDither(st.mDither);
780             mLayerPaint.setAlpha(mAlpha);
781             mLayerPaint.setColorFilter(colorFilter);
782 
783             float rad = mStrokePaint.getStrokeWidth();
784             canvas.saveLayer(mRect.left - rad, mRect.top - rad,
785                              mRect.right + rad, mRect.bottom + rad,
786                              mLayerPaint);
787 
788             // don't perform the filter in our individual paints
789             // since the layer will do it for us
790             mFillPaint.setColorFilter(null);
791             mStrokePaint.setColorFilter(null);
792         } else {
793             /*  if we're not using a layer, apply the dither/filter to our
794                 individual paints
795             */
796             mFillPaint.setAlpha(currFillAlpha);
797             mFillPaint.setDither(st.mDither);
798             mFillPaint.setColorFilter(colorFilter);
799             if (colorFilter != null && st.mSolidColors == null) {
800                 mFillPaint.setColor(mAlpha << 24);
801             }
802             if (haveStroke) {
803                 mStrokePaint.setAlpha(currStrokeAlpha);
804                 mStrokePaint.setDither(st.mDither);
805                 mStrokePaint.setColorFilter(colorFilter);
806             }
807         }
808 
809         switch (st.mShape) {
810             case RECTANGLE:
811                 if (st.mRadiusArray != null) {
812                     buildPathIfDirty();
813                     canvas.drawPath(mPath, mFillPaint);
814                     if (haveStroke) {
815                         canvas.drawPath(mPath, mStrokePaint);
816                     }
817                 } else if (st.mRadius > 0.0f) {
818                     // since the caller is only giving us 1 value, we will force
819                     // it to be square if the rect is too small in one dimension
820                     // to show it. If we did nothing, Skia would clamp the rad
821                     // independently along each axis, giving us a thin ellipse
822                     // if the rect were very wide but not very tall
823                     float rad = Math.min(st.mRadius,
824                             Math.min(mRect.width(), mRect.height()) * 0.5f);
825                     canvas.drawRoundRect(mRect, rad, rad, mFillPaint);
826                     if (haveStroke) {
827                         canvas.drawRoundRect(mRect, rad, rad, mStrokePaint);
828                     }
829                 } else {
830                     if (mFillPaint.getColor() != 0 || colorFilter != null ||
831                             mFillPaint.getShader() != null) {
832                         canvas.drawRect(mRect, mFillPaint);
833                     }
834                     if (haveStroke) {
835                         canvas.drawRect(mRect, mStrokePaint);
836                     }
837                 }
838                 break;
839             case OVAL:
840                 canvas.drawOval(mRect, mFillPaint);
841                 if (haveStroke) {
842                     canvas.drawOval(mRect, mStrokePaint);
843                 }
844                 break;
845             case LINE: {
846                 RectF r = mRect;
847                 float y = r.centerY();
848                 if (haveStroke) {
849                     canvas.drawLine(r.left, y, r.right, y, mStrokePaint);
850                 }
851                 break;
852             }
853             case RING:
854                 Path path = buildRing(st);
855                 canvas.drawPath(path, mFillPaint);
856                 if (haveStroke) {
857                     canvas.drawPath(path, mStrokePaint);
858                 }
859                 break;
860         }
861 
862         if (useLayer) {
863             canvas.restore();
864         } else {
865             mFillPaint.setAlpha(prevFillAlpha);
866             if (haveStroke) {
867                 mStrokePaint.setAlpha(prevStrokeAlpha);
868             }
869         }
870     }
871 
872     /**
873      * @param mode to draw this drawable with
874      * @hide
875      */
876     @Override
877     public void setXfermode(@Nullable Xfermode mode) {
878         super.setXfermode(mode);
879         mFillPaint.setXfermode(mode);
880     }
881 
882     /**
883      * @param aa to draw this drawable with
884      * @hide
885      */
886     public void setAntiAlias(boolean aa) {
887         mFillPaint.setAntiAlias(aa);
888     }
889 
890     private void buildPathIfDirty() {
891         final GradientState st = mGradientState;
892         if (mPathIsDirty) {
893             ensureValidRect();
894             mPath.reset();
895             mPath.addRoundRect(mRect, st.mRadiusArray, Path.Direction.CW);
896             mPathIsDirty = false;
897         }
898     }
899 
900     /**
901      * Inner radius of the ring expressed as a ratio of the ring's width.
902      *
903      * @see #getInnerRadiusRatio()
904      * @attr ref android.R.styleable#GradientDrawable_innerRadiusRatio
905      */
906     public void setInnerRadiusRatio(
907             @FloatRange(from = 0.0f, fromInclusive = false) float innerRadiusRatio) {
908         if (innerRadiusRatio <= 0) {
909             throw new IllegalArgumentException("Ratio must be greater than zero");
910         }
911         mGradientState.mInnerRadiusRatio = innerRadiusRatio;
912         mPathIsDirty = true;
913         invalidateSelf();
914     }
915 
916     /**
917      * Return the inner radius of the ring expressed as a ratio of the ring's width.
918      *
919      * @see #setInnerRadiusRatio(float)
920      * @attr ref android.R.styleable#GradientDrawable_innerRadiusRatio
921      */
922     public float getInnerRadiusRatio() {
923         return mGradientState.mInnerRadiusRatio;
924     }
925 
926     /**
927      * Configure the inner radius of the ring.
928      *
929      * @see #getInnerRadius()
930      * @attr ref android.R.styleable#GradientDrawable_innerRadius
931      */
932     public void setInnerRadius(@Px int innerRadius) {
933         mGradientState.mInnerRadius = innerRadius;
934         mPathIsDirty = true;
935         invalidateSelf();
936     }
937 
938     /**
939      * Return the inner radius of the ring
940      *
941      * @see #setInnerRadius(int)
942      * @attr ref android.R.styleable#GradientDrawable_innerRadius
943      */
944     public @Px int getInnerRadius() {
945         return mGradientState.mInnerRadius;
946     }
947 
948     /**
949      * Configure the thickness of the ring expressed as a ratio of the ring's width.
950      *
951      * @see #getThicknessRatio()
952      * @attr ref android.R.styleable#GradientDrawable_thicknessRatio
953      */
954     public void setThicknessRatio(
955             @FloatRange(from = 0.0f, fromInclusive = false) float thicknessRatio) {
956         if (thicknessRatio <= 0) {
957             throw new IllegalArgumentException("Ratio must be greater than zero");
958         }
959         mGradientState.mThicknessRatio = thicknessRatio;
960         mPathIsDirty = true;
961         invalidateSelf();
962     }
963 
964     /**
965      * Return the thickness ratio of the ring expressed as a ratio of the ring's width.
966      *
967      * @see #setThicknessRatio(float)
968      * @attr ref android.R.styleable#GradientDrawable_thicknessRatio
969      */
970     public float getThicknessRatio() {
971         return mGradientState.mThicknessRatio;
972     }
973 
974     /**
975      * Configure the thickness of the ring.
976      *
977      * @attr ref android.R.styleable#GradientDrawable_thickness
978      */
979     public void setThickness(@Px int thickness) {
980         mGradientState.mThickness = thickness;
981         mPathIsDirty = true;
982         invalidateSelf();
983     }
984 
985     /**
986      * Return the thickness of the ring
987      *
988      * @see #setThickness(int)
989      * @attr ref android.R.styleable#GradientDrawable_thickness
990      */
991     public @Px int getThickness() {
992         return mGradientState.mThickness;
993     }
994 
995     /**
996      * Configure the padding of the gradient shape
997      * @param left Left padding of the gradient shape
998      * @param top Top padding of the gradient shape
999      * @param right Right padding of the gradient shape
1000      * @param bottom Bottom padding of the gradient shape
1001      *
1002      * @attr ref android.R.styleable#GradientDrawablePadding_left
1003      * @attr ref android.R.styleable#GradientDrawablePadding_top
1004      * @attr ref android.R.styleable#GradientDrawablePadding_right
1005      * @attr ref android.R.styleable#GradientDrawablePadding_bottom
1006      */
1007     public void setPadding(@Px int left, @Px int top, @Px int right, @Px int bottom) {
1008         if (mGradientState.mPadding == null) {
1009             mGradientState.mPadding = new Rect();
1010         }
1011 
1012         mGradientState.mPadding.set(left, top, right, bottom);
1013         mPadding = mGradientState.mPadding;
1014         invalidateSelf();
1015     }
1016 
1017     private Path buildRing(GradientState st) {
1018         if (mRingPath != null && (!st.mUseLevelForShape || !mPathIsDirty)) return mRingPath;
1019         mPathIsDirty = false;
1020 
1021         float sweep = st.mUseLevelForShape ? (360.0f * getLevel() / 10000.0f) : 360f;
1022 
1023         RectF bounds = new RectF(mRect);
1024 
1025         float x = bounds.width() / 2.0f;
1026         float y = bounds.height() / 2.0f;
1027 
1028         float thickness = st.mThickness != -1 ?
1029                 st.mThickness : bounds.width() / st.mThicknessRatio;
1030         // inner radius
1031         float radius = st.mInnerRadius != -1 ?
1032                 st.mInnerRadius : bounds.width() / st.mInnerRadiusRatio;
1033 
1034         RectF innerBounds = new RectF(bounds);
1035         innerBounds.inset(x - radius, y - radius);
1036 
1037         bounds = new RectF(innerBounds);
1038         bounds.inset(-thickness, -thickness);
1039 
1040         if (mRingPath == null) {
1041             mRingPath = new Path();
1042         } else {
1043             mRingPath.reset();
1044         }
1045 
1046         final Path ringPath = mRingPath;
1047         // arcTo treats the sweep angle mod 360, so check for that, since we
1048         // think 360 means draw the entire oval
1049         if (sweep < 360 && sweep > -360) {
1050             ringPath.setFillType(Path.FillType.EVEN_ODD);
1051             // inner top
1052             ringPath.moveTo(x + radius, y);
1053             // outer top
1054             ringPath.lineTo(x + radius + thickness, y);
1055             // outer arc
1056             ringPath.arcTo(bounds, 0.0f, sweep, false);
1057             // inner arc
1058             ringPath.arcTo(innerBounds, sweep, -sweep, false);
1059             ringPath.close();
1060         } else {
1061             // add the entire ovals
1062             ringPath.addOval(bounds, Path.Direction.CW);
1063             ringPath.addOval(innerBounds, Path.Direction.CCW);
1064         }
1065 
1066         return ringPath;
1067     }
1068 
1069     /**
1070      * Changes this drawable to use a single color instead of a gradient.
1071      * <p>
1072      * <strong>Note</strong>: changing color will affect all instances of a
1073      * drawable loaded from a resource. It is recommended to invoke
1074      * {@link #mutate()} before changing the color.
1075      *
1076      * @param argb The color used to fill the shape
1077      *
1078      * @see #mutate()
1079      * @see #setColors(int[])
1080      * @see #getColor
1081      */
1082     public void setColor(@ColorInt int argb) {
1083         mGradientState.setSolidColors(ColorStateList.valueOf(argb));
1084         mFillPaint.setColor(argb);
1085         invalidateSelf();
1086     }
1087 
1088     /**
1089      * Changes this drawable to use a single color state list instead of a
1090      * gradient. Calling this method with a null argument will clear the color
1091      * and is equivalent to calling {@link #setColor(int)} with the argument
1092      * {@link Color#TRANSPARENT}.
1093      * <p>
1094      * <strong>Note</strong>: changing color will affect all instances of a
1095      * drawable loaded from a resource. It is recommended to invoke
1096      * {@link #mutate()} before changing the color.</p>
1097      *
1098      * @param colorStateList The color state list used to fill the shape
1099      *
1100      * @see #mutate()
1101      * @see #getColor
1102      */
1103     public void setColor(@Nullable ColorStateList colorStateList) {
1104         if (colorStateList == null) {
1105             setColor(Color.TRANSPARENT);
1106         } else {
1107             final int[] stateSet = getState();
1108             final int color = colorStateList.getColorForState(stateSet, 0);
1109             mGradientState.setSolidColors(colorStateList);
1110             mFillPaint.setColor(color);
1111             invalidateSelf();
1112         }
1113     }
1114 
1115     /**
1116      * Returns the color state list used to fill the shape, or {@code null} if
1117      * the shape is filled with a gradient or has no fill color.
1118      *
1119      * @return the color state list used to fill this gradient, or {@code null}
1120      *
1121      * @see #setColor(int)
1122      * @see #setColor(ColorStateList)
1123      */
1124     @Nullable
1125     public ColorStateList getColor() {
1126         return mGradientState.mSolidColors;
1127     }
1128 
1129     @Override
1130     protected boolean onStateChange(int[] stateSet) {
1131         boolean invalidateSelf = false;
1132 
1133         final GradientState s = mGradientState;
1134         final ColorStateList solidColors = s.mSolidColors;
1135         if (solidColors != null) {
1136             final int newColor = solidColors.getColorForState(stateSet, 0);
1137             final int oldColor = mFillPaint.getColor();
1138             if (oldColor != newColor) {
1139                 mFillPaint.setColor(newColor);
1140                 invalidateSelf = true;
1141             }
1142         }
1143 
1144         final Paint strokePaint = mStrokePaint;
1145         if (strokePaint != null) {
1146             final ColorStateList strokeColors = s.mStrokeColors;
1147             if (strokeColors != null) {
1148                 final int newColor = strokeColors.getColorForState(stateSet, 0);
1149                 final int oldColor = strokePaint.getColor();
1150                 if (oldColor != newColor) {
1151                     strokePaint.setColor(newColor);
1152                     invalidateSelf = true;
1153                 }
1154             }
1155         }
1156 
1157         if (s.mTint != null && s.mBlendMode != null) {
1158             mBlendModeColorFilter = updateBlendModeFilter(mBlendModeColorFilter, s.mTint,
1159                     s.mBlendMode);
1160             invalidateSelf = true;
1161         }
1162 
1163         if (invalidateSelf) {
1164             invalidateSelf();
1165             return true;
1166         }
1167 
1168         return false;
1169     }
1170 
1171     @Override
1172     public boolean isStateful() {
1173         final GradientState s = mGradientState;
1174         return super.isStateful()
1175                 || (s.mSolidColors != null && s.mSolidColors.isStateful())
1176                 || (s.mStrokeColors != null && s.mStrokeColors.isStateful())
1177                 || (s.mTint != null && s.mTint.isStateful());
1178     }
1179 
1180     @Override
1181     public boolean hasFocusStateSpecified() {
1182         final GradientState s = mGradientState;
1183         return (s.mSolidColors != null && s.mSolidColors.hasFocusStateSpecified())
1184                 || (s.mStrokeColors != null && s.mStrokeColors.hasFocusStateSpecified())
1185                 || (s.mTint != null && s.mTint.hasFocusStateSpecified());
1186     }
1187 
1188     @Override
1189     public @Config int getChangingConfigurations() {
1190         return super.getChangingConfigurations() | mGradientState.getChangingConfigurations();
1191     }
1192 
1193     @Override
1194     public void setAlpha(int alpha) {
1195         if (alpha != mAlpha) {
1196             mAlpha = alpha;
1197             invalidateSelf();
1198         }
1199     }
1200 
1201     @Override
1202     public int getAlpha() {
1203         return mAlpha;
1204     }
1205 
1206     @Override
1207     public void setDither(boolean dither) {
1208         if (dither != mGradientState.mDither) {
1209             mGradientState.mDither = dither;
1210             invalidateSelf();
1211         }
1212     }
1213 
1214     @Override
1215     @Nullable
1216     public ColorFilter getColorFilter() {
1217         return mColorFilter;
1218     }
1219 
1220     @Override
1221     public void setColorFilter(@Nullable ColorFilter colorFilter) {
1222         if (colorFilter != mColorFilter) {
1223             mColorFilter = colorFilter;
1224             invalidateSelf();
1225         }
1226     }
1227 
1228     @Override
1229     public void setTintList(@Nullable ColorStateList tint) {
1230         mGradientState.mTint = tint;
1231         mBlendModeColorFilter =
1232                 updateBlendModeFilter(mBlendModeColorFilter, tint, mGradientState.mBlendMode);
1233         invalidateSelf();
1234     }
1235 
1236     @Override
1237     public void setTintBlendMode(@NonNull BlendMode blendMode) {
1238         mGradientState.mBlendMode = blendMode;
1239         mBlendModeColorFilter = updateBlendModeFilter(mBlendModeColorFilter, mGradientState.mTint,
1240                 blendMode);
1241         invalidateSelf();
1242     }
1243 
1244     @Override
1245     public int getOpacity() {
1246         return (mAlpha == 255 && mGradientState.mOpaqueOverBounds && isOpaqueForState()) ?
1247                 PixelFormat.OPAQUE : PixelFormat.TRANSLUCENT;
1248     }
1249 
1250     @Override
1251     protected void onBoundsChange(Rect r) {
1252         super.onBoundsChange(r);
1253         mRingPath = null;
1254         mPathIsDirty = true;
1255         mGradientIsDirty = true;
1256     }
1257 
1258     @Override
1259     protected boolean onLevelChange(int level) {
1260         super.onLevelChange(level);
1261         mGradientIsDirty = true;
1262         mPathIsDirty = true;
1263         invalidateSelf();
1264         return true;
1265     }
1266 
1267     /**
1268      * This checks mGradientIsDirty, and if it is true, recomputes both our drawing
1269      * rectangle (mRect) and the gradient itself, since it depends on our
1270      * rectangle too.
1271      * @return true if the resulting rectangle is not empty, false otherwise
1272      */
1273     private boolean ensureValidRect() {
1274         if (mGradientIsDirty) {
1275             mGradientIsDirty = false;
1276 
1277             Rect bounds = getBounds();
1278             float inset = 0;
1279 
1280             if (mStrokePaint != null) {
1281                 inset = mStrokePaint.getStrokeWidth() * 0.5f;
1282             }
1283 
1284             final GradientState st = mGradientState;
1285 
1286             mRect.set(bounds.left + inset, bounds.top + inset,
1287                       bounds.right - inset, bounds.bottom - inset);
1288 
1289             int[] gradientColors = null;
1290             if (st.mGradientColors != null) {
1291                 gradientColors = new int[st.mGradientColors.length];
1292                 for (int i = 0; i < gradientColors.length; i++) {
1293                     if (st.mGradientColors[i] != null) {
1294                         gradientColors[i] = st.mGradientColors[i].getDefaultColor();
1295                     }
1296                 }
1297             }
1298             if (gradientColors != null) {
1299                 final RectF r = mRect;
1300                 final float x0, x1, y0, y1;
1301 
1302                 if (st.mGradient == LINEAR_GRADIENT) {
1303                     final float level = st.mUseLevel ? getLevel() / 10000.0f : 1.0f;
1304                     switch (st.mOrientation) {
1305                     case TOP_BOTTOM:
1306                         x0 = r.left;            y0 = r.top;
1307                         x1 = x0;                y1 = level * r.bottom;
1308                         break;
1309                     case TR_BL:
1310                         x0 = r.right;           y0 = r.top;
1311                         x1 = level * r.left;    y1 = level * r.bottom;
1312                         break;
1313                     case RIGHT_LEFT:
1314                         x0 = r.right;           y0 = r.top;
1315                         x1 = level * r.left;    y1 = y0;
1316                         break;
1317                     case BR_TL:
1318                         x0 = r.right;           y0 = r.bottom;
1319                         x1 = level * r.left;    y1 = level * r.top;
1320                         break;
1321                     case BOTTOM_TOP:
1322                         x0 = r.left;            y0 = r.bottom;
1323                         x1 = x0;                y1 = level * r.top;
1324                         break;
1325                     case BL_TR:
1326                         x0 = r.left;            y0 = r.bottom;
1327                         x1 = level * r.right;   y1 = level * r.top;
1328                         break;
1329                     case LEFT_RIGHT:
1330                         x0 = r.left;            y0 = r.top;
1331                         x1 = level * r.right;   y1 = y0;
1332                         break;
1333                     default:/* TL_BR */
1334                         x0 = r.left;            y0 = r.top;
1335                         x1 = level * r.right;   y1 = level * r.bottom;
1336                         break;
1337                     }
1338 
1339                     mFillPaint.setShader(new LinearGradient(x0, y0, x1, y1,
1340                             gradientColors, st.mPositions, Shader.TileMode.CLAMP));
1341                 } else if (st.mGradient == RADIAL_GRADIENT) {
1342                     x0 = r.left + (r.right - r.left) * st.mCenterX;
1343                     y0 = r.top + (r.bottom - r.top) * st.mCenterY;
1344 
1345                     float radius = st.mGradientRadius;
1346                     if (st.mGradientRadiusType == RADIUS_TYPE_FRACTION) {
1347                         // Fall back to parent width or height if intrinsic
1348                         // size is not specified.
1349                         final float width = st.mWidth >= 0 ? st.mWidth : r.width();
1350                         final float height = st.mHeight >= 0 ? st.mHeight : r.height();
1351                         radius *= Math.min(width, height);
1352                     } else if (st.mGradientRadiusType == RADIUS_TYPE_FRACTION_PARENT) {
1353                         radius *= Math.min(r.width(), r.height());
1354                     }
1355 
1356                     if (st.mUseLevel) {
1357                         radius *= getLevel() / 10000.0f;
1358                     }
1359 
1360                     mGradientRadius = radius;
1361 
1362                     if (radius <= 0) {
1363                         // We can't have a shader with non-positive radius, so
1364                         // let's have a very, very small radius.
1365                         radius = 0.001f;
1366                     }
1367 
1368                     mFillPaint.setShader(new RadialGradient(
1369                             x0, y0, radius, gradientColors, null, Shader.TileMode.CLAMP));
1370                 } else if (st.mGradient == SWEEP_GRADIENT) {
1371                     x0 = r.left + (r.right - r.left) * st.mCenterX;
1372                     y0 = r.top + (r.bottom - r.top) * st.mCenterY;
1373 
1374                     int[] tempColors = gradientColors;
1375                     float[] tempPositions = null;
1376 
1377                     if (st.mUseLevel) {
1378                         tempColors = st.mTempColors;
1379                         final int length = gradientColors.length;
1380                         if (tempColors == null || tempColors.length != length + 1) {
1381                             tempColors = st.mTempColors = new int[length + 1];
1382                         }
1383                         System.arraycopy(gradientColors, 0, tempColors, 0, length);
1384                         tempColors[length] = gradientColors[length - 1];
1385 
1386                         tempPositions = st.mTempPositions;
1387                         final float fraction = 1.0f / (length - 1);
1388                         if (tempPositions == null || tempPositions.length != length + 1) {
1389                             tempPositions = st.mTempPositions = new float[length + 1];
1390                         }
1391 
1392                         final float level = getLevel() / 10000.0f;
1393                         for (int i = 0; i < length; i++) {
1394                             tempPositions[i] = i * fraction * level;
1395                         }
1396                         tempPositions[length] = 1.0f;
1397 
1398                     }
1399                     mFillPaint.setShader(new SweepGradient(x0, y0, tempColors, tempPositions));
1400                 }
1401 
1402                 // If we don't have a solid color, the alpha channel must be
1403                 // maxed out so that alpha modulation works correctly.
1404                 if (st.mSolidColors == null) {
1405                     mFillPaint.setColor(Color.BLACK);
1406                 }
1407             }
1408         }
1409         return !mRect.isEmpty();
1410     }
1411 
1412     @Override
1413     public void inflate(@NonNull Resources r, @NonNull XmlPullParser parser,
1414             @NonNull AttributeSet attrs, @Nullable Theme theme)
1415             throws XmlPullParserException, IOException {
1416         super.inflate(r, parser, attrs, theme);
1417 
1418         mGradientState.setDensity(Drawable.resolveDensity(r, 0));
1419 
1420         final TypedArray a = obtainAttributes(r, theme, attrs, R.styleable.GradientDrawable);
1421         updateStateFromTypedArray(a);
1422         a.recycle();
1423 
1424         inflateChildElements(r, parser, attrs, theme);
1425 
1426         updateLocalState(r);
1427     }
1428 
1429     @Override
1430     public void applyTheme(@NonNull Theme t) {
1431         super.applyTheme(t);
1432 
1433         final GradientState state = mGradientState;
1434         if (state == null) {
1435             return;
1436         }
1437 
1438         state.setDensity(Drawable.resolveDensity(t.getResources(), 0));
1439 
1440         if (state.mThemeAttrs != null) {
1441             final TypedArray a = t.resolveAttributes(
1442                     state.mThemeAttrs, R.styleable.GradientDrawable);
1443             updateStateFromTypedArray(a);
1444             a.recycle();
1445         }
1446 
1447         if (state.mTint != null && state.mTint.canApplyTheme()) {
1448             state.mTint = state.mTint.obtainForTheme(t);
1449         }
1450 
1451         if (state.mSolidColors != null && state.mSolidColors.canApplyTheme()) {
1452             state.mSolidColors = state.mSolidColors.obtainForTheme(t);
1453         }
1454 
1455         if (state.mStrokeColors != null && state.mStrokeColors.canApplyTheme()) {
1456             state.mStrokeColors = state.mStrokeColors.obtainForTheme(t);
1457         }
1458 
1459         if (state.mGradientColors != null) {
1460             for (int i = 0; i < state.mGradientColors.length; i++) {
1461                 if (state.mGradientColors[i] != null && state.mGradientColors[i].canApplyTheme()) {
1462                     state.mGradientColors[i] = state.mGradientColors[i].obtainForTheme(t);
1463                 }
1464             }
1465         }
1466 
1467         applyThemeChildElements(t);
1468 
1469         updateLocalState(t.getResources());
1470     }
1471 
1472     /**
1473      * Updates the constant state from the values in the typed array.
1474      */
1475     private void updateStateFromTypedArray(TypedArray a) {
1476         final GradientState state = mGradientState;
1477 
1478         // Account for any configuration changes.
1479         state.mChangingConfigurations |= a.getChangingConfigurations();
1480 
1481         // Extract the theme attributes, if any.
1482         state.mThemeAttrs = a.extractThemeAttrs();
1483 
1484         state.mShape = a.getInt(R.styleable.GradientDrawable_shape, state.mShape);
1485         state.mDither = a.getBoolean(R.styleable.GradientDrawable_dither, state.mDither);
1486 
1487         if (state.mShape == RING) {
1488             state.mInnerRadius = a.getDimensionPixelSize(
1489                     R.styleable.GradientDrawable_innerRadius, state.mInnerRadius);
1490 
1491             if (state.mInnerRadius == -1) {
1492                 state.mInnerRadiusRatio = a.getFloat(
1493                         R.styleable.GradientDrawable_innerRadiusRatio, state.mInnerRadiusRatio);
1494             }
1495 
1496             state.mThickness = a.getDimensionPixelSize(
1497                     R.styleable.GradientDrawable_thickness, state.mThickness);
1498 
1499             if (state.mThickness == -1) {
1500                 state.mThicknessRatio = a.getFloat(
1501                         R.styleable.GradientDrawable_thicknessRatio, state.mThicknessRatio);
1502             }
1503 
1504             state.mUseLevelForShape = a.getBoolean(
1505                     R.styleable.GradientDrawable_useLevel, state.mUseLevelForShape);
1506         }
1507 
1508         final int tintMode = a.getInt(R.styleable.GradientDrawable_tintMode, -1);
1509         if (tintMode != -1) {
1510             state.mBlendMode = Drawable.parseBlendMode(tintMode, BlendMode.SRC_IN);
1511         }
1512 
1513         final ColorStateList tint = a.getColorStateList(R.styleable.GradientDrawable_tint);
1514         if (tint != null) {
1515             state.mTint = tint;
1516         }
1517 
1518         final int insetLeft = a.getDimensionPixelSize(
1519                 R.styleable.GradientDrawable_opticalInsetLeft, state.mOpticalInsets.left);
1520         final int insetTop = a.getDimensionPixelSize(
1521                 R.styleable.GradientDrawable_opticalInsetTop, state.mOpticalInsets.top);
1522         final int insetRight = a.getDimensionPixelSize(
1523                 R.styleable.GradientDrawable_opticalInsetRight, state.mOpticalInsets.right);
1524         final int insetBottom = a.getDimensionPixelSize(
1525                 R.styleable.GradientDrawable_opticalInsetBottom, state.mOpticalInsets.bottom);
1526         state.mOpticalInsets = Insets.of(insetLeft, insetTop, insetRight, insetBottom);
1527     }
1528 
1529     @Override
1530     public boolean canApplyTheme() {
1531         return (mGradientState != null && mGradientState.canApplyTheme()) || super.canApplyTheme();
1532     }
1533 
1534     private void applyThemeChildElements(Theme t) {
1535         final GradientState st = mGradientState;
1536 
1537         if (st.mAttrSize != null) {
1538             final TypedArray a = t.resolveAttributes(
1539                     st.mAttrSize, R.styleable.GradientDrawableSize);
1540             updateGradientDrawableSize(a);
1541             a.recycle();
1542         }
1543 
1544         if (st.mAttrGradient != null) {
1545             final TypedArray a = t.resolveAttributes(
1546                     st.mAttrGradient, R.styleable.GradientDrawableGradient);
1547             try {
1548                 updateGradientDrawableGradient(t.getResources(), a);
1549             } finally {
1550                 a.recycle();
1551             }
1552         }
1553 
1554         if (st.mAttrSolid != null) {
1555             final TypedArray a = t.resolveAttributes(
1556                     st.mAttrSolid, R.styleable.GradientDrawableSolid);
1557             updateGradientDrawableSolid(a);
1558             a.recycle();
1559         }
1560 
1561         if (st.mAttrStroke != null) {
1562             final TypedArray a = t.resolveAttributes(
1563                     st.mAttrStroke, R.styleable.GradientDrawableStroke);
1564             updateGradientDrawableStroke(a);
1565             a.recycle();
1566         }
1567 
1568         if (st.mAttrCorners != null) {
1569             final TypedArray a = t.resolveAttributes(
1570                     st.mAttrCorners, R.styleable.DrawableCorners);
1571             updateDrawableCorners(a);
1572             a.recycle();
1573         }
1574 
1575         if (st.mAttrPadding != null) {
1576             final TypedArray a = t.resolveAttributes(
1577                     st.mAttrPadding, R.styleable.GradientDrawablePadding);
1578             updateGradientDrawablePadding(a);
1579             a.recycle();
1580         }
1581     }
1582 
1583     private void inflateChildElements(Resources r, XmlPullParser parser, AttributeSet attrs,
1584             Theme theme) throws XmlPullParserException, IOException {
1585         TypedArray a;
1586         int type;
1587 
1588         final int innerDepth = parser.getDepth() + 1;
1589         int depth;
1590         while ((type=parser.next()) != XmlPullParser.END_DOCUMENT
1591                && ((depth=parser.getDepth()) >= innerDepth
1592                        || type != XmlPullParser.END_TAG)) {
1593             if (type != XmlPullParser.START_TAG) {
1594                 continue;
1595             }
1596 
1597             if (depth > innerDepth) {
1598                 continue;
1599             }
1600 
1601             String name = parser.getName();
1602 
1603             if (name.equals("size")) {
1604                 a = obtainAttributes(r, theme, attrs, R.styleable.GradientDrawableSize);
1605                 updateGradientDrawableSize(a);
1606                 a.recycle();
1607             } else if (name.equals("gradient")) {
1608                 a = obtainAttributes(r, theme, attrs, R.styleable.GradientDrawableGradient);
1609                 updateGradientDrawableGradient(r, a);
1610                 a.recycle();
1611             } else if (name.equals("solid")) {
1612                 a = obtainAttributes(r, theme, attrs, R.styleable.GradientDrawableSolid);
1613                 updateGradientDrawableSolid(a);
1614                 a.recycle();
1615             } else if (name.equals("stroke")) {
1616                 a = obtainAttributes(r, theme, attrs, R.styleable.GradientDrawableStroke);
1617                 updateGradientDrawableStroke(a);
1618                 a.recycle();
1619             } else if (name.equals("corners")) {
1620                 a = obtainAttributes(r, theme, attrs, R.styleable.DrawableCorners);
1621                 updateDrawableCorners(a);
1622                 a.recycle();
1623             } else if (name.equals("padding")) {
1624                 a = obtainAttributes(r, theme, attrs, R.styleable.GradientDrawablePadding);
1625                 updateGradientDrawablePadding(a);
1626                 a.recycle();
1627             } else {
1628                 Log.w("drawable", "Bad element under <shape>: " + name);
1629             }
1630         }
1631     }
1632 
1633     private void updateGradientDrawablePadding(TypedArray a) {
1634         final GradientState st = mGradientState;
1635 
1636         // Account for any configuration changes.
1637         st.mChangingConfigurations |= a.getChangingConfigurations();
1638 
1639         // Extract the theme attributes, if any.
1640         st.mAttrPadding = a.extractThemeAttrs();
1641 
1642         if (st.mPadding == null) {
1643             st.mPadding = new Rect();
1644         }
1645 
1646         final Rect pad = st.mPadding;
1647         pad.set(a.getDimensionPixelOffset(R.styleable.GradientDrawablePadding_left, pad.left),
1648                 a.getDimensionPixelOffset(R.styleable.GradientDrawablePadding_top, pad.top),
1649                 a.getDimensionPixelOffset(R.styleable.GradientDrawablePadding_right, pad.right),
1650                 a.getDimensionPixelOffset(R.styleable.GradientDrawablePadding_bottom, pad.bottom));
1651         mPadding = pad;
1652     }
1653 
1654     private void updateDrawableCorners(TypedArray a) {
1655         final GradientState st = mGradientState;
1656 
1657         // Account for any configuration changes.
1658         st.mChangingConfigurations |= a.getChangingConfigurations();
1659 
1660         // Extract the theme attributes, if any.
1661         st.mAttrCorners = a.extractThemeAttrs();
1662 
1663         final int radius = a.getDimensionPixelSize(
1664                 R.styleable.DrawableCorners_radius, (int) st.mRadius);
1665         setCornerRadius(radius);
1666 
1667         // TODO: Update these to be themeable.
1668         final int topLeftRadius = a.getDimensionPixelSize(
1669                 R.styleable.DrawableCorners_topLeftRadius, radius);
1670         final int topRightRadius = a.getDimensionPixelSize(
1671                 R.styleable.DrawableCorners_topRightRadius, radius);
1672         final int bottomLeftRadius = a.getDimensionPixelSize(
1673                 R.styleable.DrawableCorners_bottomLeftRadius, radius);
1674         final int bottomRightRadius = a.getDimensionPixelSize(
1675                 R.styleable.DrawableCorners_bottomRightRadius, radius);
1676         if (topLeftRadius != radius || topRightRadius != radius ||
1677                 bottomLeftRadius != radius || bottomRightRadius != radius) {
1678             // The corner radii are specified in clockwise order (see Path.addRoundRect())
1679             setCornerRadii(new float[] {
1680                     topLeftRadius, topLeftRadius,
1681                     topRightRadius, topRightRadius,
1682                     bottomRightRadius, bottomRightRadius,
1683                     bottomLeftRadius, bottomLeftRadius
1684             });
1685         }
1686     }
1687 
1688     private void updateGradientDrawableStroke(TypedArray a) {
1689         final GradientState st = mGradientState;
1690 
1691         // Account for any configuration changes.
1692         st.mChangingConfigurations |= a.getChangingConfigurations();
1693 
1694         // Extract the theme attributes, if any.
1695         st.mAttrStroke = a.extractThemeAttrs();
1696 
1697         // We have an explicit stroke defined, so the default stroke width
1698         // must be at least 0 or the current stroke width.
1699         final int defaultStrokeWidth = Math.max(0, st.mStrokeWidth);
1700         final int width = a.getDimensionPixelSize(
1701                 R.styleable.GradientDrawableStroke_width, defaultStrokeWidth);
1702         final float dashWidth = a.getDimension(
1703                 R.styleable.GradientDrawableStroke_dashWidth, st.mStrokeDashWidth);
1704 
1705         ColorStateList colorStateList = a.getColorStateList(
1706                 R.styleable.GradientDrawableStroke_color);
1707         if (colorStateList == null) {
1708             colorStateList = st.mStrokeColors;
1709         }
1710 
1711         if (dashWidth != 0.0f) {
1712             final float dashGap = a.getDimension(
1713                     R.styleable.GradientDrawableStroke_dashGap, st.mStrokeDashGap);
1714             setStroke(width, colorStateList, dashWidth, dashGap);
1715         } else {
1716             setStroke(width, colorStateList);
1717         }
1718     }
1719 
1720     private void updateGradientDrawableSolid(TypedArray a) {
1721         final GradientState st = mGradientState;
1722 
1723         // Account for any configuration changes.
1724         st.mChangingConfigurations |= a.getChangingConfigurations();
1725 
1726         // Extract the theme attributes, if any.
1727         st.mAttrSolid = a.extractThemeAttrs();
1728 
1729         final ColorStateList colorStateList = a.getColorStateList(
1730                 R.styleable.GradientDrawableSolid_color);
1731         if (colorStateList != null) {
1732             setColor(colorStateList);
1733         }
1734     }
1735 
1736     private void updateGradientDrawableGradient(Resources r, TypedArray a) {
1737         final GradientState st = mGradientState;
1738 
1739         // Account for any configuration changes.
1740         st.mChangingConfigurations |= a.getChangingConfigurations();
1741 
1742         // Extract the theme attributes, if any.
1743         st.mAttrGradient = a.extractThemeAttrs();
1744 
1745         st.mCenterX = getFloatOrFraction(
1746                 a, R.styleable.GradientDrawableGradient_centerX, st.mCenterX);
1747         st.mCenterY = getFloatOrFraction(
1748                 a, R.styleable.GradientDrawableGradient_centerY, st.mCenterY);
1749         st.mUseLevel = a.getBoolean(
1750                 R.styleable.GradientDrawableGradient_useLevel, st.mUseLevel);
1751         st.mGradient = a.getInt(
1752                 R.styleable.GradientDrawableGradient_type, st.mGradient);
1753 
1754         ColorStateList startCSL = a.getColorStateList(
1755                 R.styleable.GradientDrawableGradient_startColor);
1756         ColorStateList centerCSL = a.getColorStateList(
1757                 R.styleable.GradientDrawableGradient_centerColor);
1758         ColorStateList endCSL = a.getColorStateList(
1759                 R.styleable.GradientDrawableGradient_endColor);
1760 
1761         final boolean hasGradientColors = st.mGradientColors != null;
1762         final boolean hasGradientCenter = st.hasCenterColor();
1763 
1764         int startColor = startCSL != null ? startCSL.getDefaultColor() : 0;
1765         int centerColor = centerCSL != null ? centerCSL.getDefaultColor() : 0;
1766         int endColor = endCSL != null ? endCSL.getDefaultColor() : 0;
1767 
1768         if (hasGradientColors && st.mGradientColors[0] != null) {
1769             startColor = st.mGradientColors[0].getDefaultColor();
1770         }
1771         if (hasGradientCenter && st.mGradientColors[1] != null) {
1772             centerColor = st.mGradientColors[1].getDefaultColor();
1773         }
1774         if (hasGradientCenter && st.mGradientColors[2] != null) {
1775             // if there is a center color, the end color is the last of the 3 values
1776             endColor = st.mGradientColors[2].getDefaultColor();
1777         } else if (hasGradientColors && st.mGradientColors[1] != null) {
1778             // if there is not a center color but there are already colors configured, then
1779             // the end color is the 2nd value in the array
1780             endColor = st.mGradientColors[1].getDefaultColor();
1781         }
1782 
1783         final boolean hasCenterColor = a.hasValue(
1784                 R.styleable.GradientDrawableGradient_centerColor) || hasGradientCenter;
1785 
1786         if (hasCenterColor) {
1787             st.mGradientColors = new ColorStateList[3];
1788             st.mGradientColors[0] =
1789                     startCSL != null ? startCSL : ColorStateList.valueOf(startColor);
1790             st.mGradientColors[1] =
1791                     centerCSL != null ? centerCSL : ColorStateList.valueOf(centerColor);
1792             st.mGradientColors[2] =
1793                     endCSL != null ? endCSL : ColorStateList.valueOf(endColor);
1794 
1795             st.mPositions = new float[3];
1796             st.mPositions[0] = 0.0f;
1797             // Since 0.5f is default value, try to take the one that isn't 0.5f
1798             st.mPositions[1] = st.mCenterX != 0.5f ? st.mCenterX : st.mCenterY;
1799             st.mPositions[2] = 1f;
1800         } else {
1801             st.mGradientColors = new ColorStateList[2];
1802             st.mGradientColors[0] =
1803                     startCSL != null ? startCSL : ColorStateList.valueOf(startColor);
1804             st.mGradientColors[1] =
1805                     endCSL != null ? endCSL : ColorStateList.valueOf(endColor);
1806         }
1807 
1808         int angle = (int) a.getFloat(R.styleable.GradientDrawableGradient_angle, st.mAngle);
1809 
1810         // GradientDrawable historically has not parsed negative angle measurements and always
1811         // stays on the default orientation for API levels older than Q.
1812         // Only configure the orientation if the angle is greater than zero.
1813         // Otherwise fallback on Orientation.TOP_BOTTOM
1814         // In Android Q and later, actually wrap the negative angle measurement to the correct
1815         // value
1816         if (sWrapNegativeAngleMeasurements) {
1817             st.mAngle = ((angle % 360) + 360) % 360; // offset negative angle measures
1818         } else {
1819             st.mAngle = angle % 360;
1820         }
1821 
1822         if (st.mAngle >= 0) {
1823             switch (st.mAngle) {
1824                 case 0:
1825                     st.mOrientation = Orientation.LEFT_RIGHT;
1826                     break;
1827                 case 45:
1828                     st.mOrientation = Orientation.BL_TR;
1829                     break;
1830                 case 90:
1831                     st.mOrientation = Orientation.BOTTOM_TOP;
1832                     break;
1833                 case 135:
1834                     st.mOrientation = Orientation.BR_TL;
1835                     break;
1836                 case 180:
1837                     st.mOrientation = Orientation.RIGHT_LEFT;
1838                     break;
1839                 case 225:
1840                     st.mOrientation = Orientation.TR_BL;
1841                     break;
1842                 case 270:
1843                     st.mOrientation = Orientation.TOP_BOTTOM;
1844                     break;
1845                 case 315:
1846                     st.mOrientation = Orientation.TL_BR;
1847                     break;
1848             }
1849         } else {
1850             st.mOrientation = DEFAULT_ORIENTATION;
1851         }
1852 
1853         final TypedValue tv = a.peekValue(R.styleable.GradientDrawableGradient_gradientRadius);
1854         if (tv != null) {
1855             final float radius;
1856             final @RadiusType int radiusType;
1857             if (tv.type == TypedValue.TYPE_FRACTION) {
1858                 radius = tv.getFraction(1.0f, 1.0f);
1859 
1860                 final int unit = (tv.data >> TypedValue.COMPLEX_UNIT_SHIFT)
1861                         & TypedValue.COMPLEX_UNIT_MASK;
1862                 if (unit == TypedValue.COMPLEX_UNIT_FRACTION_PARENT) {
1863                     radiusType = RADIUS_TYPE_FRACTION_PARENT;
1864                 } else {
1865                     radiusType = RADIUS_TYPE_FRACTION;
1866                 }
1867             } else if (tv.type == TypedValue.TYPE_DIMENSION) {
1868                 radius = tv.getDimension(r.getDisplayMetrics());
1869                 radiusType = RADIUS_TYPE_PIXELS;
1870             } else {
1871                 radius = tv.getFloat();
1872                 radiusType = RADIUS_TYPE_PIXELS;
1873             }
1874 
1875             st.mGradientRadius = radius;
1876             st.mGradientRadiusType = radiusType;
1877         }
1878     }
1879 
1880     private void updateGradientDrawableSize(TypedArray a) {
1881         final GradientState st = mGradientState;
1882 
1883         // Account for any configuration changes.
1884         st.mChangingConfigurations |= a.getChangingConfigurations();
1885 
1886         // Extract the theme attributes, if any.
1887         st.mAttrSize = a.extractThemeAttrs();
1888 
1889         st.mWidth = a.getDimensionPixelSize(R.styleable.GradientDrawableSize_width, st.mWidth);
1890         st.mHeight = a.getDimensionPixelSize(R.styleable.GradientDrawableSize_height, st.mHeight);
1891     }
1892 
1893     private static float getFloatOrFraction(TypedArray a, int index, float defaultValue) {
1894         TypedValue tv = a.peekValue(index);
1895         float v = defaultValue;
1896         if (tv != null) {
1897             boolean vIsFraction = tv.type == TypedValue.TYPE_FRACTION;
1898             v = vIsFraction ? tv.getFraction(1.0f, 1.0f) : tv.getFloat();
1899         }
1900         return v;
1901     }
1902 
1903     @Override
1904     public int getIntrinsicWidth() {
1905         return mGradientState.mWidth;
1906     }
1907 
1908     @Override
1909     public int getIntrinsicHeight() {
1910         return mGradientState.mHeight;
1911     }
1912 
1913     @Override
1914     public Insets getOpticalInsets() {
1915         return mGradientState.mOpticalInsets;
1916     }
1917 
1918     @Override
1919     public ConstantState getConstantState() {
1920         mGradientState.mChangingConfigurations = getChangingConfigurations();
1921         return mGradientState;
1922     }
1923 
1924     private boolean isOpaqueForState() {
1925         if (mGradientState.mStrokeWidth >= 0 && mStrokePaint != null
1926                 && !isOpaque(mStrokePaint.getColor())) {
1927             return false;
1928         }
1929 
1930         // Don't check opacity if we're using a gradient, as we've already
1931         // checked the gradient opacity in mOpaqueOverShape.
1932         if (mGradientState.mGradientColors == null && !isOpaque(mFillPaint.getColor())) {
1933             return false;
1934         }
1935 
1936         return true;
1937     }
1938 
1939     @Override
1940     public void getOutline(Outline outline) {
1941         final GradientState st = mGradientState;
1942         final Rect bounds = getBounds();
1943         // only report non-zero alpha if shape being drawn has consistent opacity over shape. Must
1944         // either not have a stroke, or have same stroke/fill opacity
1945         boolean useFillOpacity = st.mOpaqueOverShape && (mGradientState.mStrokeWidth <= 0
1946                 || mStrokePaint == null
1947                 || mStrokePaint.getAlpha() == mFillPaint.getAlpha());
1948         outline.setAlpha(useFillOpacity
1949                 ? modulateAlpha(mFillPaint.getAlpha()) / 255.0f
1950                 : 0.0f);
1951 
1952         switch (st.mShape) {
1953             case RECTANGLE:
1954                 if (st.mRadiusArray != null) {
1955                     buildPathIfDirty();
1956                     outline.setPath(mPath);
1957                     return;
1958                 }
1959 
1960                 float rad = 0;
1961                 if (st.mRadius > 0.0f) {
1962                     // clamp the radius based on width & height, matching behavior in draw()
1963                     rad = Math.min(st.mRadius,
1964                             Math.min(bounds.width(), bounds.height()) * 0.5f);
1965                 }
1966                 outline.setRoundRect(bounds, rad);
1967                 return;
1968             case OVAL:
1969                 outline.setOval(bounds);
1970                 return;
1971             case LINE:
1972                 // Hairlines (0-width stroke) must have a non-empty outline for
1973                 // shadows to draw correctly, so we'll use a very small width.
1974                 final float halfStrokeWidth = mStrokePaint == null ?
1975                         0.0001f : mStrokePaint.getStrokeWidth() * 0.5f;
1976                 final float centerY = bounds.centerY();
1977                 final int top = (int) Math.floor(centerY - halfStrokeWidth);
1978                 final int bottom = (int) Math.ceil(centerY + halfStrokeWidth);
1979 
1980                 outline.setRect(bounds.left, top, bounds.right, bottom);
1981                 return;
1982             default:
1983                 // TODO: support more complex shapes
1984         }
1985     }
1986 
1987     @Override
1988     public Drawable mutate() {
1989         if (!mMutated && super.mutate() == this) {
1990             mGradientState = new GradientState(mGradientState, null);
1991             updateLocalState(null);
1992             mMutated = true;
1993         }
1994         return this;
1995     }
1996 
1997     /**
1998      * @hide
1999      */
2000     public void clearMutated() {
2001         super.clearMutated();
2002         mMutated = false;
2003     }
2004 
2005     final static class GradientState extends ConstantState {
2006         public @Config int mChangingConfigurations;
2007         @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 124050917)
2008         public @Shape int mShape = RECTANGLE;
2009         @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 124050917)
2010         public @GradientType int mGradient = LINEAR_GRADIENT;
2011         @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 124050917)
2012         public int mAngle = 0;
2013         @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 124050917)
2014         public Orientation mOrientation;
2015         @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 124050917)
2016         public ColorStateList mSolidColors;
2017         public ColorStateList mStrokeColors;
2018         @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug =  124050917)
2019         public ColorStateList[] mGradientColors; // no support for state-based color
2020         public @ColorInt int[] mTempColors; // no need to copy
2021         public float[] mTempPositions; // no need to copy
2022         @UnsupportedAppUsage
2023         public float[] mPositions;
2024         @UnsupportedAppUsage(trackingBug = 124050917)
2025         public int mStrokeWidth = -1; // if >= 0 use stroking.
2026         @UnsupportedAppUsage(trackingBug = 124050917)
2027         public float mStrokeDashWidth = 0.0f;
2028         @UnsupportedAppUsage(trackingBug = 124050917)
2029         public float mStrokeDashGap = 0.0f;
2030         @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 124050917)
2031         public float mRadius = 0.0f; // use this if mRadiusArray is null
2032         @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 124050917)
2033         public float[] mRadiusArray = null;
2034         @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 124050917)
2035         public Rect mPadding = null;
2036         @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 124050917)
2037         public int mWidth = -1;
2038         @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 124050917)
2039         public int mHeight = -1;
2040         @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 124050917)
2041         public float mInnerRadiusRatio = DEFAULT_INNER_RADIUS_RATIO;
2042         @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 124050218)
2043         public float mThicknessRatio = DEFAULT_THICKNESS_RATIO;
2044         @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 124050917)
2045         public int mInnerRadius = -1;
2046         @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 124050218)
2047         public int mThickness = -1;
2048         public boolean mDither = false;
2049         public Insets mOpticalInsets = Insets.NONE;
2050 
2051         float mCenterX = 0.5f;
2052         float mCenterY = 0.5f;
2053         float mGradientRadius = 0.5f;
2054         @RadiusType int mGradientRadiusType = RADIUS_TYPE_PIXELS;
2055         boolean mUseLevel = false;
2056         boolean mUseLevelForShape = true;
2057 
2058         boolean mOpaqueOverBounds;
2059         boolean mOpaqueOverShape;
2060 
2061         ColorStateList mTint = null;
2062         BlendMode mBlendMode = DEFAULT_BLEND_MODE;
2063 
2064         int mDensity = DisplayMetrics.DENSITY_DEFAULT;
2065 
2066         int[] mThemeAttrs;
2067         int[] mAttrSize;
2068         int[] mAttrGradient;
2069         int[] mAttrSolid;
2070         int[] mAttrStroke;
2071         int[] mAttrCorners;
2072         int[] mAttrPadding;
2073 
2074         public GradientState(Orientation orientation, int[] gradientColors) {
2075             mOrientation = orientation;
2076             setGradientColors(gradientColors);
2077         }
2078 
2079         public GradientState(@NonNull GradientState orig, @Nullable Resources res) {
2080             mChangingConfigurations = orig.mChangingConfigurations;
2081             mShape = orig.mShape;
2082             mGradient = orig.mGradient;
2083             mAngle = orig.mAngle;
2084             mOrientation = orig.mOrientation;
2085             mSolidColors = orig.mSolidColors;
2086             if (orig.mGradientColors != null) {
2087                 mGradientColors = orig.mGradientColors.clone();
2088             }
2089             if (orig.mPositions != null) {
2090                 mPositions = orig.mPositions.clone();
2091             }
2092             mStrokeColors = orig.mStrokeColors;
2093             mStrokeWidth = orig.mStrokeWidth;
2094             mStrokeDashWidth = orig.mStrokeDashWidth;
2095             mStrokeDashGap = orig.mStrokeDashGap;
2096             mRadius = orig.mRadius;
2097             if (orig.mRadiusArray != null) {
2098                 mRadiusArray = orig.mRadiusArray.clone();
2099             }
2100             if (orig.mPadding != null) {
2101                 mPadding = new Rect(orig.mPadding);
2102             }
2103             mWidth = orig.mWidth;
2104             mHeight = orig.mHeight;
2105             mInnerRadiusRatio = orig.mInnerRadiusRatio;
2106             mThicknessRatio = orig.mThicknessRatio;
2107             mInnerRadius = orig.mInnerRadius;
2108             mThickness = orig.mThickness;
2109             mDither = orig.mDither;
2110             mOpticalInsets = orig.mOpticalInsets;
2111             mCenterX = orig.mCenterX;
2112             mCenterY = orig.mCenterY;
2113             mGradientRadius = orig.mGradientRadius;
2114             mGradientRadiusType = orig.mGradientRadiusType;
2115             mUseLevel = orig.mUseLevel;
2116             mUseLevelForShape = orig.mUseLevelForShape;
2117             mOpaqueOverBounds = orig.mOpaqueOverBounds;
2118             mOpaqueOverShape = orig.mOpaqueOverShape;
2119             mTint = orig.mTint;
2120             mBlendMode = orig.mBlendMode;
2121             mThemeAttrs = orig.mThemeAttrs;
2122             mAttrSize = orig.mAttrSize;
2123             mAttrGradient = orig.mAttrGradient;
2124             mAttrSolid = orig.mAttrSolid;
2125             mAttrStroke = orig.mAttrStroke;
2126             mAttrCorners = orig.mAttrCorners;
2127             mAttrPadding = orig.mAttrPadding;
2128 
2129             mDensity = Drawable.resolveDensity(res, orig.mDensity);
2130             if (orig.mDensity != mDensity) {
2131                 applyDensityScaling(orig.mDensity, mDensity);
2132             }
2133         }
2134 
2135         /**
2136          * Sets the constant state density.
2137          * <p>
2138          * If the density has been previously set, dispatches the change to
2139          * subclasses so that density-dependent properties may be scaled as
2140          * necessary.
2141          *
2142          * @param targetDensity the new constant state density
2143          */
2144         public final void setDensity(int targetDensity) {
2145             if (mDensity != targetDensity) {
2146                 final int sourceDensity = mDensity;
2147                 mDensity = targetDensity;
2148 
2149                 applyDensityScaling(sourceDensity, targetDensity);
2150             }
2151         }
2152 
2153         public boolean hasCenterColor() {
2154             return mGradientColors != null && mGradientColors.length == 3;
2155         }
2156 
2157         private void applyDensityScaling(int sourceDensity, int targetDensity) {
2158             if (mInnerRadius > 0) {
2159                 mInnerRadius = Drawable.scaleFromDensity(
2160                         mInnerRadius, sourceDensity, targetDensity, true);
2161             }
2162             if (mThickness > 0) {
2163                 mThickness = Drawable.scaleFromDensity(
2164                         mThickness, sourceDensity, targetDensity, true);
2165             }
2166             if (mOpticalInsets != Insets.NONE) {
2167                 final int left = Drawable.scaleFromDensity(
2168                         mOpticalInsets.left, sourceDensity, targetDensity, true);
2169                 final int top = Drawable.scaleFromDensity(
2170                         mOpticalInsets.top, sourceDensity, targetDensity, true);
2171                 final int right = Drawable.scaleFromDensity(
2172                         mOpticalInsets.right, sourceDensity, targetDensity, true);
2173                 final int bottom = Drawable.scaleFromDensity(
2174                         mOpticalInsets.bottom, sourceDensity, targetDensity, true);
2175                 mOpticalInsets = Insets.of(left, top, right, bottom);
2176             }
2177             if (mPadding != null) {
2178                 mPadding.left = Drawable.scaleFromDensity(
2179                         mPadding.left, sourceDensity, targetDensity, false);
2180                 mPadding.top = Drawable.scaleFromDensity(
2181                         mPadding.top, sourceDensity, targetDensity, false);
2182                 mPadding.right = Drawable.scaleFromDensity(
2183                         mPadding.right, sourceDensity, targetDensity, false);
2184                 mPadding.bottom = Drawable.scaleFromDensity(
2185                         mPadding.bottom, sourceDensity, targetDensity, false);
2186             }
2187             if (mRadius > 0) {
2188                 mRadius = Drawable.scaleFromDensity(mRadius, sourceDensity, targetDensity);
2189             }
2190             if (mRadiusArray != null) {
2191                 mRadiusArray[0] = Drawable.scaleFromDensity(
2192                         (int) mRadiusArray[0], sourceDensity, targetDensity, true);
2193                 mRadiusArray[1] = Drawable.scaleFromDensity(
2194                         (int) mRadiusArray[1], sourceDensity, targetDensity, true);
2195                 mRadiusArray[2] = Drawable.scaleFromDensity(
2196                         (int) mRadiusArray[2], sourceDensity, targetDensity, true);
2197                 mRadiusArray[3] = Drawable.scaleFromDensity(
2198                         (int) mRadiusArray[3], sourceDensity, targetDensity, true);
2199             }
2200             if (mStrokeWidth > 0) {
2201                 mStrokeWidth = Drawable.scaleFromDensity(
2202                         mStrokeWidth, sourceDensity, targetDensity, true);
2203             }
2204             if (mStrokeDashWidth > 0) {
2205                 mStrokeDashWidth = Drawable.scaleFromDensity(
2206                         mStrokeDashGap, sourceDensity, targetDensity);
2207             }
2208             if (mStrokeDashGap > 0) {
2209                 mStrokeDashGap = Drawable.scaleFromDensity(
2210                         mStrokeDashGap, sourceDensity, targetDensity);
2211             }
2212             if (mGradientRadiusType == RADIUS_TYPE_PIXELS) {
2213                 mGradientRadius = Drawable.scaleFromDensity(
2214                         mGradientRadius, sourceDensity, targetDensity);
2215             }
2216             if (mWidth > 0) {
2217                 mWidth = Drawable.scaleFromDensity(mWidth, sourceDensity, targetDensity, true);
2218             }
2219             if (mHeight > 0) {
2220                 mHeight = Drawable.scaleFromDensity(mHeight, sourceDensity, targetDensity, true);
2221             }
2222         }
2223 
2224         @Override
2225         public boolean canApplyTheme() {
2226             boolean mGradientColorState = mGradientColors != null;
2227             if (mGradientColors != null) {
2228                 for (int i = 0; i < mGradientColors.length; i++) {
2229                     mGradientColorState |= (mGradientColors[i] != null && mGradientColors[i]
2230                         .canApplyTheme());
2231                 }
2232             }
2233             return mThemeAttrs != null
2234                     || mAttrSize != null || mAttrGradient != null
2235                     || mAttrSolid != null || mAttrStroke != null
2236                     || mAttrCorners != null || mAttrPadding != null
2237                     || (mTint != null && mTint.canApplyTheme())
2238                     || (mStrokeColors != null && mStrokeColors.canApplyTheme())
2239                     || (mSolidColors != null && mSolidColors.canApplyTheme())
2240                     || mGradientColorState
2241                     || super.canApplyTheme();
2242         }
2243 
2244         @Override
2245         public Drawable newDrawable() {
2246             return new GradientDrawable(this, null);
2247         }
2248 
2249         @Override
2250         public Drawable newDrawable(@Nullable Resources res) {
2251             // If this drawable is being created for a different density,
2252             // just create a new constant state and call it a day.
2253             final GradientState state;
2254             final int density = Drawable.resolveDensity(res, mDensity);
2255             if (density != mDensity) {
2256                 state = new GradientState(this, res);
2257             } else {
2258                 state = this;
2259             }
2260 
2261             return new GradientDrawable(state, res);
2262         }
2263 
2264         @Override
2265         public @Config int getChangingConfigurations() {
2266             return mChangingConfigurations
2267                     | (mStrokeColors != null ? mStrokeColors.getChangingConfigurations() : 0)
2268                     | (mSolidColors != null ? mSolidColors.getChangingConfigurations() : 0)
2269                     | (mTint != null ? mTint.getChangingConfigurations() : 0);
2270         }
2271 
2272         public void setShape(@Shape int shape) {
2273             mShape = shape;
2274             computeOpacity();
2275         }
2276 
2277         public void setGradientType(@GradientType int gradient) {
2278             mGradient = gradient;
2279         }
2280 
2281         public void setGradientCenter(float x, float y) {
2282             mCenterX = x;
2283             mCenterY = y;
2284         }
2285 
2286         @NonNull
2287         public Orientation getOrientation() {
2288             return mOrientation;
2289         }
2290 
2291         public void setGradientColors(@Nullable int[] colors) {
2292             if (colors == null) {
2293                 mGradientColors = null;
2294             } else {
2295                 // allocate new CSL array only if the size of the current array is different
2296                 // from the size of the given parameter
2297                 if (mGradientColors == null || mGradientColors.length != colors.length) {
2298                     mGradientColors = new ColorStateList[colors.length];
2299                 }
2300                 for (int i = 0; i < colors.length; i++) {
2301                     mGradientColors[i] = ColorStateList.valueOf(colors[i]);
2302                 }
2303             }
2304             mSolidColors = null;
2305             computeOpacity();
2306         }
2307 
2308         public void setSolidColors(@Nullable ColorStateList colors) {
2309             mGradientColors = null;
2310             mSolidColors = colors;
2311             computeOpacity();
2312         }
2313 
2314         private void computeOpacity() {
2315             mOpaqueOverBounds = false;
2316             mOpaqueOverShape = false;
2317 
2318             if (mGradientColors != null) {
2319                 for (int i = 0; i < mGradientColors.length; i++) {
2320                     if (mGradientColors[i] != null
2321                             && !isOpaque(mGradientColors[i].getDefaultColor())) {
2322                         return;
2323                     }
2324                 }
2325             }
2326 
2327             // An unfilled shape is not opaque over bounds or shape
2328             if (mGradientColors == null && mSolidColors == null) {
2329                 return;
2330             }
2331 
2332             // Colors are opaque, so opaqueOverShape=true,
2333             mOpaqueOverShape = true;
2334             // and opaqueOverBounds=true if shape fills bounds
2335             mOpaqueOverBounds = mShape == RECTANGLE
2336                     && mRadius <= 0
2337                     && mRadiusArray == null;
2338         }
2339 
2340         public void setStroke(int width, @Nullable ColorStateList colors, float dashWidth,
2341                 float dashGap) {
2342             mStrokeWidth = width;
2343             mStrokeColors = colors;
2344             mStrokeDashWidth = dashWidth;
2345             mStrokeDashGap = dashGap;
2346             computeOpacity();
2347         }
2348 
2349         public void setCornerRadius(float radius) {
2350             if (radius < 0) {
2351                 radius = 0;
2352             }
2353             mRadius = radius;
2354             mRadiusArray = null;
2355             computeOpacity();
2356         }
2357 
2358         public void setCornerRadii(float[] radii) {
2359             mRadiusArray = radii;
2360             if (radii == null) {
2361                 mRadius = 0;
2362             }
2363             computeOpacity();
2364         }
2365 
2366         public void setSize(int width, int height) {
2367             mWidth = width;
2368             mHeight = height;
2369         }
2370 
2371         public void setGradientRadius(float gradientRadius, @RadiusType int type) {
2372             mGradientRadius = gradientRadius;
2373             mGradientRadiusType = type;
2374         }
2375     }
2376 
2377     static boolean isOpaque(int color) {
2378         return ((color >> 24) & 0xff) == 0xff;
2379     }
2380 
2381     /**
2382      * Creates a new themed GradientDrawable based on the specified constant state.
2383      * <p>
2384      * The resulting drawable is guaranteed to have a new constant state.
2385      *
2386      * @param state Constant state from which the drawable inherits
2387      */
2388     private GradientDrawable(@NonNull GradientState state, @Nullable Resources res) {
2389         mGradientState = state;
2390 
2391         updateLocalState(res);
2392     }
2393 
2394     private void updateLocalState(Resources res) {
2395         final GradientState state = mGradientState;
2396 
2397         if (state.mSolidColors != null) {
2398             final int[] currentState = getState();
2399             final int stateColor = state.mSolidColors.getColorForState(currentState, 0);
2400             mFillPaint.setColor(stateColor);
2401         } else if (state.mGradientColors == null) {
2402             // If we don't have a solid color and we don't have a gradient,
2403             // the app is stroking the shape, set the color to the default
2404             // value of state.mSolidColor
2405             mFillPaint.setColor(0);
2406         } else {
2407             // Otherwise, make sure the fill alpha is maxed out.
2408             mFillPaint.setColor(Color.BLACK);
2409         }
2410 
2411         mPadding = state.mPadding;
2412 
2413         if (state.mStrokeWidth >= 0) {
2414             mStrokePaint = new Paint(Paint.ANTI_ALIAS_FLAG);
2415             mStrokePaint.setStyle(Paint.Style.STROKE);
2416             mStrokePaint.setStrokeWidth(state.mStrokeWidth);
2417 
2418             if (state.mStrokeColors != null) {
2419                 final int[] currentState = getState();
2420                 final int strokeStateColor = state.mStrokeColors.getColorForState(
2421                         currentState, 0);
2422                 mStrokePaint.setColor(strokeStateColor);
2423             }
2424 
2425             if (state.mStrokeDashWidth != 0.0f) {
2426                 final DashPathEffect e = new DashPathEffect(
2427                         new float[] { state.mStrokeDashWidth, state.mStrokeDashGap }, 0);
2428                 mStrokePaint.setPathEffect(e);
2429             }
2430         }
2431 
2432         mBlendModeColorFilter = updateBlendModeFilter(mBlendModeColorFilter, state.mTint,
2433                 state.mBlendMode);
2434         mGradientIsDirty = true;
2435 
2436         state.computeOpacity();
2437     }
2438 }
2439