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