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