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