diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index c36279c28fdbe..122bde1553e67 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -2712,6 +2712,9 @@
com.android.cellbroadcastreceiver/.PrivilegedCellBroadcastReceiver
+
+ "M50,0L100,0 100,100 0,100 0,0z"
+
diff --git a/graphics/java/android/graphics/drawable/DrawableInflater.java b/graphics/java/android/graphics/drawable/DrawableInflater.java
index 348af70d55688..6d0bbdf549f28 100644
--- a/graphics/java/android/graphics/drawable/DrawableInflater.java
+++ b/graphics/java/android/graphics/drawable/DrawableInflater.java
@@ -147,6 +147,8 @@ public final class DrawableInflater {
return new TransitionDrawable();
case "ripple":
return new RippleDrawable();
+ case "maskable-icon":
+ return new MaskableIconDrawable();
case "color":
return new ColorDrawable();
case "shape":
diff --git a/graphics/java/android/graphics/drawable/MaskableIconDrawable.java b/graphics/java/android/graphics/drawable/MaskableIconDrawable.java
new file mode 100644
index 0000000000000..3467b1a789e74
--- /dev/null
+++ b/graphics/java/android/graphics/drawable/MaskableIconDrawable.java
@@ -0,0 +1,1001 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.graphics.drawable;
+
+import static android.graphics.drawable.Drawable.obtainAttributes;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.pm.ActivityInfo.Config;
+import android.content.res.ColorStateList;
+import android.content.res.Resources;
+import android.content.res.Resources.Theme;
+import android.content.res.TypedArray;
+import android.graphics.Bitmap;
+import android.graphics.BitmapShader;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.ColorFilter;
+import android.graphics.Matrix;
+import android.graphics.Outline;
+import android.graphics.Paint;
+import android.graphics.Path;
+import android.graphics.PixelFormat;
+import android.graphics.PorterDuff.Mode;
+import android.graphics.Rect;
+import android.graphics.Region;
+import android.graphics.Shader;
+import android.graphics.Shader.TileMode;
+import android.util.AttributeSet;
+import android.util.DisplayMetrics;
+import android.util.PathParser;
+
+import com.android.internal.R;
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.IOException;
+
+/**
+ * This drawable supports two layers: foreground and background.
+ *
+ * The layers are clipped when rendering using the mask path defined in the device configuration.
+ *
+ *
This class can also be created via XML inflation using <maskable-icon> tag
+ * in addition to dynamic creation.
+ */
+public class MaskableIconDrawable extends Drawable implements Drawable.Callback {
+
+ /**
+ * Mask path is defined inside device configuration in following dimension: [100 x 100]
+ */
+ public static final float MASK_SIZE = 100f;
+
+ /**
+ * The view port of the layers is smaller than their intrinsic width and height by this factor.
+ *
+ * It is part of the API contract that all four sides of the layers are padded so as to provide
+ * extra content to reveal within the clip path when performing affine transformations on the
+ * layers.
+ */
+ public static final float DEFAULT_VIEW_PORT_SCALE = 2f / 3f;
+
+ /**
+ * Clip path defined in {@link com.android.internal.R.string.config_icon_mask}.
+ */
+ private static Path sMask;
+
+ /**
+ * Scaled mask based on the view bounds.
+ */
+ private final Path mMask;
+ private final Matrix mMaskMatrix;
+ private final Region mTransparentRegion;
+
+ private Bitmap mMaskBitmap;
+
+ /**
+ * Indices used to access {@link #mLayerState.mChildDrawable} array for foreground and
+ * background layer.
+ */
+ private static final int BACKGROUND_ID = 0;
+ private static final int FOREGROUND_ID = 1;
+
+ /**
+ * State variable that maintains the {@link ChildDrawable} array.
+ */
+ LayerState mLayerState;
+
+ private Shader mLayersShader;
+ private Bitmap mLayersBitmap;
+
+ private final Rect mTmpOutRect = new Rect();
+ private Rect mHotspotBounds;
+ private boolean mMutated;
+
+ private boolean mSuspendChildInvalidation;
+ private boolean mChildRequestedInvalidation;
+ private final Canvas mCanvas;
+ private Paint mPaint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.DITHER_FLAG |
+ Paint.FILTER_BITMAP_FLAG);
+
+ /**
+ * Constructor used for xml inflation.
+ */
+ MaskableIconDrawable() {
+ this((LayerState) null, null);
+ }
+
+ /**
+ * The one constructor to rule them all. This is called by all public
+ * constructors to set the state and initialize local properties.
+ */
+ MaskableIconDrawable(@Nullable LayerState state, @Nullable Resources res) {
+ mLayerState = createConstantState(state, res);
+
+ if (sMask == null) {
+ sMask = PathParser.createPathFromPathData(
+ Resources.getSystem().getString(com.android.internal.R.string.config_icon_mask));
+ }
+ mMask = new Path();
+ mMaskMatrix = new Matrix();
+ mCanvas = new Canvas();
+ mTransparentRegion = new Region();
+ }
+
+ private ChildDrawable createChildDrawable(Drawable drawable) {
+ final ChildDrawable layer = new ChildDrawable(mLayerState.mDensity);
+ layer.mDrawable = drawable;
+ layer.mDrawable.setCallback(this);
+ mLayerState.mChildrenChangingConfigurations |=
+ layer.mDrawable.getChangingConfigurations();
+ return layer;
+ }
+
+ LayerState createConstantState(@Nullable LayerState state, @Nullable Resources res) {
+ return new LayerState(state, this, res);
+ }
+
+ /**
+ * Constructor used to dynamically create this drawable.
+ *
+ * @param backgroundDrawable drawable that should be rendered in the background
+ * @param foregroundDrawable drawable that should be rendered in the foreground
+ */
+ public MaskableIconDrawable(Drawable backgroundDrawable,
+ Drawable foregroundDrawable) {
+ this((LayerState)null, null);
+ addLayer(BACKGROUND_ID, createChildDrawable(backgroundDrawable));
+ addLayer(FOREGROUND_ID, createChildDrawable(foregroundDrawable));
+ }
+
+ /**
+ * Sets the layer to the {@param index} and invalidates cache.
+ *
+ * @param index The index of the layer.
+ * @param layer The layer to add.
+ */
+ private void addLayer(int index, @NonNull ChildDrawable layer) {
+ mLayerState.mChildren[index] = layer;
+ mLayerState.invalidateCache();
+ }
+
+ @Override
+ public void inflate(@NonNull Resources r, @NonNull XmlPullParser parser,
+ @NonNull AttributeSet attrs, @Nullable Theme theme)
+ throws XmlPullParserException, IOException {
+ super.inflate(r, parser, attrs, theme);
+
+ final LayerState state = mLayerState;
+ if (state == null) {
+ return;
+ }
+
+ // The density may have changed since the last update. This will
+ // apply scaling to any existing constant state properties.
+ final int density = Drawable.resolveDensity(r, 0);
+ state.setDensity(density);
+
+ final ChildDrawable[] array = state.mChildren;
+ for (int i = 0; i < state.mChildren.length; i++) {
+ final ChildDrawable layer = array[i];
+ layer.setDensity(density);
+ }
+ inflateLayers(r, parser, attrs, theme);
+ }
+
+ /**
+ * @return the mask path object used to clip the drawable
+ */
+ public Path getIconMask() {
+ return mMask;
+ }
+
+ /**
+ * @return the foreground drawable managed by this drawable
+ */
+ public Drawable getForeground() {
+ return mLayerState.mChildren[FOREGROUND_ID].mDrawable;
+ }
+
+ /**
+ * @return the background drawable managed by this drawable
+ */
+ public Drawable getBackground() {
+ return mLayerState.mChildren[BACKGROUND_ID].mDrawable;
+ }
+
+ @Override
+ protected void onBoundsChange(Rect bounds) {
+ if (bounds.isEmpty()) {
+ return;
+ }
+ updateLayerBounds(bounds);
+ }
+
+ private void updateLayerBounds(Rect bounds) {
+ try {
+ suspendChildInvalidation();
+ updateLayerBoundsInternal(bounds);
+ updateMaskBoundsInternal(bounds);
+ } finally {
+ resumeChildInvalidation();
+ }
+ }
+
+ private void updateLayerBoundsInternal(Rect bounds) {
+ int cX = bounds.centerX();
+ int cY = bounds.centerY();
+
+ for (int i = 0, count = mLayerState.N_CHILDREN; i < count; i++) {
+ int insetWidth = (int) (bounds.width() / (DEFAULT_VIEW_PORT_SCALE * 2));
+ int insetHeight = (int) (bounds.height() / (DEFAULT_VIEW_PORT_SCALE * 2));
+ final Rect outRect = mTmpOutRect;
+ outRect.set(cX - insetWidth, cY - insetHeight, cX + insetWidth, cY + insetHeight);
+
+ final ChildDrawable r = mLayerState.mChildren[i];
+ final Drawable d = r.mDrawable;
+ d.setBounds(outRect);
+ }
+ }
+
+ private void updateMaskBoundsInternal(Rect b) {
+ mMaskMatrix.setScale(b.width() / MASK_SIZE, b.height() / MASK_SIZE);
+ sMask.transform(mMaskMatrix, mMask);
+
+ mMaskBitmap = Bitmap.createBitmap(b.width(), b.height(), Bitmap.Config.ALPHA_8);
+ mCanvas.setBitmap(mMaskBitmap);
+ mPaint.setShader(null);
+ mCanvas.drawPath(mMask, mPaint);
+
+ // reset everything that depends on the view bounds
+ mTransparentRegion.setEmpty();
+ mLayersShader = null;
+ mLayersBitmap = Bitmap.createBitmap(b.width(), b.height(), Bitmap.Config.ARGB_8888);
+ }
+
+ @Override
+ public void draw(Canvas canvas) {
+ if (mLayersShader == null) {
+ mCanvas.setBitmap(mLayersBitmap);
+ for (int i = 0; i < mLayerState.N_CHILDREN; i++) {
+ final Drawable dr = mLayerState.mChildren[i].mDrawable;
+ if (dr != null) {
+ dr.draw(mCanvas);
+ }
+ }
+ mLayersShader = new BitmapShader(mLayersBitmap, TileMode.CLAMP, TileMode.CLAMP);
+ mPaint.setShader(mLayersShader);
+ }
+ canvas.drawBitmap(mMaskBitmap, 0.0f, 0.0f, mPaint);
+ }
+
+ @Override
+ public void invalidateSelf() {
+ mLayersShader = null;
+ super.invalidateSelf();
+ }
+
+ @Override
+ public void getOutline(@NonNull Outline outline) {
+ outline.setConvexPath(mMask);
+ }
+
+ @Override
+ public @Nullable Region getTransparentRegion() {
+ if (mTransparentRegion.isEmpty()) {
+ mMask.toggleInverseFillType();
+ mTransparentRegion.set(getBounds());
+ mTransparentRegion.setPath(mMask, mTransparentRegion);
+ mMask.toggleInverseFillType();
+ }
+ return mTransparentRegion;
+ }
+
+ @Override
+ public void applyTheme(@NonNull Theme t) {
+ super.applyTheme(t);
+
+ final LayerState state = mLayerState;
+ if (state == null) {
+ return;
+ }
+
+ final int density = Drawable.resolveDensity(t.getResources(), 0);
+ state.setDensity(density);
+
+ final ChildDrawable[] array = state.mChildren;
+ for (int i = 0; i < state.N_CHILDREN; i++) {
+ final ChildDrawable layer = array[i];
+ layer.setDensity(density);
+
+ if (layer.mThemeAttrs != null) {
+ final TypedArray a = t.resolveAttributes(
+ layer.mThemeAttrs, R.styleable.MaskableIconDrawableLayer);
+ updateLayerFromTypedArray(layer, a);
+ a.recycle();
+ }
+
+ final Drawable d = layer.mDrawable;
+ if (d != null && d.canApplyTheme()) {
+ d.applyTheme(t);
+
+ // Update cached mask of child changing configurations.
+ state.mChildrenChangingConfigurations |= d.getChangingConfigurations();
+ }
+ }
+ }
+
+ /**
+ * Inflates child layers using the specified parser.
+ */
+ void inflateLayers(@NonNull Resources r, @NonNull XmlPullParser parser,
+ @NonNull AttributeSet attrs, @Nullable Theme theme)
+ throws XmlPullParserException, IOException {
+ final LayerState state = mLayerState;
+
+ final int innerDepth = parser.getDepth() + 1;
+ int type;
+ int depth;
+ int childIndex = 0;
+ while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
+ && ((depth = parser.getDepth()) >= innerDepth || type != XmlPullParser.END_TAG)) {
+ if (type != XmlPullParser.START_TAG) {
+ continue;
+ }
+
+ if (depth > innerDepth) {
+ continue;
+ }
+ String tagName = parser.getName();
+ if (tagName.equals("background")) {
+ childIndex = BACKGROUND_ID;
+ } else if (tagName.equals("foreground")) {
+ childIndex = FOREGROUND_ID;
+ } else {
+ continue;
+ }
+
+ final ChildDrawable layer = new ChildDrawable(state.mDensity);
+ final TypedArray a = obtainAttributes(r, theme, attrs,
+ R.styleable.MaskableIconDrawableLayer);
+ updateLayerFromTypedArray(layer, a);
+ a.recycle();
+
+ // If the layer doesn't have a drawable or unresolved theme
+ // attribute for a drawable, attempt to parse one from the child
+ // element. If multiple child elements exist, we'll only use the
+ // first one.
+ if (layer.mDrawable == null && (layer.mThemeAttrs == null)) {
+ while ((type = parser.next()) == XmlPullParser.TEXT) {
+ }
+ if (type != XmlPullParser.START_TAG) {
+ throw new XmlPullParserException(parser.getPositionDescription()
+ + ": or tag requires a 'color' or 'drawable'"
+ + "attribute or child tag defining a drawable");
+ }
+
+ // We found a child drawable. Take ownership.
+ layer.mDrawable = Drawable.createFromXmlInner(r, parser, attrs, theme);
+ layer.mDrawable.setCallback(this);
+ state.mChildrenChangingConfigurations |=
+ layer.mDrawable.getChangingConfigurations();
+ }
+ addLayer(childIndex, layer);
+ }
+ }
+
+ private void updateLayerFromTypedArray(@NonNull ChildDrawable layer, @NonNull TypedArray a) {
+ final LayerState state = mLayerState;
+
+ // Account for any configuration changes.
+ state.mChildrenChangingConfigurations |= a.getChangingConfigurations();
+
+ // Extract the theme attributes, if any.
+ layer.mThemeAttrs = a.extractThemeAttrs();
+
+ Drawable dr = a.getDrawable(R.styleable.MaskableIconDrawableLayer_drawable);
+ if (dr == null) {
+ int color = a.getColor(R.styleable.MaskableIconDrawableLayer_color, Color.TRANSPARENT);
+ if (color != Color.TRANSPARENT) {
+ dr = new ColorDrawable(color);
+ }
+ }
+ if (dr != null) {
+ if (layer.mDrawable != null) {
+ // It's possible that a drawable was already set, in which case
+ // we should clear the callback. We may have also integrated the
+ // drawable's changing configurations, but we don't have enough
+ // information to revert that change.
+ layer.mDrawable.setCallback(null);
+ }
+
+ // Take ownership of the new drawable.
+ layer.mDrawable = dr;
+ layer.mDrawable.setCallback(this);
+ state.mChildrenChangingConfigurations |=
+ layer.mDrawable.getChangingConfigurations();
+ }
+ }
+
+ @Override
+ public boolean canApplyTheme() {
+ return (mLayerState != null && mLayerState.canApplyTheme()) || super.canApplyTheme();
+ }
+
+ /**
+ * @hide
+ */
+ @Override
+ public boolean isProjected() {
+ if (super.isProjected()) {
+ return true;
+ }
+
+ final ChildDrawable[] layers = mLayerState.mChildren;
+ for (int i = 0; i < mLayerState.N_CHILDREN; i++) {
+ if (layers[i].mDrawable.isProjected()) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Temporarily suspends child invalidation.
+ *
+ * @see #resumeChildInvalidation()
+ */
+ private void suspendChildInvalidation() {
+ mSuspendChildInvalidation = true;
+ }
+
+ /**
+ * Resumes child invalidation after suspension, immediately performing an
+ * invalidation if one was requested by a child during suspension.
+ *
+ * @see #suspendChildInvalidation()
+ */
+ private void resumeChildInvalidation() {
+ mSuspendChildInvalidation = false;
+
+ if (mChildRequestedInvalidation) {
+ mChildRequestedInvalidation = false;
+ invalidateSelf();
+ }
+ }
+
+ @Override
+ public void invalidateDrawable(@NonNull Drawable who) {
+ if (mSuspendChildInvalidation) {
+ mChildRequestedInvalidation = true;
+ } else {
+ invalidateSelf();
+ }
+ }
+
+ @Override
+ public void scheduleDrawable(@NonNull Drawable who, @NonNull Runnable what, long when) {
+ scheduleSelf(what, when);
+ }
+
+ @Override
+ public void unscheduleDrawable(@NonNull Drawable who, @NonNull Runnable what) {
+ unscheduleSelf(what);
+ }
+
+ @Override
+ public @Config int getChangingConfigurations() {
+ return super.getChangingConfigurations() | mLayerState.getChangingConfigurations();
+ }
+
+ @Override
+ public void setHotspot(float x, float y) {
+ final ChildDrawable[] array = mLayerState.mChildren;
+ for (int i = 0; i < mLayerState.N_CHILDREN; i++) {
+ final Drawable dr = array[i].mDrawable;
+ if (dr != null) {
+ dr.setHotspot(x, y);
+ }
+ }
+ }
+
+ @Override
+ public void setHotspotBounds(int left, int top, int right, int bottom) {
+ final ChildDrawable[] array = mLayerState.mChildren;
+ for (int i = 0; i < mLayerState.N_CHILDREN; i++) {
+ final Drawable dr = array[i].mDrawable;
+ if (dr != null) {
+ dr.setHotspotBounds(left, top, right, bottom);
+ }
+ }
+
+ if (mHotspotBounds == null) {
+ mHotspotBounds = new Rect(left, top, right, bottom);
+ } else {
+ mHotspotBounds.set(left, top, right, bottom);
+ }
+ }
+
+ @Override
+ public void getHotspotBounds(Rect outRect) {
+ if (mHotspotBounds != null) {
+ outRect.set(mHotspotBounds);
+ } else {
+ super.getHotspotBounds(outRect);
+ }
+ }
+
+ @Override
+ public boolean setVisible(boolean visible, boolean restart) {
+ final boolean changed = super.setVisible(visible, restart);
+ final ChildDrawable[] array = mLayerState.mChildren;
+
+ for (int i = 0; i < mLayerState.N_CHILDREN; i++) {
+ final Drawable dr = array[i].mDrawable;
+ if (dr != null) {
+ dr.setVisible(visible, restart);
+ }
+ }
+
+ return changed;
+ }
+
+ @Override
+ public void setDither(boolean dither) {
+ final ChildDrawable[] array = mLayerState.mChildren;
+ for (int i = 0; i < mLayerState.N_CHILDREN; i++) {
+ final Drawable dr = array[i].mDrawable;
+ if (dr != null) {
+ dr.setDither(dither);
+ }
+ }
+ }
+
+ @Override
+ public void setAlpha(int alpha) {
+ final ChildDrawable[] array = mLayerState.mChildren;
+ for (int i = 0; i < mLayerState.N_CHILDREN; i++) {
+ final Drawable dr = array[i].mDrawable;
+ if (dr != null) {
+ dr.setAlpha(alpha);
+ }
+ }
+ }
+
+ @Override
+ public int getAlpha() {
+ final Drawable dr = getFirstNonNullDrawable();
+ if (dr != null) {
+ return dr.getAlpha();
+ } else {
+ return super.getAlpha();
+ }
+ }
+
+ @Override
+ public void setColorFilter(ColorFilter colorFilter) {
+ final ChildDrawable[] array = mLayerState.mChildren;
+ for (int i = 0; i < mLayerState.N_CHILDREN; i++) {
+ final Drawable dr = array[i].mDrawable;
+ if (dr != null) {
+ dr.setColorFilter(colorFilter);
+ }
+ }
+ }
+
+ @Override
+ public void setTintList(ColorStateList tint) {
+ final ChildDrawable[] array = mLayerState.mChildren;
+ final int N = mLayerState.N_CHILDREN;
+ for (int i = 0; i < N; i++) {
+ final Drawable dr = array[i].mDrawable;
+ if (dr != null) {
+ dr.setTintList(tint);
+ }
+ }
+ }
+
+ @Override
+ public void setTintMode(Mode tintMode) {
+ final ChildDrawable[] array = mLayerState.mChildren;
+ final int N = mLayerState.N_CHILDREN;
+ for (int i = 0; i < N; i++) {
+ final Drawable dr = array[i].mDrawable;
+ if (dr != null) {
+ dr.setTintMode(tintMode);
+ }
+ }
+ }
+
+ private Drawable getFirstNonNullDrawable() {
+ final ChildDrawable[] array = mLayerState.mChildren;
+ for (int i = 0; i < mLayerState.N_CHILDREN; i++) {
+ final Drawable dr = array[i].mDrawable;
+ if (dr != null) {
+ return dr;
+ }
+ }
+ return null;
+ }
+
+ public void setOpacity(int opacity) {
+ mLayerState.mOpacityOverride = opacity;
+ }
+
+ @Override
+ public int getOpacity() {
+ if (mLayerState.mOpacityOverride != PixelFormat.UNKNOWN) {
+ return mLayerState.mOpacityOverride;
+ }
+ return mLayerState.getOpacity();
+ }
+
+ @Override
+ public void setAutoMirrored(boolean mirrored) {
+ mLayerState.mAutoMirrored = mirrored;
+
+ final ChildDrawable[] array = mLayerState.mChildren;
+ for (int i = 0; i < mLayerState.N_CHILDREN; i++) {
+ final Drawable dr = array[i].mDrawable;
+ if (dr != null) {
+ dr.setAutoMirrored(mirrored);
+ }
+ }
+ }
+
+ @Override
+ public boolean isAutoMirrored() {
+ return mLayerState.mAutoMirrored;
+ }
+
+ @Override
+ public void jumpToCurrentState() {
+ final ChildDrawable[] array = mLayerState.mChildren;
+ for (int i = 0; i < mLayerState.N_CHILDREN; i++) {
+ final Drawable dr = array[i].mDrawable;
+ if (dr != null) {
+ dr.jumpToCurrentState();
+ }
+ }
+ }
+
+ @Override
+ public boolean isStateful() {
+ return mLayerState.isStateful();
+ }
+
+ @Override
+ protected boolean onStateChange(int[] state) {
+ boolean changed = false;
+
+ final ChildDrawable[] array = mLayerState.mChildren;
+ for (int i = 0; i < mLayerState.N_CHILDREN; i++) {
+ final Drawable dr = array[i].mDrawable;
+ if (dr != null && dr.isStateful() && dr.setState(state)) {
+ changed = true;
+ }
+ }
+
+ if (changed) {
+ updateLayerBounds(getBounds());
+ }
+
+ return changed;
+ }
+
+ @Override
+ protected boolean onLevelChange(int level) {
+ boolean changed = false;
+
+ final ChildDrawable[] array = mLayerState.mChildren;
+ for (int i = 0; i < mLayerState.N_CHILDREN; i++) {
+ final Drawable dr = array[i].mDrawable;
+ if (dr != null && dr.setLevel(level)) {
+ changed = true;
+ }
+ }
+
+ if (changed) {
+ updateLayerBounds(getBounds());
+ }
+
+ return changed;
+ }
+
+ @Override
+ public int getIntrinsicWidth() {
+ return (int)(getMaxIntrinsicWidth() * DEFAULT_VIEW_PORT_SCALE);
+ }
+
+ private int getMaxIntrinsicWidth() {
+ int width = -1;
+ for (int i = 0; i < mLayerState.N_CHILDREN; i++) {
+ final ChildDrawable r = mLayerState.mChildren[i];
+ final int w = r.mDrawable.getIntrinsicWidth();
+ if (w > width) {
+ width = w;
+ }
+ }
+ return width;
+ }
+
+ @Override
+ public int getIntrinsicHeight() {
+ return (int)(getMaxIntrinsicHeight() * DEFAULT_VIEW_PORT_SCALE);
+ }
+
+ private int getMaxIntrinsicHeight() {
+ int height = -1;
+ for (int i = 0; i < mLayerState.N_CHILDREN; i++) {
+ final ChildDrawable r = mLayerState.mChildren[i];
+ final int h = r.mDrawable.getIntrinsicHeight();
+ if (h > height) {
+ height = h;
+ }
+ }
+ return height;
+ }
+
+ @Override
+ public ConstantState getConstantState() {
+ if (mLayerState.canConstantState()) {
+ mLayerState.mChangingConfigurations = getChangingConfigurations();
+ return mLayerState;
+ }
+ return null;
+ }
+
+ @Override
+ public Drawable mutate() {
+ if (!mMutated && super.mutate() == this) {
+ mLayerState = createConstantState(mLayerState, null);
+ for (int i = 0; i < mLayerState.N_CHILDREN; i++) {
+ final Drawable dr = mLayerState.mChildren[i].mDrawable;
+ if (dr != null) {
+ dr.mutate();
+ }
+ }
+ mMutated = true;
+ }
+ return this;
+ }
+
+ /**
+ * @hide
+ */
+ public void clearMutated() {
+ super.clearMutated();
+ final ChildDrawable[] array = mLayerState.mChildren;
+ for (int i = 0; i < mLayerState.N_CHILDREN; i++) {
+ final Drawable dr = array[i].mDrawable;
+ if (dr != null) {
+ dr.clearMutated();
+ }
+ }
+ mMutated = false;
+ }
+
+ static class ChildDrawable {
+ public Drawable mDrawable;
+ public int[] mThemeAttrs;
+ public int mDensity = DisplayMetrics.DENSITY_DEFAULT;
+
+ ChildDrawable(int density) {
+ mDensity = density;
+ }
+
+ ChildDrawable(@NonNull ChildDrawable orig, @NonNull MaskableIconDrawable owner,
+ @Nullable Resources res) {
+
+ final Drawable dr = orig.mDrawable;
+ final Drawable clone;
+ if (dr != null) {
+ final ConstantState cs = dr.getConstantState();
+ if (cs == null) {
+ clone = dr;
+ } else if (res != null) {
+ clone = cs.newDrawable(res);
+ } else {
+ clone = cs.newDrawable();
+ }
+ clone.setCallback(owner);
+ clone.setBounds(dr.getBounds());
+ clone.setLevel(dr.getLevel());
+ } else {
+ clone = null;
+ }
+
+ mDrawable = clone;
+ mThemeAttrs = orig.mThemeAttrs;
+
+ mDensity = Drawable.resolveDensity(res, orig.mDensity);
+ }
+
+ public boolean canApplyTheme() {
+ return mThemeAttrs != null
+ || (mDrawable != null && mDrawable.canApplyTheme());
+ }
+
+ public final void setDensity(int targetDensity) {
+ if (mDensity != targetDensity) {
+ mDensity = targetDensity;
+ }
+ }
+ }
+
+ static class LayerState extends ConstantState {
+ private int[] mThemeAttrs;
+
+ final static int N_CHILDREN = 2;
+ ChildDrawable[] mChildren;
+
+ int mDensity;
+ int mOpacityOverride = PixelFormat.UNKNOWN;
+
+ @Config int mChangingConfigurations;
+ @Config int mChildrenChangingConfigurations;
+
+ private boolean mCheckedOpacity;
+ private int mOpacity;
+
+ private boolean mCheckedStateful;
+ private boolean mIsStateful;
+ private boolean mAutoMirrored = false;
+
+ LayerState(@Nullable LayerState orig, @NonNull MaskableIconDrawable owner,
+ @Nullable Resources res) {
+ mDensity = Drawable.resolveDensity(res, orig != null ? orig.mDensity : 0);
+ mChildren = new ChildDrawable[N_CHILDREN];
+ if (orig != null) {
+ final ChildDrawable[] origChildDrawable = orig.mChildren;
+
+ mChangingConfigurations = orig.mChangingConfigurations;
+ mChildrenChangingConfigurations = orig.mChildrenChangingConfigurations;
+
+ for (int i = 0; i < N_CHILDREN; i++) {
+ final ChildDrawable or = origChildDrawable[i];
+ mChildren[i] = new ChildDrawable(or, owner, res);
+ }
+
+ mCheckedOpacity = orig.mCheckedOpacity;
+ mOpacity = orig.mOpacity;
+ mCheckedStateful = orig.mCheckedStateful;
+ mIsStateful = orig.mIsStateful;
+ mAutoMirrored = orig.mAutoMirrored;
+ mThemeAttrs = orig.mThemeAttrs;
+ mOpacityOverride = orig.mOpacityOverride;
+ } else {
+ for (int i = 0; i < N_CHILDREN; i++) {
+ mChildren[i] = new ChildDrawable(mDensity);
+ }
+ }
+ }
+
+ public final void setDensity(int targetDensity) {
+ if (mDensity != targetDensity) {
+ mDensity = targetDensity;
+ }
+ }
+
+ @Override
+ public boolean canApplyTheme() {
+ if (mThemeAttrs != null || super.canApplyTheme()) {
+ return true;
+ }
+
+ final ChildDrawable[] array = mChildren;
+ for (int i = 0; i < N_CHILDREN; i++) {
+ final ChildDrawable layer = array[i];
+ if (layer.canApplyTheme()) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public Drawable newDrawable() {
+ return new MaskableIconDrawable(this, null);
+ }
+
+ @Override
+ public Drawable newDrawable(@Nullable Resources res) {
+ return new MaskableIconDrawable(this, res);
+ }
+
+ @Override
+ public @Config int getChangingConfigurations() {
+ return mChangingConfigurations
+ | mChildrenChangingConfigurations;
+ }
+
+ public final int getOpacity() {
+ if (mCheckedOpacity) {
+ return mOpacity;
+ }
+
+ final ChildDrawable[] array = mChildren;
+
+ // Seek to the first non-null drawable.
+ int firstIndex = -1;
+ for (int i = 0; i < N_CHILDREN; i++) {
+ if (array[i].mDrawable != null) {
+ firstIndex = i;
+ break;
+ }
+ }
+
+ int op;
+ if (firstIndex >= 0) {
+ op = array[firstIndex].mDrawable.getOpacity();
+ } else {
+ op = PixelFormat.TRANSPARENT;
+ }
+
+ // Merge all remaining non-null drawables.
+ for (int i = firstIndex + 1; i < N_CHILDREN; i++) {
+ final Drawable dr = array[i].mDrawable;
+ if (dr != null) {
+ op = Drawable.resolveOpacity(op, dr.getOpacity());
+ }
+ }
+
+ mOpacity = op;
+ mCheckedOpacity = true;
+ return op;
+ }
+
+ public final boolean isStateful() {
+ if (mCheckedStateful) {
+ return mIsStateful;
+ }
+
+ final ChildDrawable[] array = mChildren;
+ boolean isStateful = false;
+ for (int i = 0; i < N_CHILDREN; i++) {
+ final Drawable dr = array[i].mDrawable;
+ if (dr != null && dr.isStateful()) {
+ isStateful = true;
+ break;
+ }
+ }
+
+ mIsStateful = isStateful;
+ mCheckedStateful = true;
+ return isStateful;
+ }
+
+ public final boolean canConstantState() {
+ final ChildDrawable[] array = mChildren;
+ for (int i = 0; i < N_CHILDREN; i++) {
+ final Drawable dr = array[i].mDrawable;
+ if (dr != null && dr.getConstantState() == null) {
+ return false;
+ }
+ }
+
+ // Don't cache the result, this method is not called very often.
+ return true;
+ }
+
+ public void invalidateCache() {
+ mCheckedOpacity = false;
+ mCheckedStateful = false;
+ }
+ }
+}
\ No newline at end of file