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