content: /*
   * 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 android.annotation.NonNull;
  import android.annotation.Nullable;
  import android.annotation.TestApi;
  import android.app.ActivityThread;
  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.BlendMode;
  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.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;
  
  /**
   * <p>This class can also be created via XML inflation using <code>&lt;adaptive-icon></code> tag
   * in addition to dynamic creation.
   *
   * <p>This drawable supports two drawable layers: foreground and background. The layers are clipped
   * when rendering using the mask defined in the device configuration.
   *
   * <ul>
   * <li>Both foreground and background layers should be sized at 108 x 108 dp.</li>
   * <li>The inner 72 x 72 dp  of the icon appears within the masked viewport.</li>
   * <li>The outer 18 dp on each of the 4 sides of the layers is reserved for use by the system UI
   * surfaces to create interesting visual effects, such as parallax or pulsing.</li>
   * </ul>
   *
   * Such motion effect is achieved by internally setting the bounds of the foreground and
   * background layer as following:
   * <pre>
   * Rect(getBounds().left - getBounds().getWidth() * #getExtraInsetFraction(),
   *      getBounds().top - getBounds().getHeight() * #getExtraInsetFraction(),
   *      getBounds().right + getBounds().getWidth() * #getExtraInsetFraction(),
   *      getBounds().bottom + getBounds().getHeight() * #getExtraInsetFraction())
   * </pre>
   */
  public class AdaptiveIconDrawable extends Drawable implements Drawable.Callback {
  
      /**
       * Mask path is defined inside device configuration in following dimension: [100 x 100]
       * @hide
       */
      @TestApi
      public static final float MASK_SIZE = 100f;
  
      /**
       * Launcher icons design guideline
       */
      private static final float SAFEZONE_SCALE = 66f/72f;
  
      /**
       * All four sides of the layers are padded with extra inset so as to provide
       * extra content to reveal within the clip path when performing affine transformations on the
       * layers.
       *
       * Each layers will reserve 25% of it's width and height.
       *
       * As a result, the view port of the layers is smaller than their intrinsic width and height.
       */
      private static final float EXTRA_INSET_PERCENTAGE = 1 / 4f;
      private static final float DEFAULT_VIEW_PORT_SCALE = 1f / (1 + 2 * EXTRA_INSET_PERCENTAGE);
  
      /**
       * Clip path defined in R.string.config_icon_mask.
       */
      private static Path sMask;
  
      /**
       * Scaled mask based on the view bounds.
       */
      private final Path mMask;
      private final Path mMaskScaleOnly;
      private final Matrix mMaskMatrix;
      private final Region mTransparentRegion;
  
      /**
       * 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.
       */
      AdaptiveIconDrawable() {
          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.
       */
      AdaptiveIconDrawable(@Nullable LayerState state, @Nullable Resources res) {
          mLayerState = createConstantState(state, res);
          // config_icon_mask from context bound resource may have been chaged using
          // OverlayManager. Read that one first.
          Resources r = ActivityThread.currentActivityThread() == null
                  ? Resources.getSystem()
                  : ActivityThread.currentActivityThread().getApplication().getResources();
          // TODO: either make sMask update only when config_icon_mask changes OR
          // get rid of it all-together in layoutlib
          sMask = PathParser.createPathFromPathData(r.getString(R.string.config_icon_mask));
          mMask = new Path(sMask);
          mMaskScaleOnly = new Path(mMask);
          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 AdaptiveIconDrawable(Drawable backgroundDrawable,
              Drawable foregroundDrawable) {
          this((LayerState)null, null);
          if (backgroundDrawable != null) {
              addLayer(BACKGROUND_ID, createChildDrawable(backgroundDrawable));
          }
          if (foregroundDrawable != null) {
              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 deviceDensity = Drawable.resolveDensity(r, 0);
          state.setDensity(deviceDensity);
          state.mSrcDensityOverride = mSrcDensityOverride;
  
          final ChildDrawable[] array = state.mChildren;
          for (int i = 0; i < state.mChildren.length; i++) {
              final ChildDrawable layer = array[i];
              layer.setDensity(deviceDensity);
          }
  
          inflateLayers(r, parser, attrs, theme);
      }
  
      /**
       * All four sides of the layers are padded with extra inset so as to provide
       * extra content to reveal within the clip path when performing affine transformations on the
       * layers.
       *
       * @see #getForeground() and #getBackground() for more info on how this value is used
       */
      public static float getExtraInsetFraction() {
          return EXTRA_INSET_PERCENTAGE;
      }
  
      /**
       * @hide
       */
      public static float getExtraInsetPercentage() {
          return EXTRA_INSET_PERCENTAGE;
      }
  
      /**
       * When called before the bound is set, the returned path is identical to
       * R.string.config_icon_mask. After the bound is set, the
       * returned path's computed bound is same as the #getBounds().
       *
       * @return the mask path object used to clip the drawable
       */
      public Path getIconMask() {
          return mMask;
      }
  
      /**
       * Returns the foreground drawable managed by this class. The bound of this drawable is
       * extended by {@link #getExtraInsetFraction()} * getBounds().width on left/right sides and by
       * {@link #getExtraInsetFraction()} * getBounds().height on top/bottom sides.
       *
       * @return the foreground drawable managed by this drawable
       */
      public Drawable getForeground() {
          return mLayerState.mChildren[FOREGROUND_ID].mDrawable;
      }
  
      /**
       * Returns the foreground drawable managed by this class. The bound of this drawable is
       * extended by {@link #getExtraInsetFraction()} * getBounds().width on left/right sides and by
       * {@link #getExtraInsetFraction()} * getBounds().height on top/bottom sides.
       *
       * @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) {
          if (bounds.isEmpty()) {
              return;
          }
          try {
              suspendChildInvalidation();
              updateLayerBoundsInternal(bounds);
              updateMaskBoundsInternal(bounds);
          } finally {
              resumeChildInvalidation();
          }
      }
  
      /**
       * Set the child layer bounds bigger than the view port size by {@link #DEFAULT_VIEW_PORT_SCALE}
       */
      private void updateLayerBoundsInternal(Rect bounds) {
          int cX = bounds.width() / 2;
          int cY = bounds.height() / 2;
  
          for (int i = 0, count = mLayerState.N_CHILDREN; i < count; i++) {
              final ChildDrawable r = mLayerState.mChildren[i];
              if (r == null) {
                  continue;
              }
              final Drawable d = r.mDrawable;
              if (d == null) {
                  continue;
              }
  
              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);
  
              d.setBounds(outRect);
          }
      }
  
      private void updateMaskBoundsInternal(Rect b) {
          // reset everything that depends on the view bounds
          mMaskMatrix.setScale(b.width() / MASK_SIZE, b.height() / MASK_SIZE);
          sMask.transform(mMaskMatrix, mMaskScaleOnly);
  
          mMaskMatrix.postTranslate(b.left, b.top);
          sMask.transform(mMaskMatrix, mMask);
  
          if (mLayersBitmap == null || mLayersBitmap.getWidth() != b.width()
                  || mLayersBitmap.getHeight() != b.height()) {
              mLayersBitmap = Bitmap.createBitmap(b.width(), b.height(), Bitmap.Config.ARGB_8888);
          }
  
          mPaint.setShader(null);
          mTransparentRegion.setEmpty();
          mLayersShader = null;
      }
  
      @Override
      public void draw(Canvas canvas) {
          if (mLayersBitmap == null) {
              return;
          }
          if (mLayersShader == null) {
              mCanvas.setBitmap(mLayersBitmap);
              mCanvas.drawColor(Color.BLACK);
              for (int i = 0; i < mLayerState.N_CHILDREN; i++) {
                  if (mLayerState.mChildren[i] == null) {
                      continue;
                  }
                  final Drawable dr = mLayerState.mChildren[i].mDrawable;
                  if (dr != null) {
                      dr.draw(mCanvas);
                  }
              }
              mLayersShader = new BitmapShader(mLayersBitmap, TileMode.CLAMP, TileMode.CLAMP);
              mPaint.setShader(mLayersShader);
          }
          if (mMaskScaleOnly != null) {
              Rect bounds = getBounds();
              canvas.translate(bounds.left, bounds.top);
              canvas.drawPath(mMaskScaleOnly, mPaint);
              canvas.translate(-bounds.left, -bounds.top);
          }
      }
  
      @Override
      public void invalidateSelf() {
          mLayersShader = null;
          super.invalidateSelf();
      }
  
      @Override
      public void getOutline(@NonNull Outline outline) {
          outline.setConvexPath(mMask);
      }
  
      /** @hide */
      @TestApi
      public Region getSafeZone() {
          mMaskMatrix.reset();
          mMaskMatrix.setScale(SAFEZONE_SCALE, SAFEZONE_SCALE, getBounds().centerX(), getBounds().centerY());
          Path p = new Path();
          mMask.transform(mMaskMatrix, p);
          Region safezoneRegion = new Region(getBounds());
          safezoneRegion.setPath(p, safezoneRegion);
          return safezoneRegion;
      }
  
      @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.AdaptiveIconDrawableLayer);
                  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.
       */
      private 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.AdaptiveIconDrawableLayer);
              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()
                              + ": <foreground> or <background> tag requires a 'drawable'"
                              + "attribute or child tag defining a drawable");
                  }
  
                  // We found a child drawable. Take ownership.
                  layer.mDrawable = Drawable.createFromXmlInnerForDensity(r, parser, attrs,
                          mLayerState.mSrcDensityOverride, 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.getDrawableForDensity(R.styleable.AdaptiveIconDrawableLayer_drawable,
                  state.mSrcDensityOverride);
          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 != null && 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) {
          mPaint.setAlpha(alpha);
      }
  
      @Override
      public int getAlpha() {
          return mPaint.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 setTintBlendMode(@NonNull BlendMode blendMode) {
          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.setTintBlendMode(blendMode);
              }
          }
      }
  
      public void setOpacity(int opacity) {
          mLayerState.mOpacityOverride = opacity;
      }
  
      @Override
      public int getOpacity() {
          return PixelFormat.TRANSLUCENT;
      }
  
      @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();
      }
  
      /** @hide */
      @Override
      public boolean hasFocusStateSpecified() {
          return mLayerState.hasFocusStateSpecified();
      }
  
      @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];
              if (r.mDrawable == null) {
                  continue;
              }
              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];
              if (r.mDrawable == null) {
                  continue;
              }
              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 AdaptiveIconDrawable 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;
  
          // The density at which to render the drawable and its children.
          int mDensity;
  
          // The density to use when inflating/looking up the children drawables. A value of 0 means
          // use the system's density.
          int mSrcDensityOverride = 0;
  
          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 AdaptiveIconDrawable 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;
                  mSrcDensityOverride = orig.mSrcDensityOverride;
              } 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 AdaptiveIconDrawable(this, null);
          }
  
          @Override
          public Drawable newDrawable(@Nullable Resources res) {
              return new AdaptiveIconDrawable(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 hasFocusStateSpecified() {
              final ChildDrawable[] array = mChildren;
              for (int i = 0; i < N_CHILDREN; i++) {
                  final Drawable dr = array[i].mDrawable;
                  if (dr != null && dr.hasFocusStateSpecified()) {
                      return true;
                  }
              }
              return false;
          }
  
          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;
          }
      }
  }
  