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