1 /*
2  * Copyright (C) 2017 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package android.graphics.drawable;
18 
19 import android.annotation.NonNull;
20 import android.annotation.Nullable;
21 import android.annotation.TestApi;
22 import android.content.pm.ActivityInfo.Config;
23 import android.content.res.ColorStateList;
24 import android.content.res.Resources;
25 import android.content.res.Resources.Theme;
26 import android.content.res.TypedArray;
27 import android.graphics.Bitmap;
28 import android.graphics.BitmapShader;
29 import android.graphics.Canvas;
30 import android.graphics.Color;
31 import android.graphics.ColorFilter;
32 import android.graphics.Matrix;
33 import android.graphics.Outline;
34 import android.graphics.Paint;
35 import android.graphics.Path;
36 import android.graphics.PixelFormat;
37 import android.graphics.PorterDuff.Mode;
38 import android.graphics.Rect;
39 import android.graphics.Region;
40 import android.graphics.Shader;
41 import android.graphics.Shader.TileMode;
42 import android.util.AttributeSet;
43 import android.util.DisplayMetrics;
44 import android.util.PathParser;
45 
46 import com.android.internal.R;
47 
48 import org.xmlpull.v1.XmlPullParser;
49 import org.xmlpull.v1.XmlPullParserException;
50 
51 import java.io.IOException;
52 
53 /**
54  * <p>This class can also be created via XML inflation using <code>&lt;adaptive-icon></code> tag
55  * in addition to dynamic creation.
56  *
57  * <p>This drawable supports two drawable layers: foreground and background. The layers are clipped
58  * when rendering using the mask defined in the device configuration.
59  *
60  * <ul>
61  * <li>Both foreground and background layers should be sized at 108 x 108 dp.</li>
62  * <li>The inner 72 x 72 dp  of the icon appears within the masked viewport.</li>
63  * <li>The outer 18 dp on each of the 4 sides of the layers is reserved for use by the system UI
64  * surfaces to create interesting visual effects, such as parallax or pulsing.</li>
65  * </ul>
66  *
67  * Such motion effect is achieved by internally setting the bounds of the foreground and
68  * background layer as following:
69  * <pre>
70  * Rect(getBounds().left - getBounds().getWidth() * #getExtraInsetFraction(),
71  *      getBounds().top - getBounds().getHeight() * #getExtraInsetFraction(),
72  *      getBounds().right + getBounds().getWidth() * #getExtraInsetFraction(),
73  *      getBounds().bottom + getBounds().getHeight() * #getExtraInsetFraction())
74  * </pre>
75  */
76 public class AdaptiveIconDrawable extends Drawable implements Drawable.Callback {
77 
78     /**
79      * Mask path is defined inside device configuration in following dimension: [100 x 100]
80      * @hide
81      */
82     @TestApi
83     public static final float MASK_SIZE = 100f;
84 
85     /**
86      * Launcher icons design guideline
87      */
88     private static final float SAFEZONE_SCALE = 66f/72f;
89 
90     /**
91      * All four sides of the layers are padded with extra inset so as to provide
92      * extra content to reveal within the clip path when performing affine transformations on the
93      * layers.
94      *
95      * Each layers will reserve 25% of it's width and height.
96      *
97      * As a result, the view port of the layers is smaller than their intrinsic width and height.
98      */
99     private static final float EXTRA_INSET_PERCENTAGE = 1 / 4f;
100     private static final float DEFAULT_VIEW_PORT_SCALE = 1f / (1 + 2 * EXTRA_INSET_PERCENTAGE);
101 
102     /**
103      * Clip path defined in R.string.config_icon_mask.
104      */
105     private static Path sMask;
106 
107     /**
108      * Scaled mask based on the view bounds.
109      */
110     private final Path mMask;
111     private final Matrix mMaskMatrix;
112     private final Region mTransparentRegion;
113 
114     private Bitmap mMaskBitmap;
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 
155         if (sMask == null) {
156             sMask = PathParser.createPathFromPathData(
157                 Resources.getSystem().getString(R.string.config_icon_mask));
158         }
159         mMask = PathParser.createPathFromPathData(
160             Resources.getSystem().getString(R.string.config_icon_mask));
161         mMaskMatrix = new Matrix();
162         mCanvas = new Canvas();
163         mTransparentRegion = new Region();
164     }
165 
createChildDrawable(Drawable drawable)166     private ChildDrawable createChildDrawable(Drawable drawable) {
167         final ChildDrawable layer = new ChildDrawable(mLayerState.mDensity);
168         layer.mDrawable = drawable;
169         layer.mDrawable.setCallback(this);
170         mLayerState.mChildrenChangingConfigurations |=
171             layer.mDrawable.getChangingConfigurations();
172         return layer;
173     }
174 
createConstantState(@ullable LayerState state, @Nullable Resources res)175     LayerState createConstantState(@Nullable LayerState state, @Nullable Resources res) {
176         return new LayerState(state, this, res);
177     }
178 
179     /**
180      * Constructor used to dynamically create this drawable.
181      *
182      * @param backgroundDrawable drawable that should be rendered in the background
183      * @param foregroundDrawable drawable that should be rendered in the foreground
184      */
AdaptiveIconDrawable(Drawable backgroundDrawable, Drawable foregroundDrawable)185     public AdaptiveIconDrawable(Drawable backgroundDrawable,
186             Drawable foregroundDrawable) {
187         this((LayerState)null, null);
188         if (backgroundDrawable != null) {
189             addLayer(BACKGROUND_ID, createChildDrawable(backgroundDrawable));
190         }
191         if (foregroundDrawable != null) {
192             addLayer(FOREGROUND_ID, createChildDrawable(foregroundDrawable));
193         }
194     }
195 
196     /**
197      * Sets the layer to the {@param index} and invalidates cache.
198      *
199      * @param index The index of the layer.
200      * @param layer The layer to add.
201      */
addLayer(int index, @NonNull ChildDrawable layer)202     private void addLayer(int index, @NonNull ChildDrawable layer) {
203         mLayerState.mChildren[index] = layer;
204         mLayerState.invalidateCache();
205     }
206 
207     @Override
inflate(@onNull Resources r, @NonNull XmlPullParser parser, @NonNull AttributeSet attrs, @Nullable Theme theme)208     public void inflate(@NonNull Resources r, @NonNull XmlPullParser parser,
209             @NonNull AttributeSet attrs, @Nullable Theme theme)
210             throws XmlPullParserException, IOException {
211         super.inflate(r, parser, attrs, theme);
212 
213         final LayerState state = mLayerState;
214         if (state == null) {
215             return;
216         }
217 
218         // The density may have changed since the last update. This will
219         // apply scaling to any existing constant state properties.
220         final int deviceDensity = Drawable.resolveDensity(r, 0);
221         state.setDensity(deviceDensity);
222         state.mSrcDensityOverride = mSrcDensityOverride;
223 
224         final ChildDrawable[] array = state.mChildren;
225         for (int i = 0; i < state.mChildren.length; i++) {
226             final ChildDrawable layer = array[i];
227             layer.setDensity(deviceDensity);
228         }
229 
230         inflateLayers(r, parser, attrs, theme);
231     }
232 
233     /**
234      * All four sides of the layers are padded with extra inset so as to provide
235      * extra content to reveal within the clip path when performing affine transformations on the
236      * layers.
237      *
238      * @see #getForeground() and #getBackground() for more info on how this value is used
239      */
getExtraInsetFraction()240     public static float getExtraInsetFraction() {
241         return EXTRA_INSET_PERCENTAGE;
242     }
243 
244     /**
245      * @hide
246      */
getExtraInsetPercentage()247     public static float getExtraInsetPercentage() {
248         return EXTRA_INSET_PERCENTAGE;
249     }
250 
251     /**
252      * When called before the bound is set, the returned path is identical to
253      * R.string.config_icon_mask. After the bound is set, the
254      * returned path's computed bound is same as the #getBounds().
255      *
256      * @return the mask path object used to clip the drawable
257      */
getIconMask()258     public Path getIconMask() {
259         return mMask;
260     }
261 
262     /**
263      * Returns the foreground drawable managed by this class. The bound of this drawable is
264      * extended by {@link #getExtraInsetFraction()} * getBounds().width on left/right sides and by
265      * {@link #getExtraInsetFraction()} * getBounds().height on top/bottom sides.
266      *
267      * @return the foreground drawable managed by this drawable
268      */
getForeground()269     public Drawable getForeground() {
270         return mLayerState.mChildren[FOREGROUND_ID].mDrawable;
271     }
272 
273     /**
274      * Returns the foreground drawable managed by this class. The bound of this drawable is
275      * extended by {@link #getExtraInsetFraction()} * getBounds().width on left/right sides and by
276      * {@link #getExtraInsetFraction()} * getBounds().height on top/bottom sides.
277      *
278      * @return the background drawable managed by this drawable
279      */
getBackground()280     public Drawable getBackground() {
281         return mLayerState.mChildren[BACKGROUND_ID].mDrawable;
282     }
283 
284     @Override
onBoundsChange(Rect bounds)285     protected void onBoundsChange(Rect bounds) {
286         if (bounds.isEmpty()) {
287             return;
288         }
289         updateLayerBounds(bounds);
290     }
291 
updateLayerBounds(Rect bounds)292     private void updateLayerBounds(Rect bounds) {
293         if (bounds.isEmpty()) {
294             return;
295         }
296         try {
297             suspendChildInvalidation();
298             updateLayerBoundsInternal(bounds);
299             updateMaskBoundsInternal(bounds);
300         } finally {
301             resumeChildInvalidation();
302         }
303     }
304 
305     /**
306      * Set the child layer bounds bigger than the view port size by {@link #DEFAULT_VIEW_PORT_SCALE}
307      */
updateLayerBoundsInternal(Rect bounds)308     private void updateLayerBoundsInternal(Rect bounds) {
309         int cX = bounds.width() / 2;
310         int cY = bounds.height() / 2;
311 
312         for (int i = 0, count = mLayerState.N_CHILDREN; i < count; i++) {
313             final ChildDrawable r = mLayerState.mChildren[i];
314             if (r == null) {
315                 continue;
316             }
317             final Drawable d = r.mDrawable;
318             if (d == null) {
319                 continue;
320             }
321 
322             int insetWidth = (int) (bounds.width() / (DEFAULT_VIEW_PORT_SCALE * 2));
323             int insetHeight = (int) (bounds.height() / (DEFAULT_VIEW_PORT_SCALE * 2));
324             final Rect outRect = mTmpOutRect;
325             outRect.set(cX - insetWidth, cY - insetHeight, cX + insetWidth, cY + insetHeight);
326 
327             d.setBounds(outRect);
328         }
329     }
330 
updateMaskBoundsInternal(Rect b)331     private void updateMaskBoundsInternal(Rect b) {
332         mMaskMatrix.setScale(b.width() / MASK_SIZE, b.height() / MASK_SIZE);
333         sMask.transform(mMaskMatrix, mMask);
334 
335         if (mMaskBitmap == null || mMaskBitmap.getWidth() != b.width() ||
336             mMaskBitmap.getHeight() != b.height()) {
337             mMaskBitmap = Bitmap.createBitmap(b.width(), b.height(), Bitmap.Config.ALPHA_8);
338             mLayersBitmap = Bitmap.createBitmap(b.width(), b.height(), Bitmap.Config.ARGB_8888);
339         }
340         // mMaskBitmap bound [0, w] x [0, h]
341         mCanvas.setBitmap(mMaskBitmap);
342         mPaint.setShader(null);
343         mCanvas.drawPath(mMask, mPaint);
344 
345         // mMask bound [left, top, right, bottom]
346         mMaskMatrix.postTranslate(b.left, b.top);
347         mMask.reset();
348         sMask.transform(mMaskMatrix, mMask);
349         // reset everything that depends on the view bounds
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 (mMaskBitmap != null) {
375             Rect bounds = getBounds();
376             canvas.drawBitmap(mMaskBitmap, bounds.left, bounds.top, mPaint);
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.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 PixelFormat.TRANSLUCENT;
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
setTintMode(Mode tintMode)704     public void setTintMode(Mode tintMode) {
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.setTintMode(tintMode);
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         if (mLayerState.mOpacityOverride != PixelFormat.UNKNOWN) {
722             return mLayerState.mOpacityOverride;
723         }
724         return mLayerState.getOpacity();
725     }
726 
727     @Override
setAutoMirrored(boolean mirrored)728     public void setAutoMirrored(boolean mirrored) {
729         mLayerState.mAutoMirrored = mirrored;
730 
731         final ChildDrawable[] array = mLayerState.mChildren;
732         for (int i = 0; i < mLayerState.N_CHILDREN; i++) {
733             final Drawable dr = array[i].mDrawable;
734             if (dr != null) {
735                 dr.setAutoMirrored(mirrored);
736             }
737         }
738     }
739 
740     @Override
isAutoMirrored()741     public boolean isAutoMirrored() {
742         return mLayerState.mAutoMirrored;
743     }
744 
745     @Override
jumpToCurrentState()746     public void jumpToCurrentState() {
747         final ChildDrawable[] array = mLayerState.mChildren;
748         for (int i = 0; i < mLayerState.N_CHILDREN; i++) {
749             final Drawable dr = array[i].mDrawable;
750             if (dr != null) {
751                 dr.jumpToCurrentState();
752             }
753         }
754     }
755 
756     @Override
isStateful()757     public boolean isStateful() {
758         return mLayerState.isStateful();
759     }
760 
761     /** @hide */
762     @Override
hasFocusStateSpecified()763     public boolean hasFocusStateSpecified() {
764         return mLayerState.hasFocusStateSpecified();
765     }
766 
767     @Override
onStateChange(int[] state)768     protected boolean onStateChange(int[] state) {
769         boolean changed = false;
770 
771         final ChildDrawable[] array = mLayerState.mChildren;
772         for (int i = 0; i < mLayerState.N_CHILDREN; i++) {
773             final Drawable dr = array[i].mDrawable;
774             if (dr != null && dr.isStateful() && dr.setState(state)) {
775                 changed = true;
776             }
777         }
778 
779         if (changed) {
780             updateLayerBounds(getBounds());
781         }
782 
783         return changed;
784     }
785 
786     @Override
onLevelChange(int level)787     protected boolean onLevelChange(int level) {
788         boolean changed = false;
789 
790         final ChildDrawable[] array = mLayerState.mChildren;
791         for (int i = 0; i < mLayerState.N_CHILDREN; i++) {
792             final Drawable dr = array[i].mDrawable;
793             if (dr != null && dr.setLevel(level)) {
794                 changed = true;
795             }
796         }
797 
798         if (changed) {
799             updateLayerBounds(getBounds());
800         }
801 
802         return changed;
803     }
804 
805     @Override
getIntrinsicWidth()806     public int getIntrinsicWidth() {
807         return (int)(getMaxIntrinsicWidth() * DEFAULT_VIEW_PORT_SCALE);
808     }
809 
getMaxIntrinsicWidth()810     private int getMaxIntrinsicWidth() {
811         int width = -1;
812         for (int i = 0; i < mLayerState.N_CHILDREN; i++) {
813             final ChildDrawable r = mLayerState.mChildren[i];
814             if (r.mDrawable == null) {
815                 continue;
816             }
817             final int w = r.mDrawable.getIntrinsicWidth();
818             if (w > width) {
819                 width = w;
820             }
821         }
822         return width;
823     }
824 
825     @Override
getIntrinsicHeight()826     public int getIntrinsicHeight() {
827         return (int)(getMaxIntrinsicHeight() * DEFAULT_VIEW_PORT_SCALE);
828     }
829 
getMaxIntrinsicHeight()830     private int getMaxIntrinsicHeight() {
831         int height = -1;
832         for (int i = 0; i < mLayerState.N_CHILDREN; i++) {
833             final ChildDrawable r = mLayerState.mChildren[i];
834             if (r.mDrawable == null) {
835                 continue;
836             }
837             final int h = r.mDrawable.getIntrinsicHeight();
838             if (h > height) {
839                 height = h;
840             }
841         }
842         return height;
843     }
844 
845     @Override
getConstantState()846     public ConstantState getConstantState() {
847         if (mLayerState.canConstantState()) {
848             mLayerState.mChangingConfigurations = getChangingConfigurations();
849             return mLayerState;
850         }
851         return null;
852     }
853 
854     @Override
mutate()855     public Drawable mutate() {
856         if (!mMutated && super.mutate() == this) {
857             mLayerState = createConstantState(mLayerState, null);
858             for (int i = 0; i < mLayerState.N_CHILDREN; i++) {
859                 final Drawable dr = mLayerState.mChildren[i].mDrawable;
860                 if (dr != null) {
861                     dr.mutate();
862                 }
863             }
864             mMutated = true;
865         }
866         return this;
867     }
868 
869     /**
870      * @hide
871      */
clearMutated()872     public void clearMutated() {
873         super.clearMutated();
874         final ChildDrawable[] array = mLayerState.mChildren;
875         for (int i = 0; i < mLayerState.N_CHILDREN; i++) {
876             final Drawable dr = array[i].mDrawable;
877             if (dr != null) {
878                 dr.clearMutated();
879             }
880         }
881         mMutated = false;
882     }
883 
884     static class ChildDrawable {
885         public Drawable mDrawable;
886         public int[] mThemeAttrs;
887         public int mDensity = DisplayMetrics.DENSITY_DEFAULT;
888 
ChildDrawable(int density)889         ChildDrawable(int density) {
890             mDensity = density;
891         }
892 
ChildDrawable(@onNull ChildDrawable orig, @NonNull AdaptiveIconDrawable owner, @Nullable Resources res)893         ChildDrawable(@NonNull ChildDrawable orig, @NonNull AdaptiveIconDrawable owner,
894                 @Nullable Resources res) {
895 
896             final Drawable dr = orig.mDrawable;
897             final Drawable clone;
898             if (dr != null) {
899                 final ConstantState cs = dr.getConstantState();
900                 if (cs == null) {
901                     clone = dr;
902                 } else if (res != null) {
903                     clone = cs.newDrawable(res);
904                 } else {
905                     clone = cs.newDrawable();
906                 }
907                 clone.setCallback(owner);
908                 clone.setBounds(dr.getBounds());
909                 clone.setLevel(dr.getLevel());
910             } else {
911                 clone = null;
912             }
913 
914             mDrawable = clone;
915             mThemeAttrs = orig.mThemeAttrs;
916 
917             mDensity = Drawable.resolveDensity(res, orig.mDensity);
918         }
919 
canApplyTheme()920         public boolean canApplyTheme() {
921             return mThemeAttrs != null
922                     || (mDrawable != null && mDrawable.canApplyTheme());
923         }
924 
setDensity(int targetDensity)925         public final void setDensity(int targetDensity) {
926             if (mDensity != targetDensity) {
927                 mDensity = targetDensity;
928             }
929         }
930     }
931 
932     static class LayerState extends ConstantState {
933         private int[] mThemeAttrs;
934 
935         final static int N_CHILDREN = 2;
936         ChildDrawable[] mChildren;
937 
938         // The density at which to render the drawable and its children.
939         int mDensity;
940 
941         // The density to use when inflating/looking up the children drawables. A value of 0 means
942         // use the system's density.
943         int mSrcDensityOverride = 0;
944 
945         int mOpacityOverride = PixelFormat.UNKNOWN;
946 
947         @Config int mChangingConfigurations;
948         @Config int mChildrenChangingConfigurations;
949 
950         private boolean mCheckedOpacity;
951         private int mOpacity;
952 
953         private boolean mCheckedStateful;
954         private boolean mIsStateful;
955         private boolean mAutoMirrored = false;
956 
LayerState(@ullable LayerState orig, @NonNull AdaptiveIconDrawable owner, @Nullable Resources res)957         LayerState(@Nullable LayerState orig, @NonNull AdaptiveIconDrawable owner,
958                 @Nullable Resources res) {
959             mDensity = Drawable.resolveDensity(res, orig != null ? orig.mDensity : 0);
960             mChildren = new ChildDrawable[N_CHILDREN];
961             if (orig != null) {
962                 final ChildDrawable[] origChildDrawable = orig.mChildren;
963 
964                 mChangingConfigurations = orig.mChangingConfigurations;
965                 mChildrenChangingConfigurations = orig.mChildrenChangingConfigurations;
966 
967                 for (int i = 0; i < N_CHILDREN; i++) {
968                     final ChildDrawable or = origChildDrawable[i];
969                     mChildren[i] = new ChildDrawable(or, owner, res);
970                 }
971 
972                 mCheckedOpacity = orig.mCheckedOpacity;
973                 mOpacity = orig.mOpacity;
974                 mCheckedStateful = orig.mCheckedStateful;
975                 mIsStateful = orig.mIsStateful;
976                 mAutoMirrored = orig.mAutoMirrored;
977                 mThemeAttrs = orig.mThemeAttrs;
978                 mOpacityOverride = orig.mOpacityOverride;
979                 mSrcDensityOverride = orig.mSrcDensityOverride;
980             } else {
981                 for (int i = 0; i < N_CHILDREN; i++) {
982                     mChildren[i] = new ChildDrawable(mDensity);
983                 }
984             }
985         }
986 
setDensity(int targetDensity)987         public final void setDensity(int targetDensity) {
988             if (mDensity != targetDensity) {
989                 mDensity = targetDensity;
990             }
991         }
992 
993         @Override
canApplyTheme()994         public boolean canApplyTheme() {
995             if (mThemeAttrs != null || super.canApplyTheme()) {
996                 return true;
997             }
998 
999             final ChildDrawable[] array = mChildren;
1000             for (int i = 0; i < N_CHILDREN; i++) {
1001                 final ChildDrawable layer = array[i];
1002                 if (layer.canApplyTheme()) {
1003                     return true;
1004                 }
1005             }
1006             return false;
1007         }
1008 
1009         @Override
newDrawable()1010         public Drawable newDrawable() {
1011             return new AdaptiveIconDrawable(this, null);
1012         }
1013 
1014         @Override
newDrawable(@ullable Resources res)1015         public Drawable newDrawable(@Nullable Resources res) {
1016             return new AdaptiveIconDrawable(this, res);
1017         }
1018 
1019         @Override
getChangingConfigurations()1020         public @Config int getChangingConfigurations() {
1021             return mChangingConfigurations
1022                     | mChildrenChangingConfigurations;
1023         }
1024 
getOpacity()1025         public final int getOpacity() {
1026             if (mCheckedOpacity) {
1027                 return mOpacity;
1028             }
1029 
1030             final ChildDrawable[] array = mChildren;
1031 
1032             // Seek to the first non-null drawable.
1033             int firstIndex = -1;
1034             for (int i = 0; i < N_CHILDREN; i++) {
1035                 if (array[i].mDrawable != null) {
1036                     firstIndex = i;
1037                     break;
1038                 }
1039             }
1040 
1041             int op;
1042             if (firstIndex >= 0) {
1043                 op = array[firstIndex].mDrawable.getOpacity();
1044             } else {
1045                 op = PixelFormat.TRANSPARENT;
1046             }
1047 
1048             // Merge all remaining non-null drawables.
1049             for (int i = firstIndex + 1; i < N_CHILDREN; i++) {
1050                 final Drawable dr = array[i].mDrawable;
1051                 if (dr != null) {
1052                     op = Drawable.resolveOpacity(op, dr.getOpacity());
1053                 }
1054             }
1055 
1056             mOpacity = op;
1057             mCheckedOpacity = true;
1058             return op;
1059         }
1060 
isStateful()1061         public final boolean isStateful() {
1062             if (mCheckedStateful) {
1063                 return mIsStateful;
1064             }
1065 
1066             final ChildDrawable[] array = mChildren;
1067             boolean isStateful = false;
1068             for (int i = 0; i < N_CHILDREN; i++) {
1069                 final Drawable dr = array[i].mDrawable;
1070                 if (dr != null && dr.isStateful()) {
1071                     isStateful = true;
1072                     break;
1073                 }
1074             }
1075 
1076             mIsStateful = isStateful;
1077             mCheckedStateful = true;
1078             return isStateful;
1079         }
1080 
hasFocusStateSpecified()1081         public final boolean hasFocusStateSpecified() {
1082             final ChildDrawable[] array = mChildren;
1083             for (int i = 0; i < N_CHILDREN; i++) {
1084                 final Drawable dr = array[i].mDrawable;
1085                 if (dr != null && dr.hasFocusStateSpecified()) {
1086                     return true;
1087                 }
1088             }
1089             return false;
1090         }
1091 
canConstantState()1092         public final boolean canConstantState() {
1093             final ChildDrawable[] array = mChildren;
1094             for (int i = 0; i < N_CHILDREN; i++) {
1095                 final Drawable dr = array[i].mDrawable;
1096                 if (dr != null && dr.getConstantState() == null) {
1097                     return false;
1098                 }
1099             }
1100 
1101             // Don't cache the result, this method is not called very often.
1102             return true;
1103         }
1104 
invalidateCache()1105         public void invalidateCache() {
1106             mCheckedOpacity = false;
1107             mCheckedStateful = false;
1108         }
1109     }
1110 }
1111