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