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