• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 package com.android.customization.widget;
2 
3 import android.content.res.ColorStateList;
4 import android.content.res.Resources;
5 import android.content.res.Resources.Theme;
6 import android.graphics.Bitmap;
7 import android.graphics.BitmapShader;
8 import android.graphics.Canvas;
9 import android.graphics.Color;
10 import android.graphics.ColorFilter;
11 import android.graphics.Matrix;
12 import android.graphics.Outline;
13 import android.graphics.Paint;
14 import android.graphics.Path;
15 import android.graphics.PixelFormat;
16 import android.graphics.PorterDuff.Mode;
17 import android.graphics.Rect;
18 import android.graphics.Region;
19 import android.graphics.Shader;
20 import android.graphics.Shader.TileMode;
21 import android.graphics.drawable.AdaptiveIconDrawable;
22 import android.graphics.drawable.Drawable;
23 import android.util.AttributeSet;
24 import android.util.DisplayMetrics;
25 
26 import androidx.annotation.NonNull;
27 import androidx.annotation.Nullable;
28 
29 import org.xmlpull.v1.XmlPullParser;
30 import org.xmlpull.v1.XmlPullParserException;
31 
32 import java.io.IOException;
33 
34 /**
35  * This is basically a copy of {@link AdaptiveIconDrawable} but which allows a custom path for
36  * the icon mask instead of using the one defined in the system.
37  * Note: Unlike AdaptiveIconDrawable we don't need to deal with densityOverride here so that
38  * logic is omitted.
39  */
40 public class DynamicAdaptiveIconDrawable extends Drawable implements Drawable.Callback {
41 
42     /**
43      * Mask path is defined inside device configuration in following dimension: [100 x 100]
44      */
45     private static final float MASK_SIZE = 100f;
46 
47     /**
48      * All four sides of the layers are padded with extra inset so as to provide
49      * extra content to reveal within the clip path when performing affine transformations on the
50      * layers.
51      *
52      * Each layers will reserve 25% of it's width and height.
53      *
54      * As a result, the view port of the layers is smaller than their intrinsic width and height.
55      */
56     private static final float EXTRA_INSET_PERCENTAGE = 1 / 4f;
57     private static final float DEFAULT_VIEW_PORT_SCALE = 1f / (1 + 2 * EXTRA_INSET_PERCENTAGE);
58 
59     private final Path mOriginalMask;
60 
61     /**
62      * Scaled mask based on the view bounds.
63      */
64     private final Path mMask;
65     private final Path mMaskScaleOnly;
66     private final Matrix mMaskMatrix;
67     private final Region mTransparentRegion;
68 
69     /**
70      * Indices used to access {@link #mLayerState.mChildren} array for foreground and
71      * background layer.
72      */
73     private static final int BACKGROUND_ID = 0;
74     private static final int FOREGROUND_ID = 1;
75 
76     /**
77      * State variable that maintains the {@link ChildDrawable} array.
78      */
79     private LayerState mLayerState;
80 
81     private Shader mLayersShader;
82     private Bitmap mLayersBitmap;
83 
84     private final Rect mTmpOutRect = new Rect();
85     private Rect mHotspotBounds;
86     private boolean mMutated;
87 
88     private boolean mSuspendChildInvalidation;
89     private boolean mChildRequestedInvalidation;
90     private final Canvas mCanvas;
91     private Paint mPaint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.DITHER_FLAG |
92             Paint.FILTER_BITMAP_FLAG);
93 
94     /**
95      * Constructor used for xml inflation.
96      */
DynamicAdaptiveIconDrawable()97     DynamicAdaptiveIconDrawable() {
98         this((LayerState) null, null, null);
99     }
100 
101     /**
102      * The one constructor to rule them all. This is called by all public
103      * constructors to set the state and initialize local properties.
104      */
DynamicAdaptiveIconDrawable(@ullable LayerState state, @Nullable Resources res, Path iconMask)105     private DynamicAdaptiveIconDrawable(@Nullable LayerState state, @Nullable Resources res,
106             Path iconMask) {
107         mLayerState = createConstantState(state, res);
108 
109         mOriginalMask = iconMask;
110         mMask = new Path(iconMask);
111         mMaskScaleOnly = new Path(mMask);
112         mMaskMatrix = new Matrix();
113         mCanvas = new Canvas();
114         mTransparentRegion = new Region();
115     }
116 
createChildDrawable(Drawable drawable)117     private ChildDrawable createChildDrawable(Drawable drawable) {
118         final ChildDrawable layer = new ChildDrawable(mLayerState.mDensity);
119         layer.mDrawable = drawable;
120         layer.mDrawable.setCallback(this);
121         mLayerState.mChildrenChangingConfigurations |=
122                 layer.mDrawable.getChangingConfigurations();
123         return layer;
124     }
125 
createConstantState(@ullable LayerState state, @Nullable Resources res)126     private LayerState createConstantState(@Nullable LayerState state, @Nullable Resources res) {
127         return new LayerState(state, this, res);
128     }
129 
130     /**
131      * Constructor used to dynamically create this drawable.
132      *
133      * @param backgroundDrawable drawable that should be rendered in the background
134      * @param foregroundDrawable drawable that should be rendered in the foreground
135      * @param iconMask path to use to mask the icon
136      */
DynamicAdaptiveIconDrawable(Drawable backgroundDrawable, Drawable foregroundDrawable, Path iconMask)137     public DynamicAdaptiveIconDrawable(Drawable backgroundDrawable,
138             Drawable foregroundDrawable, Path iconMask) {
139         this((LayerState)null, null, iconMask);
140         if (backgroundDrawable != null) {
141             addLayer(BACKGROUND_ID, createChildDrawable(backgroundDrawable));
142         }
143         if (foregroundDrawable != null) {
144             addLayer(FOREGROUND_ID, createChildDrawable(foregroundDrawable));
145         }
146     }
147 
148     /**
149      * Sets the layer to the {@param index} and invalidates cache.
150      *
151      * @param index The index of the layer.
152      * @param layer The layer to add.
153      */
addLayer(int index, @NonNull ChildDrawable layer)154     private void addLayer(int index, @NonNull ChildDrawable layer) {
155         mLayerState.mChildren[index] = layer;
156         mLayerState.invalidateCache();
157     }
158 
159     @Override
inflate(@onNull Resources r, @NonNull XmlPullParser parser, @NonNull AttributeSet attrs, @Nullable Theme theme)160     public void inflate(@NonNull Resources r, @NonNull XmlPullParser parser,
161             @NonNull AttributeSet attrs, @Nullable Theme theme)
162             throws XmlPullParserException, IOException {
163         super.inflate(r, parser, attrs, theme);
164 
165         final LayerState state = mLayerState;
166         if (state == null) {
167             return;
168         }
169 
170         inflateLayers(r, parser, attrs, theme);
171     }
172 
173     /**
174      * When called before the bound is set, the returned path is identical to
175      * R.string.config_icon_mask. After the bound is set, the
176      * returned path's computed bound is same as the #getBounds().
177      *
178      * @return the mask path object used to clip the drawable
179      */
getIconMask()180     public Path getIconMask() {
181         return mMask;
182     }
183 
184     /**
185      * Returns the foreground drawable managed by this class.
186      *
187      * @return the foreground drawable managed by this drawable
188      */
getForeground()189     public Drawable getForeground() {
190         return mLayerState.mChildren[FOREGROUND_ID].mDrawable;
191     }
192 
193     /**
194      * Returns the foreground drawable managed by this class. The bound of this drawable is
195      * extended by {@link #getExtraInsetFraction()} * getBounds().width on left/right sides and by
196      * {@link #getExtraInsetFraction()} * getBounds().height on top/bottom sides.
197      *
198      * @return the background drawable managed by this drawable
199      */
getBackground()200     public Drawable getBackground() {
201         return mLayerState.mChildren[BACKGROUND_ID].mDrawable;
202     }
203 
204     @Override
onBoundsChange(Rect bounds)205     protected void onBoundsChange(Rect bounds) {
206         if (bounds.isEmpty()) {
207             return;
208         }
209         updateLayerBounds(bounds);
210     }
211 
updateLayerBounds(Rect bounds)212     private void updateLayerBounds(Rect bounds) {
213         if (bounds.isEmpty()) {
214             return;
215         }
216         try {
217             suspendChildInvalidation();
218             updateLayerBoundsInternal(bounds);
219             updateMaskBoundsInternal(bounds);
220         } finally {
221             resumeChildInvalidation();
222         }
223     }
224 
225     /**
226      * Set the child layer bounds bigger than the view port size by {@link #DEFAULT_VIEW_PORT_SCALE}
227      */
updateLayerBoundsInternal(Rect bounds)228     private void updateLayerBoundsInternal(Rect bounds) {
229         int cX = bounds.width() / 2;
230         int cY = bounds.height() / 2;
231 
232         for (int i = 0, count = mLayerState.N_CHILDREN; i < count; i++) {
233             final ChildDrawable r = mLayerState.mChildren[i];
234             if (r == null) {
235                 continue;
236             }
237             final Drawable d = r.mDrawable;
238             if (d == null) {
239                 continue;
240             }
241 
242             int insetWidth = (int) (bounds.width() / (DEFAULT_VIEW_PORT_SCALE * 2));
243             int insetHeight = (int) (bounds.height() / (DEFAULT_VIEW_PORT_SCALE * 2));
244             final Rect outRect = mTmpOutRect;
245             outRect.set(cX - insetWidth, cY - insetHeight, cX + insetWidth, cY + insetHeight);
246 
247             d.setBounds(outRect);
248         }
249     }
250 
updateMaskBoundsInternal(Rect b)251     private void updateMaskBoundsInternal(Rect b) {
252         // reset everything that depends on the view bounds
253         mMaskMatrix.setScale(b.width() / MASK_SIZE, b.height() / MASK_SIZE);
254         mOriginalMask.transform(mMaskMatrix, mMaskScaleOnly);
255 
256         mMaskMatrix.postTranslate(b.left, b.top);
257         mOriginalMask.transform(mMaskMatrix, mMask);
258 
259         if (mLayersBitmap == null || mLayersBitmap.getWidth() != b.width()
260                 || mLayersBitmap.getHeight() != b.height()) {
261             mLayersBitmap = Bitmap.createBitmap(b.width(), b.height(), Bitmap.Config.ARGB_8888);
262         }
263 
264         mPaint.setShader(null);
265         mTransparentRegion.setEmpty();
266         mLayersShader = null;
267     }
268 
269     @Override
draw(Canvas canvas)270     public void draw(Canvas canvas) {
271         if (mLayersBitmap == null) {
272             return;
273         }
274         if (mLayersShader == null) {
275             mCanvas.setBitmap(mLayersBitmap);
276             mCanvas.drawColor(Color.BLACK);
277             for (int i = 0; i < mLayerState.N_CHILDREN; i++) {
278                 if (mLayerState.mChildren[i] == null) {
279                     continue;
280                 }
281                 final Drawable dr = mLayerState.mChildren[i].mDrawable;
282                 if (dr != null) {
283                     dr.draw(mCanvas);
284                 }
285             }
286             mLayersShader = new BitmapShader(mLayersBitmap, TileMode.CLAMP, TileMode.CLAMP);
287             mPaint.setShader(mLayersShader);
288         }
289         if (mMaskScaleOnly != null) {
290             Rect bounds = getBounds();
291             canvas.translate(bounds.left, bounds.top);
292             canvas.drawPath(mMaskScaleOnly, mPaint);
293             canvas.translate(-bounds.left, -bounds.top);
294         }
295     }
296 
297     @Override
invalidateSelf()298     public void invalidateSelf() {
299         mLayersShader = null;
300         super.invalidateSelf();
301     }
302 
303     @Override
getOutline(@onNull Outline outline)304     public void getOutline(@NonNull Outline outline) {
305         outline.setConvexPath(mMask);
306     }
307 
308     @Override
getTransparentRegion()309     public @Nullable Region getTransparentRegion() {
310         if (mTransparentRegion.isEmpty()) {
311             mMask.toggleInverseFillType();
312             mTransparentRegion.set(getBounds());
313             mTransparentRegion.setPath(mMask, mTransparentRegion);
314             mMask.toggleInverseFillType();
315         }
316         return mTransparentRegion;
317     }
318 
319     @Override
applyTheme(@onNull Theme t)320     public void applyTheme(@NonNull Theme t) {
321         super.applyTheme(t);
322 
323         final LayerState state = mLayerState;
324         if (state == null) {
325             return;
326         }
327 
328         final ChildDrawable[] array = state.mChildren;
329         for (int i = 0; i < state.N_CHILDREN; i++) {
330             final ChildDrawable layer = array[i];
331 
332             final Drawable d = layer.mDrawable;
333             if (d != null && d.canApplyTheme()) {
334                 d.applyTheme(t);
335 
336                 // Update cached mask of child changing configurations.
337                 state.mChildrenChangingConfigurations |= d.getChangingConfigurations();
338             }
339         }
340     }
341 
342     /**
343      * Inflates child layers using the specified parser.
344      */
inflateLayers(@onNull Resources r, @NonNull XmlPullParser parser, @NonNull AttributeSet attrs, @Nullable Theme theme)345     private void inflateLayers(@NonNull Resources r, @NonNull XmlPullParser parser,
346             @NonNull AttributeSet attrs, @Nullable Theme theme)
347             throws XmlPullParserException, IOException {
348         final LayerState state = mLayerState;
349 
350         final int innerDepth = parser.getDepth() + 1;
351         int type;
352         int depth;
353         int childIndex = 0;
354         while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
355                 && ((depth = parser.getDepth()) >= innerDepth || type != XmlPullParser.END_TAG)) {
356             if (type != XmlPullParser.START_TAG) {
357                 continue;
358             }
359 
360             if (depth > innerDepth) {
361                 continue;
362             }
363             String tagName = parser.getName();
364             if (tagName.equals("background")) {
365                 childIndex = BACKGROUND_ID;
366             } else if (tagName.equals("foreground")) {
367                 childIndex = FOREGROUND_ID;
368             } else {
369                 continue;
370             }
371 
372             final ChildDrawable layer = new ChildDrawable(state.mDensity);
373 
374             // If the layer doesn't have a drawable or unresolved theme
375             // attribute for a drawable, attempt to parse one from the child
376             // element. If multiple child elements exist, we'll only use the
377             // first one.
378             if (layer.mDrawable == null && (layer.mThemeAttrs == null)) {
379                 while ((type = parser.next()) == XmlPullParser.TEXT) {
380                 }
381                 if (type != XmlPullParser.START_TAG) {
382                     throw new XmlPullParserException(parser.getPositionDescription()
383                             + ": <foreground> or <background> tag requires a 'drawable'"
384                             + "attribute or child tag defining a drawable");
385                 }
386 
387                 // We found a child drawable. Take ownership.
388                 layer.mDrawable = Drawable.createFromXmlInner(r, parser, attrs, theme);
389                 layer.mDrawable.setCallback(this);
390                 state.mChildrenChangingConfigurations |=
391                         layer.mDrawable.getChangingConfigurations();
392             }
393             addLayer(childIndex, layer);
394         }
395     }
396 
397     @Override
canApplyTheme()398     public boolean canApplyTheme() {
399         return (mLayerState != null && mLayerState.canApplyTheme()) || super.canApplyTheme();
400     }
401 
402     @Override
isProjected()403     public boolean isProjected() {
404         if (super.isProjected()) {
405             return true;
406         }
407 
408         final ChildDrawable[] layers = mLayerState.mChildren;
409         for (int i = 0; i < mLayerState.N_CHILDREN; i++) {
410             if (layers[i].mDrawable != null && layers[i].mDrawable.isProjected()) {
411                 return true;
412             }
413         }
414         return false;
415     }
416 
417     /**
418      * Temporarily suspends child invalidation.
419      *
420      * @see #resumeChildInvalidation()
421      */
suspendChildInvalidation()422     private void suspendChildInvalidation() {
423         mSuspendChildInvalidation = true;
424     }
425 
426     /**
427      * Resumes child invalidation after suspension, immediately performing an
428      * invalidation if one was requested by a child during suspension.
429      *
430      * @see #suspendChildInvalidation()
431      */
resumeChildInvalidation()432     private void resumeChildInvalidation() {
433         mSuspendChildInvalidation = false;
434 
435         if (mChildRequestedInvalidation) {
436             mChildRequestedInvalidation = false;
437             invalidateSelf();
438         }
439     }
440 
441     @Override
invalidateDrawable(@onNull Drawable who)442     public void invalidateDrawable(@NonNull Drawable who) {
443         if (mSuspendChildInvalidation) {
444             mChildRequestedInvalidation = true;
445         } else {
446             invalidateSelf();
447         }
448     }
449 
450     @Override
scheduleDrawable(@onNull Drawable who, @NonNull Runnable what, long when)451     public void scheduleDrawable(@NonNull Drawable who, @NonNull Runnable what, long when) {
452         scheduleSelf(what, when);
453     }
454 
455     @Override
unscheduleDrawable(@onNull Drawable who, @NonNull Runnable what)456     public void unscheduleDrawable(@NonNull Drawable who, @NonNull Runnable what) {
457         unscheduleSelf(what);
458     }
459 
460     @Override
getChangingConfigurations()461     public int getChangingConfigurations() {
462         return super.getChangingConfigurations() | mLayerState.getChangingConfigurations();
463     }
464 
465     @Override
setHotspot(float x, float y)466     public void setHotspot(float x, float y) {
467         final ChildDrawable[] array = mLayerState.mChildren;
468         for (int i = 0; i < mLayerState.N_CHILDREN; i++) {
469             final Drawable dr = array[i].mDrawable;
470             if (dr != null) {
471                 dr.setHotspot(x, y);
472             }
473         }
474     }
475 
476     @Override
setHotspotBounds(int left, int top, int right, int bottom)477     public void setHotspotBounds(int left, int top, int right, int bottom) {
478         final ChildDrawable[] array = mLayerState.mChildren;
479         for (int i = 0; i < mLayerState.N_CHILDREN; i++) {
480             final Drawable dr = array[i].mDrawable;
481             if (dr != null) {
482                 dr.setHotspotBounds(left, top, right, bottom);
483             }
484         }
485 
486         if (mHotspotBounds == null) {
487             mHotspotBounds = new Rect(left, top, right, bottom);
488         } else {
489             mHotspotBounds.set(left, top, right, bottom);
490         }
491     }
492 
493     @Override
getHotspotBounds(Rect outRect)494     public void getHotspotBounds(Rect outRect) {
495         if (mHotspotBounds != null) {
496             outRect.set(mHotspotBounds);
497         } else {
498             super.getHotspotBounds(outRect);
499         }
500     }
501 
502     @Override
setVisible(boolean visible, boolean restart)503     public boolean setVisible(boolean visible, boolean restart) {
504         final boolean changed = super.setVisible(visible, restart);
505         final ChildDrawable[] array = mLayerState.mChildren;
506 
507         for (int i = 0; i < mLayerState.N_CHILDREN; i++) {
508             final Drawable dr = array[i].mDrawable;
509             if (dr != null) {
510                 dr.setVisible(visible, restart);
511             }
512         }
513 
514         return changed;
515     }
516 
517     @Override
setDither(boolean dither)518     public void setDither(boolean dither) {
519         final ChildDrawable[] array = mLayerState.mChildren;
520         for (int i = 0; i < mLayerState.N_CHILDREN; i++) {
521             final Drawable dr = array[i].mDrawable;
522             if (dr != null) {
523                 dr.setDither(dither);
524             }
525         }
526     }
527 
528     @Override
setAlpha(int alpha)529     public void setAlpha(int alpha) {
530         mPaint.setAlpha(alpha);
531     }
532 
533     @Override
getAlpha()534     public int getAlpha() {
535         return mPaint.getAlpha();
536     }
537 
538     @Override
setColorFilter(ColorFilter colorFilter)539     public void setColorFilter(ColorFilter colorFilter) {
540         final ChildDrawable[] array = mLayerState.mChildren;
541         for (int i = 0; i < mLayerState.N_CHILDREN; i++) {
542             final Drawable dr = array[i].mDrawable;
543             if (dr != null) {
544                 dr.setColorFilter(colorFilter);
545             }
546         }
547     }
548 
549     @Override
setTintList(ColorStateList tint)550     public void setTintList(ColorStateList tint) {
551         final ChildDrawable[] array = mLayerState.mChildren;
552         final int N = mLayerState.N_CHILDREN;
553         for (int i = 0; i < N; i++) {
554             final Drawable dr = array[i].mDrawable;
555             if (dr != null) {
556                 dr.setTintList(tint);
557             }
558         }
559     }
560 
561     @Override
setTintMode(Mode tintMode)562     public void setTintMode(Mode tintMode) {
563         final ChildDrawable[] array = mLayerState.mChildren;
564         final int N = mLayerState.N_CHILDREN;
565         for (int i = 0; i < N; i++) {
566             final Drawable dr = array[i].mDrawable;
567             if (dr != null) {
568                 dr.setTintMode(tintMode);
569             }
570         }
571     }
572 
setOpacity(int opacity)573     public void setOpacity(int opacity) {
574         mLayerState.mOpacityOverride = opacity;
575     }
576 
577     @Override
getOpacity()578     public int getOpacity() {
579         return PixelFormat.TRANSLUCENT;
580     }
581 
582     @Override
setAutoMirrored(boolean mirrored)583     public void setAutoMirrored(boolean mirrored) {
584         mLayerState.mAutoMirrored = mirrored;
585 
586         final ChildDrawable[] array = mLayerState.mChildren;
587         for (int i = 0; i < mLayerState.N_CHILDREN; i++) {
588             final Drawable dr = array[i].mDrawable;
589             if (dr != null) {
590                 dr.setAutoMirrored(mirrored);
591             }
592         }
593     }
594 
595     @Override
isAutoMirrored()596     public boolean isAutoMirrored() {
597         return mLayerState.mAutoMirrored;
598     }
599 
600     @Override
jumpToCurrentState()601     public void jumpToCurrentState() {
602         final ChildDrawable[] array = mLayerState.mChildren;
603         for (int i = 0; i < mLayerState.N_CHILDREN; i++) {
604             final Drawable dr = array[i].mDrawable;
605             if (dr != null) {
606                 dr.jumpToCurrentState();
607             }
608         }
609     }
610 
611     @Override
isStateful()612     public boolean isStateful() {
613         return mLayerState.isStateful();
614     }
615 
616     @Override
onStateChange(int[] state)617     protected boolean onStateChange(int[] state) {
618         boolean changed = false;
619 
620         final ChildDrawable[] array = mLayerState.mChildren;
621         for (int i = 0; i < mLayerState.N_CHILDREN; i++) {
622             final Drawable dr = array[i].mDrawable;
623             if (dr != null && dr.isStateful() && dr.setState(state)) {
624                 changed = true;
625             }
626         }
627 
628         if (changed) {
629             updateLayerBounds(getBounds());
630         }
631 
632         return changed;
633     }
634 
635     @Override
onLevelChange(int level)636     protected boolean onLevelChange(int level) {
637         boolean changed = false;
638 
639         final ChildDrawable[] array = mLayerState.mChildren;
640         for (int i = 0; i < mLayerState.N_CHILDREN; i++) {
641             final Drawable dr = array[i].mDrawable;
642             if (dr != null && dr.setLevel(level)) {
643                 changed = true;
644             }
645         }
646 
647         if (changed) {
648             updateLayerBounds(getBounds());
649         }
650 
651         return changed;
652     }
653 
654     @Override
getIntrinsicWidth()655     public int getIntrinsicWidth() {
656         return (int)(getMaxIntrinsicWidth() * DEFAULT_VIEW_PORT_SCALE);
657     }
658 
getMaxIntrinsicWidth()659     private int getMaxIntrinsicWidth() {
660         int width = -1;
661         for (int i = 0; i < mLayerState.N_CHILDREN; i++) {
662             final ChildDrawable r = mLayerState.mChildren[i];
663             if (r.mDrawable == null) {
664                 continue;
665             }
666             final int w = r.mDrawable.getIntrinsicWidth();
667             if (w > width) {
668                 width = w;
669             }
670         }
671         return width;
672     }
673 
674     @Override
getIntrinsicHeight()675     public int getIntrinsicHeight() {
676         return (int)(getMaxIntrinsicHeight() * DEFAULT_VIEW_PORT_SCALE);
677     }
678 
getMaxIntrinsicHeight()679     private int getMaxIntrinsicHeight() {
680         int height = -1;
681         for (int i = 0; i < mLayerState.N_CHILDREN; i++) {
682             final ChildDrawable r = mLayerState.mChildren[i];
683             if (r.mDrawable == null) {
684                 continue;
685             }
686             final int h = r.mDrawable.getIntrinsicHeight();
687             if (h > height) {
688                 height = h;
689             }
690         }
691         return height;
692     }
693 
694     @Override
getConstantState()695     public ConstantState getConstantState() {
696         if (mLayerState.canConstantState()) {
697             mLayerState.mChangingConfigurations = getChangingConfigurations();
698             return mLayerState;
699         }
700         return null;
701     }
702 
703     @Override
mutate()704     public Drawable mutate() {
705         if (!mMutated && super.mutate() == this) {
706             mLayerState = createConstantState(mLayerState, null);
707             for (int i = 0; i < mLayerState.N_CHILDREN; i++) {
708                 final Drawable dr = mLayerState.mChildren[i].mDrawable;
709                 if (dr != null) {
710                     dr.mutate();
711                 }
712             }
713             mMutated = true;
714         }
715         return this;
716     }
717 
718     static class ChildDrawable {
719         public Drawable mDrawable;
720         public int[] mThemeAttrs;
721         public int mDensity = DisplayMetrics.DENSITY_DEFAULT;
722 
ChildDrawable(int density)723         ChildDrawable(int density) {
724             mDensity = density;
725         }
726 
ChildDrawable(@onNull ChildDrawable orig, @NonNull DynamicAdaptiveIconDrawable owner, @Nullable Resources res)727         ChildDrawable(@NonNull ChildDrawable orig, @NonNull DynamicAdaptiveIconDrawable owner,
728                 @Nullable Resources res) {
729 
730             final Drawable dr = orig.mDrawable;
731             final Drawable clone;
732             if (dr != null) {
733                 final ConstantState cs = dr.getConstantState();
734                 if (cs == null) {
735                     clone = dr;
736                 } else if (res != null) {
737                     clone = cs.newDrawable(res);
738                 } else {
739                     clone = cs.newDrawable();
740                 }
741                 clone.setCallback(owner);
742                 clone.setBounds(dr.getBounds());
743                 clone.setLevel(dr.getLevel());
744             } else {
745                 clone = null;
746             }
747 
748             mDrawable = clone;
749             mThemeAttrs = orig.mThemeAttrs;
750         }
751 
canApplyTheme()752         public boolean canApplyTheme() {
753             return mThemeAttrs != null
754                     || (mDrawable != null && mDrawable.canApplyTheme());
755         }
756 
setDensity(int targetDensity)757         public final void setDensity(int targetDensity) {
758             if (mDensity != targetDensity) {
759                 mDensity = targetDensity;
760             }
761         }
762     }
763 
764     static class LayerState extends ConstantState {
765         private final DynamicAdaptiveIconDrawable mOwner;
766 
767         private int[] mThemeAttrs;
768 
769         final static int N_CHILDREN = 2;
770         ChildDrawable[] mChildren;
771 
772         // The density at which to render the drawable and its children.
773         int mDensity;
774 
775         // The density to use when inflating/looking up the children drawables. A value of 0 means
776         // use the system's density.
777         int mSrcDensityOverride = 0;
778 
779         int mOpacityOverride = PixelFormat.UNKNOWN;
780 
781         int mChangingConfigurations;
782         int mChildrenChangingConfigurations;
783 
784         private boolean mCheckedOpacity;
785         private int mOpacity;
786 
787         private boolean mCheckedStateful;
788         private boolean mIsStateful;
789         private boolean mAutoMirrored = false;
790 
LayerState(@ullable LayerState orig, @NonNull DynamicAdaptiveIconDrawable owner, @Nullable Resources res)791         LayerState(@Nullable LayerState orig, @NonNull DynamicAdaptiveIconDrawable owner,
792                 @Nullable Resources res) {
793             mOwner = owner;
794             mChildren = new ChildDrawable[N_CHILDREN];
795             if (orig != null) {
796                 final ChildDrawable[] origChildDrawable = orig.mChildren;
797 
798                 mChangingConfigurations = orig.mChangingConfigurations;
799                 mChildrenChangingConfigurations = orig.mChildrenChangingConfigurations;
800 
801                 for (int i = 0; i < N_CHILDREN; i++) {
802                     final ChildDrawable or = origChildDrawable[i];
803                     mChildren[i] = new ChildDrawable(or, mOwner, res);
804                 }
805 
806                 mCheckedOpacity = orig.mCheckedOpacity;
807                 mOpacity = orig.mOpacity;
808                 mCheckedStateful = orig.mCheckedStateful;
809                 mIsStateful = orig.mIsStateful;
810                 mAutoMirrored = orig.mAutoMirrored;
811                 mThemeAttrs = orig.mThemeAttrs;
812                 mOpacityOverride = orig.mOpacityOverride;
813                 mSrcDensityOverride = orig.mSrcDensityOverride;
814             } else {
815                 for (int i = 0; i < N_CHILDREN; i++) {
816                     mChildren[i] = new ChildDrawable(mDensity);
817                 }
818             }
819         }
820 
setDensity(int targetDensity)821         public final void setDensity(int targetDensity) {
822             if (mDensity != targetDensity) {
823                 mDensity = targetDensity;
824             }
825         }
826 
827         @Override
canApplyTheme()828         public boolean canApplyTheme() {
829             if (mThemeAttrs != null || super.canApplyTheme()) {
830                 return true;
831             }
832 
833             final ChildDrawable[] array = mChildren;
834             for (int i = 0; i < N_CHILDREN; i++) {
835                 final ChildDrawable layer = array[i];
836                 if (layer.canApplyTheme()) {
837                     return true;
838                 }
839             }
840             return false;
841         }
842 
843         @Override
newDrawable()844         public Drawable newDrawable() {
845             return new DynamicAdaptiveIconDrawable(mOwner.getBackground(), mOwner.getForeground(),
846                     mOwner.mOriginalMask);
847         }
848 
849         @Override
newDrawable(@ullable Resources res)850         public Drawable newDrawable(@Nullable Resources res) {
851             return newDrawable();
852         }
853 
854         @Override
getChangingConfigurations()855         public int getChangingConfigurations() {
856             return mChangingConfigurations
857                     | mChildrenChangingConfigurations;
858         }
859 
getOpacity()860         public final int getOpacity() {
861             if (mCheckedOpacity) {
862                 return mOpacity;
863             }
864 
865             final ChildDrawable[] array = mChildren;
866 
867             // Seek to the first non-null drawable.
868             int firstIndex = -1;
869             for (int i = 0; i < N_CHILDREN; i++) {
870                 if (array[i].mDrawable != null) {
871                     firstIndex = i;
872                     break;
873                 }
874             }
875 
876             int op;
877             if (firstIndex >= 0) {
878                 op = array[firstIndex].mDrawable.getOpacity();
879             } else {
880                 op = PixelFormat.TRANSPARENT;
881             }
882 
883             // Merge all remaining non-null drawables.
884             for (int i = firstIndex + 1; i < N_CHILDREN; i++) {
885                 final Drawable dr = array[i].mDrawable;
886                 if (dr != null) {
887                     op = Drawable.resolveOpacity(op, dr.getOpacity());
888                 }
889             }
890 
891             mOpacity = op;
892             mCheckedOpacity = true;
893             return op;
894         }
895 
isStateful()896         public final boolean isStateful() {
897             if (mCheckedStateful) {
898                 return mIsStateful;
899             }
900 
901             final ChildDrawable[] array = mChildren;
902             boolean isStateful = false;
903             for (int i = 0; i < N_CHILDREN; i++) {
904                 final Drawable dr = array[i].mDrawable;
905                 if (dr != null && dr.isStateful()) {
906                     isStateful = true;
907                     break;
908                 }
909             }
910 
911             mIsStateful = isStateful;
912             mCheckedStateful = true;
913             return isStateful;
914         }
915 
canConstantState()916         public final boolean canConstantState() {
917             final ChildDrawable[] array = mChildren;
918             for (int i = 0; i < N_CHILDREN; i++) {
919                 final Drawable dr = array[i].mDrawable;
920                 if (dr != null && dr.getConstantState() == null) {
921                     return false;
922                 }
923             }
924 
925             // Don't cache the result, this method is not called very often.
926             return true;
927         }
928 
invalidateCache()929         public void invalidateCache() {
930             mCheckedOpacity = false;
931             mCheckedStateful = false;
932         }
933     }
934 }