AdaptiveIconDrawable.java revision 50954d2b4ea938d787ef5021d75f6bc02826607a
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 android.annotation.NonNull;
20import android.annotation.Nullable;
21import android.annotation.TestApi;
22import android.content.pm.ActivityInfo.Config;
23import android.content.res.ColorStateList;
24import android.content.res.Resources;
25import android.content.res.Resources.Theme;
26import android.content.res.TypedArray;
27import android.graphics.Bitmap;
28import android.graphics.BitmapShader;
29import android.graphics.Canvas;
30import android.graphics.Color;
31import android.graphics.ColorFilter;
32import android.graphics.Matrix;
33import android.graphics.Outline;
34import android.graphics.Paint;
35import android.graphics.Path;
36import android.graphics.PixelFormat;
37import android.graphics.PorterDuff.Mode;
38import android.graphics.Rect;
39import android.graphics.Region;
40import android.graphics.Shader;
41import android.graphics.Shader.TileMode;
42import android.util.AttributeSet;
43import android.util.DisplayMetrics;
44import android.util.PathParser;
45
46import com.android.internal.R;
47import org.xmlpull.v1.XmlPullParser;
48import org.xmlpull.v1.XmlPullParserException;
49
50import java.io.IOException;
51
52/**
53 * <p>This class can also be created via XML inflation using <code>&lt;adaptive-icon></code> tag
54 * in addition to dynamic creation.
55 *
56 * <p>This drawable supports two drawable layers: foreground and background. The layers are clipped
57 * when rendering using the mask defined in the device configuration.
58 *
59 * <ul>
60 * <li>Both foreground and background layers should be sized at 108 x 108 dp.</li>
61 * <li>The inner 72 x 72 dp  of the icon appears within the masked viewport.</li>
62 * <li>The outer 18 dp on each of the 4 sides of the layers is reserved for use by the system UI
63 * surfaces to create interesting visual effects, such as parallax or pulsing.</li>
64 * </ul>
65 *
66 * Such motion effect is achieved by internally setting the bounds of the foreground and
67 * background layer as following:
68 * <pre>
69 * Rect(getBounds().left - getBounds().getWidth() * #getExtraInsetFraction(),
70 *      getBounds().top - getBounds().getHeight() * #getExtraInsetFraction(),
71 *      getBounds().right + getBounds().getWidth() * #getExtraInsetFraction(),
72 *      getBounds().bottom + getBounds().getHeight() * #getExtraInsetFraction())
73 * </pre>
74 */
75public class AdaptiveIconDrawable extends Drawable implements Drawable.Callback {
76
77    /**
78     * Mask path is defined inside device configuration in following dimension: [100 x 100]
79     * @hide
80     */
81    public static final float MASK_SIZE = 100f;
82
83    /**
84     * Launcher icons design guideline
85     */
86    private static final float SAFEZONE_SCALE = 72f/66f;
87
88    /**
89     * All four sides of the layers are padded with extra inset so as to provide
90     * extra content to reveal within the clip path when performing affine transformations on the
91     * layers.
92     *
93     * Each layers will reserve 25% of it's width and height.
94     *
95     * As a result, the view port of the layers is smaller than their intrinsic width and height.
96     */
97    private static final float EXTRA_INSET_PERCENTAGE = 1 / 4f;
98    private static final float DEFAULT_VIEW_PORT_SCALE = 1f / (1 + 2 * EXTRA_INSET_PERCENTAGE);
99
100    /**
101     * Clip path defined in R.string.config_icon_mask.
102     */
103    private static Path sMask;
104
105    /**
106     * Scaled mask based on the view bounds.
107     */
108    private final Path mMask;
109    private final Matrix mMaskMatrix;
110    private final Region mTransparentRegion;
111
112    private Bitmap mMaskBitmap;
113
114    /**
115     * Indices used to access {@link #mLayerState.mChildDrawable} array for foreground and
116     * background layer.
117     */
118    private static final int BACKGROUND_ID = 0;
119    private static final int FOREGROUND_ID = 1;
120
121    /**
122     * State variable that maintains the {@link ChildDrawable} array.
123     */
124    LayerState mLayerState;
125
126    private Shader mLayersShader;
127    private Bitmap mLayersBitmap;
128
129    private final Rect mTmpOutRect = new Rect();
130    private Rect mHotspotBounds;
131    private boolean mMutated;
132
133    private boolean mSuspendChildInvalidation;
134    private boolean mChildRequestedInvalidation;
135    private final Canvas mCanvas;
136    private Paint mPaint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.DITHER_FLAG |
137        Paint.FILTER_BITMAP_FLAG);
138
139    /**
140     * Constructor used for xml inflation.
141     */
142    AdaptiveIconDrawable() {
143        this((LayerState) null, null);
144    }
145
146    /**
147     * The one constructor to rule them all. This is called by all public
148     * constructors to set the state and initialize local properties.
149     */
150    AdaptiveIconDrawable(@Nullable LayerState state, @Nullable Resources res) {
151        mLayerState = createConstantState(state, res);
152
153        if (sMask == null) {
154            sMask = PathParser.createPathFromPathData(
155                Resources.getSystem().getString(R.string.config_icon_mask));
156        }
157        mMask = PathParser.createPathFromPathData(
158            Resources.getSystem().getString(R.string.config_icon_mask));
159        mMaskMatrix = new Matrix();
160        mCanvas = new Canvas();
161        mTransparentRegion = new Region();
162    }
163
164    private ChildDrawable createChildDrawable(Drawable drawable) {
165        final ChildDrawable layer = new ChildDrawable(mLayerState.mDensity);
166        layer.mDrawable = drawable;
167        layer.mDrawable.setCallback(this);
168        mLayerState.mChildrenChangingConfigurations |=
169            layer.mDrawable.getChangingConfigurations();
170        return layer;
171    }
172
173    LayerState createConstantState(@Nullable LayerState state, @Nullable Resources res) {
174        return new LayerState(state, this, res);
175    }
176
177    /**
178     * Constructor used to dynamically create this drawable.
179     *
180     * @param backgroundDrawable drawable that should be rendered in the background
181     * @param foregroundDrawable drawable that should be rendered in the foreground
182     * @hide
183     */
184    public AdaptiveIconDrawable(Drawable backgroundDrawable,
185            Drawable foregroundDrawable) {
186        this((LayerState)null, null);
187        if (backgroundDrawable != null) {
188            addLayer(BACKGROUND_ID, createChildDrawable(backgroundDrawable));
189        }
190        if (foregroundDrawable != null) {
191            addLayer(FOREGROUND_ID, createChildDrawable(foregroundDrawable));
192        }
193    }
194
195    /**
196     * Sets the layer to the {@param index} and invalidates cache.
197     *
198     * @param index The index of the layer.
199     * @param layer The layer to add.
200     */
201    private void addLayer(int index, @NonNull ChildDrawable layer) {
202        mLayerState.mChildren[index] = layer;
203        mLayerState.invalidateCache();
204    }
205
206    @Override
207    public void inflate(@NonNull Resources r, @NonNull XmlPullParser parser,
208            @NonNull AttributeSet attrs, @Nullable Theme theme)
209            throws XmlPullParserException, IOException {
210        super.inflate(r, parser, attrs, theme);
211
212        final LayerState state = mLayerState;
213        if (state == null) {
214            return;
215        }
216
217        // The density may have changed since the last update. This will
218        // apply scaling to any existing constant state properties.
219        final int deviceDensity = Drawable.resolveDensity(r, 0);
220        state.setDensity(deviceDensity);
221        state.mSrcDensityOverride = mSrcDensityOverride;
222
223        final ChildDrawable[] array = state.mChildren;
224        for (int i = 0; i < state.mChildren.length; i++) {
225            final ChildDrawable layer = array[i];
226            layer.setDensity(deviceDensity);
227        }
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    private 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.createFromXmlInnerForDensity(r, parser, attrs,
495                        mLayerState.mSrcDensityOverride, theme);
496                layer.mDrawable.setCallback(this);
497                state.mChildrenChangingConfigurations |=
498                        layer.mDrawable.getChangingConfigurations();
499            }
500            addLayer(childIndex, layer);
501        }
502    }
503
504    private void updateLayerFromTypedArray(@NonNull ChildDrawable layer, @NonNull TypedArray a) {
505        final LayerState state = mLayerState;
506
507        // Account for any configuration changes.
508        state.mChildrenChangingConfigurations |= a.getChangingConfigurations();
509
510        // Extract the theme attributes, if any.
511        layer.mThemeAttrs = a.extractThemeAttrs();
512
513        Drawable dr = a.getDrawableForDensity(R.styleable.AdaptiveIconDrawableLayer_drawable,
514                state.mSrcDensityOverride);
515        if (dr != null) {
516            if (layer.mDrawable != null) {
517                // It's possible that a drawable was already set, in which case
518                // we should clear the callback. We may have also integrated the
519                // drawable's changing configurations, but we don't have enough
520                // information to revert that change.
521                layer.mDrawable.setCallback(null);
522            }
523
524            // Take ownership of the new drawable.
525            layer.mDrawable = dr;
526            layer.mDrawable.setCallback(this);
527            state.mChildrenChangingConfigurations |=
528                layer.mDrawable.getChangingConfigurations();
529        }
530    }
531
532    @Override
533    public boolean canApplyTheme() {
534        return (mLayerState != null && mLayerState.canApplyTheme()) || super.canApplyTheme();
535    }
536
537    /**
538     * @hide
539     */
540    @Override
541    public boolean isProjected() {
542        if (super.isProjected()) {
543            return true;
544        }
545
546        final ChildDrawable[] layers = mLayerState.mChildren;
547        for (int i = 0; i < mLayerState.N_CHILDREN; i++) {
548            if (layers[i].mDrawable.isProjected()) {
549                return true;
550            }
551        }
552        return false;
553    }
554
555    /**
556     * Temporarily suspends child invalidation.
557     *
558     * @see #resumeChildInvalidation()
559     */
560    private void suspendChildInvalidation() {
561        mSuspendChildInvalidation = true;
562    }
563
564    /**
565     * Resumes child invalidation after suspension, immediately performing an
566     * invalidation if one was requested by a child during suspension.
567     *
568     * @see #suspendChildInvalidation()
569     */
570    private void resumeChildInvalidation() {
571        mSuspendChildInvalidation = false;
572
573        if (mChildRequestedInvalidation) {
574            mChildRequestedInvalidation = false;
575            invalidateSelf();
576        }
577    }
578
579    @Override
580    public void invalidateDrawable(@NonNull Drawable who) {
581        if (mSuspendChildInvalidation) {
582            mChildRequestedInvalidation = true;
583        } else {
584            invalidateSelf();
585        }
586    }
587
588    @Override
589    public void scheduleDrawable(@NonNull Drawable who, @NonNull Runnable what, long when) {
590        scheduleSelf(what, when);
591    }
592
593    @Override
594    public void unscheduleDrawable(@NonNull Drawable who, @NonNull Runnable what) {
595        unscheduleSelf(what);
596    }
597
598    @Override
599    public @Config int getChangingConfigurations() {
600        return super.getChangingConfigurations() | mLayerState.getChangingConfigurations();
601    }
602
603    @Override
604    public void setHotspot(float x, float y) {
605        final ChildDrawable[] array = mLayerState.mChildren;
606        for (int i = 0; i < mLayerState.N_CHILDREN; i++) {
607            final Drawable dr = array[i].mDrawable;
608            if (dr != null) {
609                dr.setHotspot(x, y);
610            }
611        }
612    }
613
614    @Override
615    public void setHotspotBounds(int left, int top, int right, int bottom) {
616        final ChildDrawable[] array = mLayerState.mChildren;
617        for (int i = 0; i < mLayerState.N_CHILDREN; i++) {
618            final Drawable dr = array[i].mDrawable;
619            if (dr != null) {
620                dr.setHotspotBounds(left, top, right, bottom);
621            }
622        }
623
624        if (mHotspotBounds == null) {
625            mHotspotBounds = new Rect(left, top, right, bottom);
626        } else {
627            mHotspotBounds.set(left, top, right, bottom);
628        }
629    }
630
631    @Override
632    public void getHotspotBounds(Rect outRect) {
633        if (mHotspotBounds != null) {
634            outRect.set(mHotspotBounds);
635        } else {
636            super.getHotspotBounds(outRect);
637        }
638    }
639
640    @Override
641    public boolean setVisible(boolean visible, boolean restart) {
642        final boolean changed = super.setVisible(visible, restart);
643        final ChildDrawable[] array = mLayerState.mChildren;
644
645        for (int i = 0; i < mLayerState.N_CHILDREN; i++) {
646            final Drawable dr = array[i].mDrawable;
647            if (dr != null) {
648                dr.setVisible(visible, restart);
649            }
650        }
651
652        return changed;
653    }
654
655    @Override
656    public void setDither(boolean dither) {
657        final ChildDrawable[] array = mLayerState.mChildren;
658        for (int i = 0; i < mLayerState.N_CHILDREN; i++) {
659            final Drawable dr = array[i].mDrawable;
660            if (dr != null) {
661                dr.setDither(dither);
662            }
663        }
664    }
665
666    @Override
667    public void setAlpha(int alpha) {
668        final ChildDrawable[] array = mLayerState.mChildren;
669        for (int i = 0; i < mLayerState.N_CHILDREN; i++) {
670            final Drawable dr = array[i].mDrawable;
671            if (dr != null) {
672                dr.setAlpha(alpha);
673            }
674        }
675    }
676
677    @Override
678    public int getAlpha() {
679        final Drawable dr = getFirstNonNullDrawable();
680        if (dr != null) {
681            return dr.getAlpha();
682        } else {
683            return super.getAlpha();
684        }
685    }
686
687    @Override
688    public void setColorFilter(ColorFilter colorFilter) {
689        final ChildDrawable[] array = mLayerState.mChildren;
690        for (int i = 0; i < mLayerState.N_CHILDREN; i++) {
691            final Drawable dr = array[i].mDrawable;
692            if (dr != null) {
693                dr.setColorFilter(colorFilter);
694            }
695        }
696    }
697
698    @Override
699    public void setTintList(ColorStateList tint) {
700        final ChildDrawable[] array = mLayerState.mChildren;
701        final int N = mLayerState.N_CHILDREN;
702        for (int i = 0; i < N; i++) {
703            final Drawable dr = array[i].mDrawable;
704            if (dr != null) {
705                dr.setTintList(tint);
706            }
707        }
708    }
709
710    @Override
711    public void setTintMode(Mode tintMode) {
712        final ChildDrawable[] array = mLayerState.mChildren;
713        final int N = mLayerState.N_CHILDREN;
714        for (int i = 0; i < N; i++) {
715            final Drawable dr = array[i].mDrawable;
716            if (dr != null) {
717                dr.setTintMode(tintMode);
718            }
719        }
720    }
721
722    private Drawable getFirstNonNullDrawable() {
723        final ChildDrawable[] array = mLayerState.mChildren;
724        for (int i = 0; i < mLayerState.N_CHILDREN; i++) {
725            final Drawable dr = array[i].mDrawable;
726            if (dr != null) {
727                return dr;
728            }
729        }
730        return null;
731    }
732
733    public void setOpacity(int opacity) {
734        mLayerState.mOpacityOverride = opacity;
735    }
736
737    @Override
738    public int getOpacity() {
739        if (mLayerState.mOpacityOverride != PixelFormat.UNKNOWN) {
740            return mLayerState.mOpacityOverride;
741        }
742        return mLayerState.getOpacity();
743    }
744
745    @Override
746    public void setAutoMirrored(boolean mirrored) {
747        mLayerState.mAutoMirrored = mirrored;
748
749        final ChildDrawable[] array = mLayerState.mChildren;
750        for (int i = 0; i < mLayerState.N_CHILDREN; i++) {
751            final Drawable dr = array[i].mDrawable;
752            if (dr != null) {
753                dr.setAutoMirrored(mirrored);
754            }
755        }
756    }
757
758    @Override
759    public boolean isAutoMirrored() {
760        return mLayerState.mAutoMirrored;
761    }
762
763    @Override
764    public void jumpToCurrentState() {
765        final ChildDrawable[] array = mLayerState.mChildren;
766        for (int i = 0; i < mLayerState.N_CHILDREN; i++) {
767            final Drawable dr = array[i].mDrawable;
768            if (dr != null) {
769                dr.jumpToCurrentState();
770            }
771        }
772    }
773
774    @Override
775    public boolean isStateful() {
776        return mLayerState.isStateful();
777    }
778
779    /** @hide */
780    @Override
781    public boolean hasFocusStateSpecified() {
782        return mLayerState.hasFocusStateSpecified();
783    }
784
785    @Override
786    protected boolean onStateChange(int[] state) {
787        boolean changed = false;
788
789        final ChildDrawable[] array = mLayerState.mChildren;
790        for (int i = 0; i < mLayerState.N_CHILDREN; i++) {
791            final Drawable dr = array[i].mDrawable;
792            if (dr != null && dr.isStateful() && dr.setState(state)) {
793                changed = true;
794            }
795        }
796
797        if (changed) {
798            updateLayerBounds(getBounds());
799        }
800
801        return changed;
802    }
803
804    @Override
805    protected boolean onLevelChange(int level) {
806        boolean changed = false;
807
808        final ChildDrawable[] array = mLayerState.mChildren;
809        for (int i = 0; i < mLayerState.N_CHILDREN; i++) {
810            final Drawable dr = array[i].mDrawable;
811            if (dr != null && dr.setLevel(level)) {
812                changed = true;
813            }
814        }
815
816        if (changed) {
817            updateLayerBounds(getBounds());
818        }
819
820        return changed;
821    }
822
823    @Override
824    public int getIntrinsicWidth() {
825        return (int)(getMaxIntrinsicWidth() * DEFAULT_VIEW_PORT_SCALE);
826    }
827
828    private int getMaxIntrinsicWidth() {
829        int width = -1;
830        for (int i = 0; i < mLayerState.N_CHILDREN; i++) {
831            final ChildDrawable r = mLayerState.mChildren[i];
832            if (r.mDrawable == null) {
833                continue;
834            }
835            final int w = r.mDrawable.getIntrinsicWidth();
836            if (w > width) {
837                width = w;
838            }
839        }
840        return width;
841    }
842
843    @Override
844    public int getIntrinsicHeight() {
845        return (int)(getMaxIntrinsicHeight() * DEFAULT_VIEW_PORT_SCALE);
846    }
847
848    private int getMaxIntrinsicHeight() {
849        int height = -1;
850        for (int i = 0; i < mLayerState.N_CHILDREN; i++) {
851            final ChildDrawable r = mLayerState.mChildren[i];
852            if (r.mDrawable == null) {
853                continue;
854            }
855            final int h = r.mDrawable.getIntrinsicHeight();
856            if (h > height) {
857                height = h;
858            }
859        }
860        return height;
861    }
862
863    @Override
864    public ConstantState getConstantState() {
865        if (mLayerState.canConstantState()) {
866            mLayerState.mChangingConfigurations = getChangingConfigurations();
867            return mLayerState;
868        }
869        return null;
870    }
871
872    @Override
873    public Drawable mutate() {
874        if (!mMutated && super.mutate() == this) {
875            mLayerState = createConstantState(mLayerState, null);
876            for (int i = 0; i < mLayerState.N_CHILDREN; i++) {
877                final Drawable dr = mLayerState.mChildren[i].mDrawable;
878                if (dr != null) {
879                    dr.mutate();
880                }
881            }
882            mMutated = true;
883        }
884        return this;
885    }
886
887    /**
888     * @hide
889     */
890    public void clearMutated() {
891        super.clearMutated();
892        final ChildDrawable[] array = mLayerState.mChildren;
893        for (int i = 0; i < mLayerState.N_CHILDREN; i++) {
894            final Drawable dr = array[i].mDrawable;
895            if (dr != null) {
896                dr.clearMutated();
897            }
898        }
899        mMutated = false;
900    }
901
902    static class ChildDrawable {
903        public Drawable mDrawable;
904        public int[] mThemeAttrs;
905        public int mDensity = DisplayMetrics.DENSITY_DEFAULT;
906
907        ChildDrawable(int density) {
908            mDensity = density;
909        }
910
911        ChildDrawable(@NonNull ChildDrawable orig, @NonNull AdaptiveIconDrawable owner,
912                @Nullable Resources res) {
913
914            final Drawable dr = orig.mDrawable;
915            final Drawable clone;
916            if (dr != null) {
917                final ConstantState cs = dr.getConstantState();
918                if (cs == null) {
919                    clone = dr;
920                } else if (res != null) {
921                    clone = cs.newDrawable(res);
922                } else {
923                    clone = cs.newDrawable();
924                }
925                clone.setCallback(owner);
926                clone.setBounds(dr.getBounds());
927                clone.setLevel(dr.getLevel());
928            } else {
929                clone = null;
930            }
931
932            mDrawable = clone;
933            mThemeAttrs = orig.mThemeAttrs;
934
935            mDensity = Drawable.resolveDensity(res, orig.mDensity);
936        }
937
938        public boolean canApplyTheme() {
939            return mThemeAttrs != null
940                    || (mDrawable != null && mDrawable.canApplyTheme());
941        }
942
943        public final void setDensity(int targetDensity) {
944            if (mDensity != targetDensity) {
945                mDensity = targetDensity;
946            }
947        }
948    }
949
950    static class LayerState extends ConstantState {
951        private int[] mThemeAttrs;
952
953        final static int N_CHILDREN = 2;
954        ChildDrawable[] mChildren;
955
956        // The density at which to render the drawable and its children.
957        int mDensity;
958
959        // The density to use when inflating/looking up the children drawables. A value of 0 means
960        // use the system's density.
961        int mSrcDensityOverride = 0;
962
963        int mOpacityOverride = PixelFormat.UNKNOWN;
964
965        @Config int mChangingConfigurations;
966        @Config int mChildrenChangingConfigurations;
967
968        private boolean mCheckedOpacity;
969        private int mOpacity;
970
971        private boolean mCheckedStateful;
972        private boolean mIsStateful;
973        private boolean mAutoMirrored = false;
974
975        LayerState(@Nullable LayerState orig, @NonNull AdaptiveIconDrawable owner,
976                @Nullable Resources res) {
977            mDensity = Drawable.resolveDensity(res, orig != null ? orig.mDensity : 0);
978            mChildren = new ChildDrawable[N_CHILDREN];
979            if (orig != null) {
980                final ChildDrawable[] origChildDrawable = orig.mChildren;
981
982                mChangingConfigurations = orig.mChangingConfigurations;
983                mChildrenChangingConfigurations = orig.mChildrenChangingConfigurations;
984
985                for (int i = 0; i < N_CHILDREN; i++) {
986                    final ChildDrawable or = origChildDrawable[i];
987                    mChildren[i] = new ChildDrawable(or, owner, res);
988                }
989
990                mCheckedOpacity = orig.mCheckedOpacity;
991                mOpacity = orig.mOpacity;
992                mCheckedStateful = orig.mCheckedStateful;
993                mIsStateful = orig.mIsStateful;
994                mAutoMirrored = orig.mAutoMirrored;
995                mThemeAttrs = orig.mThemeAttrs;
996                mOpacityOverride = orig.mOpacityOverride;
997                mSrcDensityOverride = orig.mSrcDensityOverride;
998            } else {
999                for (int i = 0; i < N_CHILDREN; i++) {
1000                    mChildren[i] = new ChildDrawable(mDensity);
1001                }
1002            }
1003        }
1004
1005        public final void setDensity(int targetDensity) {
1006            if (mDensity != targetDensity) {
1007                mDensity = targetDensity;
1008            }
1009        }
1010
1011        @Override
1012        public boolean canApplyTheme() {
1013            if (mThemeAttrs != null || super.canApplyTheme()) {
1014                return true;
1015            }
1016
1017            final ChildDrawable[] array = mChildren;
1018            for (int i = 0; i < N_CHILDREN; i++) {
1019                final ChildDrawable layer = array[i];
1020                if (layer.canApplyTheme()) {
1021                    return true;
1022                }
1023            }
1024            return false;
1025        }
1026
1027        @Override
1028        public Drawable newDrawable() {
1029            return new AdaptiveIconDrawable(this, null);
1030        }
1031
1032        @Override
1033        public Drawable newDrawable(@Nullable Resources res) {
1034            return new AdaptiveIconDrawable(this, res);
1035        }
1036
1037        @Override
1038        public @Config int getChangingConfigurations() {
1039            return mChangingConfigurations
1040                    | mChildrenChangingConfigurations;
1041        }
1042
1043        public final int getOpacity() {
1044            if (mCheckedOpacity) {
1045                return mOpacity;
1046            }
1047
1048            final ChildDrawable[] array = mChildren;
1049
1050            // Seek to the first non-null drawable.
1051            int firstIndex = -1;
1052            for (int i = 0; i < N_CHILDREN; i++) {
1053                if (array[i].mDrawable != null) {
1054                    firstIndex = i;
1055                    break;
1056                }
1057            }
1058
1059            int op;
1060            if (firstIndex >= 0) {
1061                op = array[firstIndex].mDrawable.getOpacity();
1062            } else {
1063                op = PixelFormat.TRANSPARENT;
1064            }
1065
1066            // Merge all remaining non-null drawables.
1067            for (int i = firstIndex + 1; i < N_CHILDREN; i++) {
1068                final Drawable dr = array[i].mDrawable;
1069                if (dr != null) {
1070                    op = Drawable.resolveOpacity(op, dr.getOpacity());
1071                }
1072            }
1073
1074            mOpacity = op;
1075            mCheckedOpacity = true;
1076            return op;
1077        }
1078
1079        public final boolean isStateful() {
1080            if (mCheckedStateful) {
1081                return mIsStateful;
1082            }
1083
1084            final ChildDrawable[] array = mChildren;
1085            boolean isStateful = false;
1086            for (int i = 0; i < N_CHILDREN; i++) {
1087                final Drawable dr = array[i].mDrawable;
1088                if (dr != null && dr.isStateful()) {
1089                    isStateful = true;
1090                    break;
1091                }
1092            }
1093
1094            mIsStateful = isStateful;
1095            mCheckedStateful = true;
1096            return isStateful;
1097        }
1098
1099        public final boolean hasFocusStateSpecified() {
1100            final ChildDrawable[] array = mChildren;
1101            for (int i = 0; i < N_CHILDREN; i++) {
1102                final Drawable dr = array[i].mDrawable;
1103                if (dr != null && dr.hasFocusStateSpecified()) {
1104                    return true;
1105                }
1106            }
1107            return false;
1108        }
1109
1110        public final boolean canConstantState() {
1111            final ChildDrawable[] array = mChildren;
1112            for (int i = 0; i < N_CHILDREN; i++) {
1113                final Drawable dr = array[i].mDrawable;
1114                if (dr != null && dr.getConstantState() == null) {
1115                    return false;
1116                }
1117            }
1118
1119            // Don't cache the result, this method is not called very often.
1120            return true;
1121        }
1122
1123        public void invalidateCache() {
1124            mCheckedOpacity = false;
1125            mCheckedStateful = false;
1126        }
1127    }
1128}