content: /*
   * Copyright (C) 2006 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.UnsupportedAppUsage;
  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.BlendMode;
  import android.graphics.Canvas;
  import android.graphics.ColorFilter;
  import android.graphics.Outline;
  import android.graphics.PixelFormat;
  import android.graphics.Rect;
  import android.util.AttributeSet;
  import android.util.DisplayMetrics;
  import android.util.LayoutDirection;
  import android.util.Log;
  import android.view.Gravity;
  import android.view.View;
  
  import com.android.internal.R;
  
  import org.xmlpull.v1.XmlPullParser;
  import org.xmlpull.v1.XmlPullParserException;
  
  import java.io.IOException;
  
  /**
   * A Drawable that manages an array of other Drawables. These are drawn in array
   * order, so the element with the largest index will be drawn on top.
   * <p>
   * It can be defined in an XML file with the <code>&lt;layer-list></code> element.
   * Each Drawable in the layer is defined in a nested <code>&lt;item></code>.
   * <p>
   * For more information, see the guide to
   * <a href="{@docRoot}guide/topics/resources/drawable-resource.html">Drawable Resources</a>.
   *
   * @attr ref android.R.styleable#LayerDrawable_paddingMode
   * @attr ref android.R.styleable#LayerDrawableItem_left
   * @attr ref android.R.styleable#LayerDrawableItem_top
   * @attr ref android.R.styleable#LayerDrawableItem_right
   * @attr ref android.R.styleable#LayerDrawableItem_bottom
   * @attr ref android.R.styleable#LayerDrawableItem_start
   * @attr ref android.R.styleable#LayerDrawableItem_end
   * @attr ref android.R.styleable#LayerDrawableItem_width
   * @attr ref android.R.styleable#LayerDrawableItem_height
   * @attr ref android.R.styleable#LayerDrawableItem_gravity
   * @attr ref android.R.styleable#LayerDrawableItem_drawable
   * @attr ref android.R.styleable#LayerDrawableItem_id
  */
  public class LayerDrawable extends Drawable implements Drawable.Callback {
      private static final String LOG_TAG = "LayerDrawable";
  
      /**
       * Padding mode used to nest each layer inside the padding of the previous
       * layer.
       *
       * @see #setPaddingMode(int)
       */
      public static final int PADDING_MODE_NEST = 0;
  
      /**
       * Padding mode used to stack each layer directly atop the previous layer.
       *
       * @see #setPaddingMode(int)
       */
      public static final int PADDING_MODE_STACK = 1;
  
      /**
       * Value used for undefined start and end insets.
       *
       * @see #getLayerInsetStart(int)
       * @see #getLayerInsetEnd(int)
       */
      public static final int INSET_UNDEFINED = Integer.MIN_VALUE;
  
      @NonNull
      @UnsupportedAppUsage
      LayerState mLayerState;
  
      private int[] mPaddingL;
      private int[] mPaddingT;
      private int[] mPaddingR;
      private int[] mPaddingB;
  
      private final Rect mTmpRect = new Rect();
      private final Rect mTmpOutRect = new Rect();
      private final Rect mTmpContainer = new Rect();
      private Rect mHotspotBounds;
      private boolean mMutated;
  
      private boolean mSuspendChildInvalidation;
      private boolean mChildRequestedInvalidation;
  
      /**
       * Creates a new layer drawable with the list of specified layers.
       *
       * @param layers a list of drawables to use as layers in this new drawable,
       *               must be non-null
       */
      public LayerDrawable(@NonNull Drawable[] layers) {
          this(layers, null);
      }
  
      /**
       * Creates a new layer drawable with the specified list of layers and the
       * specified constant state.
       *
       * @param layers The list of layers to add to this drawable.
       * @param state The constant drawable state.
       */
      LayerDrawable(@NonNull Drawable[] layers, @Nullable LayerState state) {
          this(state, null);
  
          if (layers == null) {
              throw new IllegalArgumentException("layers must be non-null");
          }
  
          final int length = layers.length;
          final ChildDrawable[] r = new ChildDrawable[length];
          for (int i = 0; i < length; i++) {
              r[i] = new ChildDrawable(mLayerState.mDensity);
              Drawable child = layers[i];
              r[i].mDrawable = child;
              if (child != null) {
                  child.setCallback(this);
                  mLayerState.mChildrenChangingConfigurations |= child.getChangingConfigurations();
              }
          }
          mLayerState.mNumChildren = length;
          mLayerState.mChildren = r;
  
          ensurePadding();
          refreshPadding();
      }
  
      LayerDrawable() {
          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.
       */
      LayerDrawable(@Nullable LayerState state, @Nullable Resources res) {
          mLayerState = createConstantState(state, res);
          if (mLayerState.mNumChildren > 0) {
              ensurePadding();
              refreshPadding();
          }
      }
  
      LayerState createConstantState(@Nullable LayerState state, @Nullable Resources res) {
          return new LayerState(state, this, res);
      }
  
      @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);
  
          // The density may have changed since the last update. This will
          // apply scaling to any existing constant state properties.
          final LayerState state = mLayerState;
          final int density = Drawable.resolveDensity(r, 0);
          state.setDensity(density);
  
          final TypedArray a = obtainAttributes(r, theme, attrs, R.styleable.LayerDrawable);
          updateStateFromTypedArray(a);
          a.recycle();
  
          final ChildDrawable[] array = state.mChildren;
          final int N = state.mNumChildren;
          for (int i = 0; i < N; i++) {
              final ChildDrawable layer = array[i];
              layer.setDensity(density);
          }
  
          inflateLayers(r, parser, attrs, theme);
  
          ensurePadding();
          refreshPadding();
      }
  
      @Override
      public void applyTheme(@NonNull Theme t) {
          super.applyTheme(t);
  
          final LayerState state = mLayerState;
          final int density = Drawable.resolveDensity(t.getResources(), 0);
          state.setDensity(density);
  
          if (state.mThemeAttrs != null) {
              final TypedArray a = t.resolveAttributes(
                      state.mThemeAttrs, R.styleable.LayerDrawable);
              updateStateFromTypedArray(a);
              a.recycle();
          }
  
          final ChildDrawable[] array = state.mChildren;
          final int N = state.mNumChildren;
          for (int i = 0; i < N; i++) {
              final ChildDrawable layer = array[i];
              layer.setDensity(density);
  
              if (layer.mThemeAttrs != null) {
                  final TypedArray a = t.resolveAttributes(
                          layer.mThemeAttrs, R.styleable.LayerDrawableItem);
                  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;
          while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
                  && ((depth = parser.getDepth()) >= innerDepth || type != XmlPullParser.END_TAG)) {
              if (type != XmlPullParser.START_TAG) {
                  continue;
              }
  
              if (depth > innerDepth || !parser.getName().equals("item")) {
                  continue;
              }
  
              final ChildDrawable layer = new ChildDrawable(state.mDensity);
              final TypedArray a = obtainAttributes(r, theme, attrs, R.styleable.LayerDrawableItem);
              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 ||
                      layer.mThemeAttrs[R.styleable.LayerDrawableItem_drawable] == 0)) {
                  while ((type = parser.next()) == XmlPullParser.TEXT) {
                  }
                  if (type != XmlPullParser.START_TAG) {
                      throw new XmlPullParserException(parser.getPositionDescription()
                              + ": <item> tag requires a '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(layer);
          }
      }
  
      /**
       * Initializes the constant state from the values in the typed array.
       */
      private void updateStateFromTypedArray(@NonNull TypedArray a) {
          final LayerState state = mLayerState;
  
          // Account for any configuration changes.
          state.mChangingConfigurations |= a.getChangingConfigurations();
  
          // Extract the theme attributes, if any.
          state.mThemeAttrs = a.extractThemeAttrs();
  
          final int N = a.getIndexCount();
          for (int i = 0; i < N; i++) {
              final int attr = a.getIndex(i);
              switch (attr) {
                  case R.styleable.LayerDrawable_opacity:
                      state.mOpacityOverride = a.getInt(attr, state.mOpacityOverride);
                      break;
                  case R.styleable.LayerDrawable_paddingTop:
                      state.mPaddingTop = a.getDimensionPixelOffset(attr, state.mPaddingTop);
                      break;
                  case R.styleable.LayerDrawable_paddingBottom:
                      state.mPaddingBottom = a.getDimensionPixelOffset(attr, state.mPaddingBottom);
                      break;
                  case R.styleable.LayerDrawable_paddingLeft:
                      state.mPaddingLeft = a.getDimensionPixelOffset(attr, state.mPaddingLeft);
                      break;
                  case R.styleable.LayerDrawable_paddingRight:
                      state.mPaddingRight = a.getDimensionPixelOffset(attr, state.mPaddingRight);
                      break;
                  case R.styleable.LayerDrawable_paddingStart:
                      state.mPaddingStart = a.getDimensionPixelOffset(attr, state.mPaddingStart);
                      break;
                  case R.styleable.LayerDrawable_paddingEnd:
                      state.mPaddingEnd = a.getDimensionPixelOffset(attr, state.mPaddingEnd);
                      break;
                  case R.styleable.LayerDrawable_autoMirrored:
                      state.mAutoMirrored = a.getBoolean(attr, state.mAutoMirrored);
                      break;
                  case R.styleable.LayerDrawable_paddingMode:
                      state.mPaddingMode = a.getInteger(attr, state.mPaddingMode);
                      break;
              }
          }
      }
  
      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();
  
          final int N = a.getIndexCount();
          for (int i = 0; i < N; i++) {
              final int attr = a.getIndex(i);
              switch (attr) {
                  case R.styleable.LayerDrawableItem_left:
                      layer.mInsetL = a.getDimensionPixelOffset(attr, layer.mInsetL);
                      break;
                  case R.styleable.LayerDrawableItem_top:
                      layer.mInsetT = a.getDimensionPixelOffset(attr, layer.mInsetT);
                      break;
                  case R.styleable.LayerDrawableItem_right:
                      layer.mInsetR = a.getDimensionPixelOffset(attr, layer.mInsetR);
                      break;
                  case R.styleable.LayerDrawableItem_bottom:
                      layer.mInsetB = a.getDimensionPixelOffset(attr, layer.mInsetB);
                      break;
                  case R.styleable.LayerDrawableItem_start:
                      layer.mInsetS = a.getDimensionPixelOffset(attr, layer.mInsetS);
                      break;
                  case R.styleable.LayerDrawableItem_end:
                      layer.mInsetE = a.getDimensionPixelOffset(attr, layer.mInsetE);
                      break;
                  case R.styleable.LayerDrawableItem_width:
                      layer.mWidth = a.getDimensionPixelSize(attr, layer.mWidth);
                      break;
                  case R.styleable.LayerDrawableItem_height:
                      layer.mHeight = a.getDimensionPixelSize(attr, layer.mHeight);
                      break;
                  case R.styleable.LayerDrawableItem_gravity:
                      layer.mGravity = a.getInteger(attr, layer.mGravity);
                      break;
                  case R.styleable.LayerDrawableItem_id:
                      layer.mId = a.getResourceId(attr, layer.mId);
                      break;
              }
          }
  
          final Drawable dr = a.getDrawable(R.styleable.LayerDrawableItem_drawable);
          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.canApplyTheme() || super.canApplyTheme();
      }
  
      /**
       * @hide
       */
      @Override
      public boolean isProjected() {
          if (super.isProjected()) {
              return true;
          }
  
          final ChildDrawable[] layers = mLayerState.mChildren;
          final int N = mLayerState.mNumChildren;
          for (int i = 0; i < N; i++) {
              Drawable childDrawable = layers[i].mDrawable;
              if (childDrawable != null && childDrawable.isProjected()) {
                  return true;
              }
          }
  
          return false;
      }
  
      /**
       * Adds a new layer at the end of list of layers and returns its index.
       *
       * @param layer The layer to add.
       * @return The index of the layer.
       */
      @UnsupportedAppUsage
      int addLayer(@NonNull ChildDrawable layer) {
          final LayerState st = mLayerState;
          final int N = st.mChildren != null ? st.mChildren.length : 0;
          final int i = st.mNumChildren;
          if (i >= N) {
              final ChildDrawable[] nu = new ChildDrawable[N + 10];
              if (i > 0) {
                  System.arraycopy(st.mChildren, 0, nu, 0, i);
              }
  
              st.mChildren = nu;
          }
  
          st.mChildren[i] = layer;
          st.mNumChildren++;
          st.invalidateCache();
          return i;
      }
  
      /**
       * Add a new layer to this drawable. The new layer is identified by an id.
       *
       * @param dr The drawable to add as a layer.
       * @param themeAttrs Theme attributes extracted from the layer.
       * @param id The id of the new layer.
       * @param left The left padding of the new layer.
       * @param top The top padding of the new layer.
       * @param right The right padding of the new layer.
       * @param bottom The bottom padding of the new layer.
       */
      ChildDrawable addLayer(Drawable dr, int[] themeAttrs, int id,
              int left, int top, int right, int bottom) {
          final ChildDrawable childDrawable = createLayer(dr);
          childDrawable.mId = id;
          childDrawable.mThemeAttrs = themeAttrs;
          childDrawable.mDrawable.setAutoMirrored(isAutoMirrored());
          childDrawable.mInsetL = left;
          childDrawable.mInsetT = top;
          childDrawable.mInsetR = right;
          childDrawable.mInsetB = bottom;
  
          addLayer(childDrawable);
  
          mLayerState.mChildrenChangingConfigurations |= dr.getChangingConfigurations();
          dr.setCallback(this);
  
          return childDrawable;
      }
  
      private ChildDrawable createLayer(Drawable dr) {
          final ChildDrawable layer = new ChildDrawable(mLayerState.mDensity);
          layer.mDrawable = dr;
          return layer;
      }
  
      /**
       * Adds a new layer containing the specified {@code drawable} to the end of
       * the layer list and returns its index.
       *
       * @param dr The drawable to add as a new layer.
       * @return The index of the new layer.
       */
      public int addLayer(Drawable dr) {
          final ChildDrawable layer = createLayer(dr);
          final int index = addLayer(layer);
          ensurePadding();
          refreshChildPadding(index, layer);
          return index;
      }
  
      /**
       * Looks for a layer with the given ID and returns its {@link Drawable}.
       * <p>
       * If multiple layers are found for the given ID, returns the
       * {@link Drawable} for the matching layer at the highest index.
       *
       * @param id The layer ID to search for.
       * @return The {@link Drawable} for the highest-indexed layer that has the
       *         given ID, or null if not found.
       */
      public Drawable findDrawableByLayerId(int id) {
          final ChildDrawable[] layers = mLayerState.mChildren;
          for (int i = mLayerState.mNumChildren - 1; i >= 0; i--) {
              if (layers[i].mId == id) {
                  return layers[i].mDrawable;
              }
          }
  
          return null;
      }
  
      /**
       * Sets the ID of a layer.
       *
       * @param index The index of the layer to modify, must be in the range
       *              {@code 0...getNumberOfLayers()-1}.
       * @param id The id to assign to the layer.
       *
       * @see #getId(int)
       * @attr ref android.R.styleable#LayerDrawableItem_id
       */
      public void setId(int index, int id) {
          mLayerState.mChildren[index].mId = id;
      }
  
      /**
       * Returns the ID of the specified layer.
       *
       * @param index The index of the layer, must be in the range
       *              {@code 0...getNumberOfLayers()-1}.
       * @return The id of the layer or {@link android.view.View#NO_ID} if the
       *         layer has no id.
       *
       * @see #setId(int, int)
       * @attr ref android.R.styleable#LayerDrawableItem_id
       */
      public int getId(int index) {
          if (index >= mLayerState.mNumChildren) {
              throw new IndexOutOfBoundsException();
          }
          return mLayerState.mChildren[index].mId;
      }
  
      /**
       * Returns the number of layers contained within this layer drawable.
       *
       * @return The number of layers.
       */
      public int getNumberOfLayers() {
          return mLayerState.mNumChildren;
      }
  
      /**
       * Replaces the {@link Drawable} for the layer with the given id.
       *
       * @param id The layer ID to search for.
       * @param drawable The replacement {@link Drawable}.
       * @return Whether the {@link Drawable} was replaced (could return false if
       *         the id was not found).
       */
      public boolean setDrawableByLayerId(int id, Drawable drawable) {
          final int index = findIndexByLayerId(id);
          if (index < 0) {
              return false;
          }
  
          setDrawable(index, drawable);
          return true;
      }
  
      /**
       * Returns the layer with the specified {@code id}.
       * <p>
       * If multiple layers have the same ID, returns the layer with the lowest
       * index.
       *
       * @param id The ID of the layer to return.
       * @return The index of the layer with the specified ID.
       */
      public int findIndexByLayerId(int id) {
          final ChildDrawable[] layers = mLayerState.mChildren;
          final int N = mLayerState.mNumChildren;
          for (int i = 0; i < N; i++) {
              final ChildDrawable childDrawable = layers[i];
              if (childDrawable.mId == id) {
                  return i;
              }
          }
  
          return -1;
      }
  
      /**
       * Sets the drawable for the layer at the specified index.
       *
       * @param index The index of the layer to modify, must be in the range
       *              {@code 0...getNumberOfLayers()-1}.
       * @param drawable The drawable to set for the layer.
       *
       * @see #getDrawable(int)
       * @attr ref android.R.styleable#LayerDrawableItem_drawable
       */
      public void setDrawable(int index, Drawable drawable) {
          if (index >= mLayerState.mNumChildren) {
              throw new IndexOutOfBoundsException();
          }
  
          final ChildDrawable[] layers = mLayerState.mChildren;
          final ChildDrawable childDrawable = layers[index];
          if (childDrawable.mDrawable != null) {
              if (drawable != null) {
                  final Rect bounds = childDrawable.mDrawable.getBounds();
                  drawable.setBounds(bounds);
              }
  
              childDrawable.mDrawable.setCallback(null);
          }
  
          if (drawable != null) {
              drawable.setCallback(this);
          }
  
          childDrawable.mDrawable = drawable;
          mLayerState.invalidateCache();
  
          refreshChildPadding(index, childDrawable);
      }
  
      /**
       * Returns the drawable for the layer at the specified index.
       *
       * @param index The index of the layer, must be in the range
       *              {@code 0...getNumberOfLayers()-1}.
       * @return The {@link Drawable} at the specified layer index.
       *
       * @see #setDrawable(int, Drawable)
       * @attr ref android.R.styleable#LayerDrawableItem_drawable
       */
      public Drawable getDrawable(int index) {
          if (index >= mLayerState.mNumChildren) {
              throw new IndexOutOfBoundsException();
          }
          return mLayerState.mChildren[index].mDrawable;
      }
  
      /**
       * Sets an explicit size for the specified layer.
       * <p>
       * <strong>Note:</strong> Setting an explicit layer size changes the
       * default layer gravity behavior. See {@link #setLayerGravity(int, int)}
       * for more information.
       *
       * @param index the index of the layer to adjust
       * @param w width in pixels, or -1 to use the intrinsic width
       * @param h height in pixels, or -1 to use the intrinsic height
       * @see #getLayerWidth(int)
       * @see #getLayerHeight(int)
       * @attr ref android.R.styleable#LayerDrawableItem_width
       * @attr ref android.R.styleable#LayerDrawableItem_height
       */
      public void setLayerSize(int index, int w, int h) {
          final ChildDrawable childDrawable = mLayerState.mChildren[index];
          childDrawable.mWidth = w;
          childDrawable.mHeight = h;
      }
  
      /**
       * @param index the index of the layer to adjust
       * @param w width in pixels, or -1 to use the intrinsic width
       * @attr ref android.R.styleable#LayerDrawableItem_width
       */
      public void setLayerWidth(int index, int w) {
          final ChildDrawable childDrawable = mLayerState.mChildren[index];
          childDrawable.mWidth = w;
      }
  
      /**
       * @param index the index of the drawable to adjust
       * @return the explicit width of the layer, or -1 if not specified
       * @see #setLayerSize(int, int, int)
       * @attr ref android.R.styleable#LayerDrawableItem_width
       */
      public int getLayerWidth(int index) {
          final ChildDrawable childDrawable = mLayerState.mChildren[index];
          return childDrawable.mWidth;
      }
  
      /**
       * @param index the index of the layer to adjust
       * @param h height in pixels, or -1 to use the intrinsic height
       * @attr ref android.R.styleable#LayerDrawableItem_height
       */
      public void setLayerHeight(int index, int h) {
          final ChildDrawable childDrawable = mLayerState.mChildren[index];
          childDrawable.mHeight = h;
      }
  
      /**
       * @param index the index of the drawable to adjust
       * @return the explicit height of the layer, or -1 if not specified
       * @see #setLayerSize(int, int, int)
       * @attr ref android.R.styleable#LayerDrawableItem_height
       */
      public int getLayerHeight(int index) {
          final ChildDrawable childDrawable = mLayerState.mChildren[index];
          return childDrawable.mHeight;
      }
  
      /**
       * Sets the gravity used to position or stretch the specified layer within
       * its container. Gravity is applied after any layer insets (see
       * {@link #setLayerInset(int, int, int, int, int)}) or padding (see
       * {@link #setPaddingMode(int)}).
       * <p>
       * If gravity is specified as {@link Gravity#NO_GRAVITY}, the default
       * behavior depends on whether an explicit width or height has been set
       * (see {@link #setLayerSize(int, int, int)}), If a dimension is not set,
       * gravity in that direction defaults to {@link Gravity#FILL_HORIZONTAL} or
       * {@link Gravity#FILL_VERTICAL}; otherwise, gravity in that direction
       * defaults to {@link Gravity#LEFT} or {@link Gravity#TOP}.
       *
       * @param index the index of the drawable to adjust
       * @param gravity the gravity to set for the layer
       *
       * @see #getLayerGravity(int)
       * @attr ref android.R.styleable#LayerDrawableItem_gravity
       */
      public void setLayerGravity(int index, int gravity) {
          final ChildDrawable childDrawable = mLayerState.mChildren[index];
          childDrawable.mGravity = gravity;
      }
  
      /**
       * @param index the index of the layer
       * @return the gravity used to position or stretch the specified layer
       *         within its container
       *
       * @see #setLayerGravity(int, int)
       * @attr ref android.R.styleable#LayerDrawableItem_gravity
       */
      public int getLayerGravity(int index) {
          final ChildDrawable childDrawable = mLayerState.mChildren[index];
          return childDrawable.mGravity;
      }
  
      /**
       * Specifies the insets in pixels for the drawable at the specified index.
       *
       * @param index the index of the drawable to adjust
       * @param l number of pixels to add to the left bound
       * @param t number of pixels to add to the top bound
       * @param r number of pixels to subtract from the right bound
       * @param b number of pixels to subtract from the bottom bound
       *
       * @attr ref android.R.styleable#LayerDrawableItem_left
       * @attr ref android.R.styleable#LayerDrawableItem_top
       * @attr ref android.R.styleable#LayerDrawableItem_right
       * @attr ref android.R.styleable#LayerDrawableItem_bottom
       */
      public void setLayerInset(int index, int l, int t, int r, int b) {
          setLayerInsetInternal(index, l, t, r, b, INSET_UNDEFINED, INSET_UNDEFINED);
      }
  
      /**
       * Specifies the relative insets in pixels for the drawable at the
       * specified index.
       *
       * @param index the index of the layer to adjust
       * @param s number of pixels to inset from the start bound
       * @param t number of pixels to inset from the top bound
       * @param e number of pixels to inset from the end bound
       * @param b number of pixels to inset from the bottom bound
       *
       * @attr ref android.R.styleable#LayerDrawableItem_start
       * @attr ref android.R.styleable#LayerDrawableItem_top
       * @attr ref android.R.styleable#LayerDrawableItem_end
       * @attr ref android.R.styleable#LayerDrawableItem_bottom
       */
      public void setLayerInsetRelative(int index, int s, int t, int e, int b) {
          setLayerInsetInternal(index, 0, t, 0, b, s, e);
      }
  
      /**
       * @param index the index of the layer to adjust
       * @param l number of pixels to inset from the left bound
       * @attr ref android.R.styleable#LayerDrawableItem_left
       */
      public void setLayerInsetLeft(int index, int l) {
          final ChildDrawable childDrawable = mLayerState.mChildren[index];
          childDrawable.mInsetL = l;
      }
  
      /**
       * @param index the index of the layer
       * @return number of pixels to inset from the left bound
       * @attr ref android.R.styleable#LayerDrawableItem_left
       */
      public int getLayerInsetLeft(int index) {
          final ChildDrawable childDrawable = mLayerState.mChildren[index];
          return childDrawable.mInsetL;
      }
  
      /**
       * @param index the index of the layer to adjust
       * @param r number of pixels to inset from the right bound
       * @attr ref android.R.styleable#LayerDrawableItem_right
       */
      public void setLayerInsetRight(int index, int r) {
          final ChildDrawable childDrawable = mLayerState.mChildren[index];
          childDrawable.mInsetR = r;
      }
  
      /**
       * @param index the index of the layer
       * @return number of pixels to inset from the right bound
       * @attr ref android.R.styleable#LayerDrawableItem_right
       */
      public int getLayerInsetRight(int index) {
          final ChildDrawable childDrawable = mLayerState.mChildren[index];
          return childDrawable.mInsetR;
      }
  
      /**
       * @param index the index of the layer to adjust
       * @param t number of pixels to inset from the top bound
       * @attr ref android.R.styleable#LayerDrawableItem_top
       */
      public void setLayerInsetTop(int index, int t) {
          final ChildDrawable childDrawable = mLayerState.mChildren[index];
          childDrawable.mInsetT = t;
      }
  
      /**
       * @param index the index of the layer
       * @return number of pixels to inset from the top bound
       * @attr ref android.R.styleable#LayerDrawableItem_top
       */
      public int getLayerInsetTop(int index) {
          final ChildDrawable childDrawable = mLayerState.mChildren[index];
          return childDrawable.mInsetT;
      }
  
      /**
       * @param index the index of the layer to adjust
       * @param b number of pixels to inset from the bottom bound
       * @attr ref android.R.styleable#LayerDrawableItem_bottom
       */
      public void setLayerInsetBottom(int index, int b) {
          final ChildDrawable childDrawable = mLayerState.mChildren[index];
          childDrawable.mInsetB = b;
      }
  
      /**
       * @param index the index of the layer
       * @return number of pixels to inset from the bottom bound
       * @attr ref android.R.styleable#LayerDrawableItem_bottom
       */
      public int getLayerInsetBottom(int index) {
          final ChildDrawable childDrawable = mLayerState.mChildren[index];
          return childDrawable.mInsetB;
      }
  
      /**
       * @param index the index of the layer to adjust
       * @param s number of pixels to inset from the start bound
       * @attr ref android.R.styleable#LayerDrawableItem_start
       */
      public void setLayerInsetStart(int index, int s) {
          final ChildDrawable childDrawable = mLayerState.mChildren[index];
          childDrawable.mInsetS = s;
      }
  
      /**
       * @param index the index of the layer
       * @return the number of pixels to inset from the start bound, or
       *         {@link #INSET_UNDEFINED} if not specified
       * @attr ref android.R.styleable#LayerDrawableItem_start
       */
      public int getLayerInsetStart(int index) {
          final ChildDrawable childDrawable = mLayerState.mChildren[index];
          return childDrawable.mInsetS;
      }
  
      /**
       * @param index the index of the layer to adjust
       * @param e number of pixels to inset from the end bound, or
       *         {@link #INSET_UNDEFINED} if not specified
       * @attr ref android.R.styleable#LayerDrawableItem_end
       */
      public void setLayerInsetEnd(int index, int e) {
          final ChildDrawable childDrawable = mLayerState.mChildren[index];
          childDrawable.mInsetE = e;
      }
  
      /**
       * @param index the index of the layer
       * @return number of pixels to inset from the end bound
       * @attr ref android.R.styleable#LayerDrawableItem_end
       */
      public int getLayerInsetEnd(int index) {
          final ChildDrawable childDrawable = mLayerState.mChildren[index];
          return childDrawable.mInsetE;
      }
  
      private void setLayerInsetInternal(int index, int l, int t, int r, int b, int s, int e) {
          final ChildDrawable childDrawable = mLayerState.mChildren[index];
          childDrawable.mInsetL = l;
          childDrawable.mInsetT = t;
          childDrawable.mInsetR = r;
          childDrawable.mInsetB = b;
          childDrawable.mInsetS = s;
          childDrawable.mInsetE = e;
      }
  
      /**
       * Specifies how layer padding should affect the bounds of subsequent
       * layers. The default value is {@link #PADDING_MODE_NEST}.
       *
       * @param mode padding mode, one of:
       *            <ul>
       *            <li>{@link #PADDING_MODE_NEST} to nest each layer inside the
       *            padding of the previous layer
       *            <li>{@link #PADDING_MODE_STACK} to stack each layer directly
       *            atop the previous layer
       *            </ul>
       *
       * @see #getPaddingMode()
       * @attr ref android.R.styleable#LayerDrawable_paddingMode
       */
      public void setPaddingMode(int mode) {
          if (mLayerState.mPaddingMode != mode) {
              mLayerState.mPaddingMode = mode;
          }
      }
  
      /**
       * @return the current padding mode
       *
       * @see #setPaddingMode(int)
       * @attr ref android.R.styleable#LayerDrawable_paddingMode
       */
      public int getPaddingMode() {
        return mLayerState.mPaddingMode;
      }
  
      /**
       * 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 {
              // This may have been called as the result of a tint changing, in
              // which case we may need to refresh the cached statefulness or
              // opacity.
              mLayerState.invalidateCache();
  
              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 void draw(Canvas canvas) {
          final ChildDrawable[] array = mLayerState.mChildren;
          final int N = mLayerState.mNumChildren;
          for (int i = 0; i < N; i++) {
              final Drawable dr = array[i].mDrawable;
              if (dr != null) {
                  dr.draw(canvas);
              }
          }
      }
  
      @Override
      public @Config int getChangingConfigurations() {
          return super.getChangingConfigurations() | mLayerState.getChangingConfigurations();
      }
  
      @Override
      public boolean getPadding(Rect padding) {
          final LayerState layerState = mLayerState;
          if (layerState.mPaddingMode == PADDING_MODE_NEST) {
              computeNestedPadding(padding);
          } else {
              computeStackedPadding(padding);
          }
  
          final int paddingT = layerState.mPaddingTop;
          final int paddingB = layerState.mPaddingBottom;
  
          // Resolve padding for RTL. Relative padding overrides absolute
          // padding.
          final boolean isLayoutRtl = getLayoutDirection() == LayoutDirection.RTL;
          final int paddingRtlL = isLayoutRtl ? layerState.mPaddingEnd : layerState.mPaddingStart;
          final int paddingRtlR = isLayoutRtl ? layerState.mPaddingStart : layerState.mPaddingEnd;
          final int paddingL = paddingRtlL >= 0 ? paddingRtlL : layerState.mPaddingLeft;
          final int paddingR = paddingRtlR >= 0 ? paddingRtlR : layerState.mPaddingRight;
  
          // If padding was explicitly specified (e.g. not -1) then override the
          // computed padding in that dimension.
          if (paddingL >= 0) {
              padding.left = paddingL;
          }
  
          if (paddingT >= 0) {
              padding.top = paddingT;
          }
  
          if (paddingR >= 0) {
              padding.right = paddingR;
          }
  
          if (paddingB >= 0) {
              padding.bottom = paddingB;
          }
  
          return padding.left != 0 || padding.top != 0 || padding.right != 0 || padding.bottom != 0;
      }
  
      /**
       * Sets the absolute padding.
       * <p>
       * If padding in a dimension is specified as {@code -1}, the resolved
       * padding will use the value computed according to the padding mode (see
       * {@link #setPaddingMode(int)}).
       * <p>
       * Calling this method clears any relative padding values previously set
       * using {@link #setPaddingRelative(int, int, int, int)}.
       *
       * @param left the left padding in pixels, or -1 to use computed padding
       * @param top the top padding in pixels, or -1 to use computed padding
       * @param right the right padding in pixels, or -1 to use computed padding
       * @param bottom the bottom padding in pixels, or -1 to use computed
       *               padding
       * @attr ref android.R.styleable#LayerDrawable_paddingLeft
       * @attr ref android.R.styleable#LayerDrawable_paddingTop
       * @attr ref android.R.styleable#LayerDrawable_paddingRight
       * @attr ref android.R.styleable#LayerDrawable_paddingBottom
       * @see #setPaddingRelative(int, int, int, int)
       */
      public void setPadding(int left, int top, int right, int bottom) {
          final LayerState layerState = mLayerState;
          layerState.mPaddingLeft = left;
          layerState.mPaddingTop = top;
          layerState.mPaddingRight = right;
          layerState.mPaddingBottom = bottom;
  
          // Clear relative padding values.
          layerState.mPaddingStart = -1;
          layerState.mPaddingEnd = -1;
      }
  
      /**
       * Sets the relative padding.
       * <p>
       * If padding in a dimension is specified as {@code -1}, the resolved
       * padding will use the value computed according to the padding mode (see
       * {@link #setPaddingMode(int)}).
       * <p>
       * Calling this method clears any absolute padding values previously set
       * using {@link #setPadding(int, int, int, int)}.
       *
       * @param start the start padding in pixels, or -1 to use computed padding
       * @param top the top padding in pixels, or -1 to use computed padding
       * @param end the end padding in pixels, or -1 to use computed padding
       * @param bottom the bottom padding in pixels, or -1 to use computed
       *               padding
       * @attr ref android.R.styleable#LayerDrawable_paddingStart
       * @attr ref android.R.styleable#LayerDrawable_paddingTop
       * @attr ref android.R.styleable#LayerDrawable_paddingEnd
       * @attr ref android.R.styleable#LayerDrawable_paddingBottom
       * @see #setPadding(int, int, int, int)
       */
      public void setPaddingRelative(int start, int top, int end, int bottom) {
          final LayerState layerState = mLayerState;
          layerState.mPaddingStart = start;
          layerState.mPaddingTop = top;
          layerState.mPaddingEnd = end;
          layerState.mPaddingBottom = bottom;
  
          // Clear absolute padding values.
          layerState.mPaddingLeft = -1;
          layerState.mPaddingRight = -1;
      }
  
      /**
       * Returns the left padding in pixels.
       * <p>
       * A return value of {@code -1} means there is no explicit padding set for
       * this dimension. As a result, the value for this dimension returned by
       * {@link #getPadding(Rect)} will be computed from the child layers
       * according to the padding mode (see {@link #getPaddingMode()}.
       *
       * @return the left padding in pixels, or -1 if not explicitly specified
       * @see #setPadding(int, int, int, int)
       * @see #getPadding(Rect)
       */
      public int getLeftPadding() {
          return mLayerState.mPaddingLeft;
      }
  
      /**
       * Returns the right padding in pixels.
       * <p>
       * A return value of {@code -1} means there is no explicit padding set for
       * this dimension. As a result, the value for this dimension returned by
       * {@link #getPadding(Rect)} will be computed from the child layers
       * according to the padding mode (see {@link #getPaddingMode()}.
       *
       * @return the right padding in pixels, or -1 if not explicitly specified
       * @see #setPadding(int, int, int, int)
       * @see #getPadding(Rect)
       */
      public int getRightPadding() {
          return mLayerState.mPaddingRight;
      }
  
      /**
       * Returns the start padding in pixels.
       * <p>
       * A return value of {@code -1} means there is no explicit padding set for
       * this dimension. As a result, the value for this dimension returned by
       * {@link #getPadding(Rect)} will be computed from the child layers
       * according to the padding mode (see {@link #getPaddingMode()}.
       *
       * @return the start padding in pixels, or -1 if not explicitly specified
       * @see #setPaddingRelative(int, int, int, int)
       * @see #getPadding(Rect)
       */
      public int getStartPadding() {
          return mLayerState.mPaddingStart;
      }
  
      /**
       * Returns the end padding in pixels.
       * <p>
       * A return value of {@code -1} means there is no explicit padding set for
       * this dimension. As a result, the value for this dimension returned by
       * {@link #getPadding(Rect)} will be computed from the child layers
       * according to the padding mode (see {@link #getPaddingMode()}.
       *
       * @return the end padding in pixels, or -1 if not explicitly specified
       * @see #setPaddingRelative(int, int, int, int)
       * @see #getPadding(Rect)
       */
      public int getEndPadding() {
          return mLayerState.mPaddingEnd;
      }
  
      /**
       * Returns the top padding in pixels.
       * <p>
       * A return value of {@code -1} means there is no explicit padding set for
       * this dimension. As a result, the value for this dimension returned by
       * {@link #getPadding(Rect)} will be computed from the child layers
       * according to the padding mode (see {@link #getPaddingMode()}.
       *
       * @return the top padding in pixels, or -1 if not explicitly specified
       * @see #setPadding(int, int, int, int)
       * @see #setPaddingRelative(int, int, int, int)
       * @see #getPadding(Rect)
       */
      public int getTopPadding() {
          return mLayerState.mPaddingTop;
      }
  
      /**
       * Returns the bottom padding in pixels.
       * <p>
       * A return value of {@code -1} means there is no explicit padding set for
       * this dimension. As a result, the value for this dimension returned by
       * {@link #getPadding(Rect)} will be computed from the child layers
       * according to the padding mode (see {@link #getPaddingMode()}.
       *
       * @return the bottom padding in pixels, or -1 if not explicitly specified
       * @see #setPadding(int, int, int, int)
       * @see #setPaddingRelative(int, int, int, int)
       * @see #getPadding(Rect)
       */
      public int getBottomPadding() {
          return mLayerState.mPaddingBottom;
      }
  
      private void computeNestedPadding(Rect padding) {
          padding.left = 0;
          padding.top = 0;
          padding.right = 0;
          padding.bottom = 0;
  
          // Add all the padding.
          final ChildDrawable[] array = mLayerState.mChildren;
          final int N = mLayerState.mNumChildren;
          for (int i = 0; i < N; i++) {
              refreshChildPadding(i, array[i]);
  
              padding.left += mPaddingL[i];
              padding.top += mPaddingT[i];
              padding.right += mPaddingR[i];
              padding.bottom += mPaddingB[i];
          }
      }
  
      private void computeStackedPadding(Rect padding) {
          padding.left = 0;
          padding.top = 0;
          padding.right = 0;
          padding.bottom = 0;
  
          // Take the max padding.
          final ChildDrawable[] array = mLayerState.mChildren;
          final int N = mLayerState.mNumChildren;
          for (int i = 0; i < N; i++) {
              refreshChildPadding(i, array[i]);
  
              padding.left = Math.max(padding.left, mPaddingL[i]);
              padding.top = Math.max(padding.top, mPaddingT[i]);
              padding.right = Math.max(padding.right, mPaddingR[i]);
              padding.bottom = Math.max(padding.bottom, mPaddingB[i]);
          }
      }
  
      /**
       * Populates <code>outline</code> with the first available (non-empty) layer outline.
       *
       * @param outline Outline in which to place the first available layer outline
       */
      @Override
      public void getOutline(@NonNull Outline outline) {
          final ChildDrawable[] array = mLayerState.mChildren;
          final int N = mLayerState.mNumChildren;
          for (int i = 0; i < N; i++) {
              final Drawable dr = array[i].mDrawable;
              if (dr != null) {
                  dr.getOutline(outline);
                  if (!outline.isEmpty()) {
                      return;
                  }
              }
          }
      }
  
      @Override
      public void setHotspot(float x, float y) {
          final ChildDrawable[] array = mLayerState.mChildren;
          final int N = mLayerState.mNumChildren;
          for (int i = 0; i < N; 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;
          final int N = mLayerState.mNumChildren;
          for (int i = 0; i < N; 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;
          final int N = mLayerState.mNumChildren;
          for (int i = 0; i < N; 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;
          final int N = mLayerState.mNumChildren;
          for (int i = 0; i < N; i++) {
              final Drawable dr = array[i].mDrawable;
              if (dr != null) {
                  dr.setDither(dither);
              }
          }
      }
  
      @Override
      public void setAlpha(int alpha) {
          final ChildDrawable[] array = mLayerState.mChildren;
          final int N = mLayerState.mNumChildren;
          for (int i = 0; i < N; 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;
          final int N = mLayerState.mNumChildren;
          for (int i = 0; i < N; 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.mNumChildren;
          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.mNumChildren;
          for (int i = 0; i < N; i++) {
              final Drawable dr = array[i].mDrawable;
              if (dr != null) {
                  dr.setTintBlendMode(blendMode);
              }
          }
      }
  
      private Drawable getFirstNonNullDrawable() {
          final ChildDrawable[] array = mLayerState.mChildren;
          final int N = mLayerState.mNumChildren;
          for (int i = 0; i < N; i++) {
              final Drawable dr = array[i].mDrawable;
              if (dr != null) {
                  return dr;
              }
          }
          return null;
      }
  
      /**
       * Sets the opacity of this drawable directly instead of collecting the
       * states from the layers.
       *
       * @param opacity The opacity to use, or {@link PixelFormat#UNKNOWN
       *            PixelFormat.UNKNOWN} for the default behavior
       * @see PixelFormat#UNKNOWN
       * @see PixelFormat#TRANSLUCENT
       * @see PixelFormat#TRANSPARENT
       * @see PixelFormat#OPAQUE
       */
      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;
          final int N = mLayerState.mNumChildren;
          for (int i = 0; i < N; 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;
          final int N = mLayerState.mNumChildren;
          for (int i = 0; i < N; 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;
          final int N = mLayerState.mNumChildren;
          for (int i = 0; i < N; i++) {
              final Drawable dr = array[i].mDrawable;
              if (dr != null && dr.isStateful() && dr.setState(state)) {
                  refreshChildPadding(i, array[i]);
                  changed = true;
              }
          }
  
          if (changed) {
              updateLayerBounds(getBounds());
          }
  
          return changed;
      }
  
      @Override
      protected boolean onLevelChange(int level) {
          boolean changed = false;
  
          final ChildDrawable[] array = mLayerState.mChildren;
          final int N = mLayerState.mNumChildren;
          for (int i = 0; i < N; i++) {
              final Drawable dr = array[i].mDrawable;
              if (dr != null && dr.setLevel(level)) {
                  refreshChildPadding(i, array[i]);
                  changed = true;
              }
          }
  
          if (changed) {
              updateLayerBounds(getBounds());
          }
  
          return changed;
      }
  
      @Override
      protected void onBoundsChange(Rect bounds) {
          updateLayerBounds(bounds);
      }
  
      private void updateLayerBounds(Rect bounds) {
          try {
              suspendChildInvalidation();
              updateLayerBoundsInternal(bounds);
          } finally {
              resumeChildInvalidation();
          }
      }
  
      private void updateLayerBoundsInternal(Rect bounds) {
          int paddingL = 0;
          int paddingT = 0;
          int paddingR = 0;
          int paddingB = 0;
  
          final Rect outRect = mTmpOutRect;
          final int layoutDirection = getLayoutDirection();
          final boolean isLayoutRtl = layoutDirection == LayoutDirection.RTL;
          final boolean isPaddingNested = mLayerState.mPaddingMode == PADDING_MODE_NEST;
          final ChildDrawable[] array = mLayerState.mChildren;
  
          for (int i = 0, count = mLayerState.mNumChildren; i < count; i++) {
              final ChildDrawable r = array[i];
              final Drawable d = r.mDrawable;
              if (d == null) {
                  continue;
              }
  
              final int insetT = r.mInsetT;
              final int insetB = r.mInsetB;
  
              // Resolve insets for RTL. Relative insets override absolute
              // insets.
              final int insetRtlL = isLayoutRtl ? r.mInsetE : r.mInsetS;
              final int insetRtlR = isLayoutRtl ? r.mInsetS : r.mInsetE;
              final int insetL = insetRtlL == INSET_UNDEFINED ? r.mInsetL : insetRtlL;
              final int insetR = insetRtlR == INSET_UNDEFINED ? r.mInsetR : insetRtlR;
  
              // Establish containing region based on aggregate padding and
              // requested insets for the current layer.
              final Rect container = mTmpContainer;
              container.set(bounds.left + insetL + paddingL, bounds.top + insetT + paddingT,
                      bounds.right - insetR - paddingR, bounds.bottom - insetB - paddingB);
  
              // Compute a reasonable default gravity based on the intrinsic and
              // explicit dimensions, if specified.
              final int intrinsicW = d.getIntrinsicWidth();
              final int intrinsicH = d.getIntrinsicHeight();
              final int layerW = r.mWidth;
              final int layerH = r.mHeight;
              final int gravity = resolveGravity(r.mGravity, layerW, layerH, intrinsicW, intrinsicH);
  
              // Explicit dimensions override intrinsic dimensions.
              final int resolvedW = layerW < 0 ? intrinsicW : layerW;
              final int resolvedH = layerH < 0 ? intrinsicH : layerH;
              Gravity.apply(gravity, resolvedW, resolvedH, container, outRect, layoutDirection);
              d.setBounds(outRect);
  
              if (isPaddingNested) {
                  paddingL += mPaddingL[i];
                  paddingR += mPaddingR[i];
                  paddingT += mPaddingT[i];
                  paddingB += mPaddingB[i];
              }
          }
      }
  
      /**
       * Resolves layer gravity given explicit gravity and dimensions.
       * <p>
       * If the client hasn't specified a gravity but has specified an explicit
       * dimension, defaults to START or TOP. Otherwise, defaults to FILL to
       * preserve legacy behavior.
       *
       * @param gravity layer gravity
       * @param width width of the layer if set, -1 otherwise
       * @param height height of the layer if set, -1 otherwise
       * @return the default gravity for the layer
       */
      private static int resolveGravity(int gravity, int width, int height,
              int intrinsicWidth, int intrinsicHeight) {
          if (!Gravity.isHorizontal(gravity)) {
              if (width < 0) {
                  gravity |= Gravity.FILL_HORIZONTAL;
              } else {
                  gravity |= Gravity.START;
              }
          }
  
          if (!Gravity.isVertical(gravity)) {
              if (height < 0) {
                  gravity |= Gravity.FILL_VERTICAL;
              } else {
                  gravity |= Gravity.TOP;
              }
          }
  
          // If a dimension if not specified, either implicitly or explicitly,
          // force FILL for that dimension's gravity. This ensures that colors
          // are handled correctly and ensures backward compatibility.
          if (width < 0 && intrinsicWidth < 0) {
              gravity |= Gravity.FILL_HORIZONTAL;
          }
  
          if (height < 0 && intrinsicHeight < 0) {
              gravity |= Gravity.FILL_VERTICAL;
          }
  
          return gravity;
      }
  
      @Override
      public int getIntrinsicWidth() {
          int width = -1;
          int padL = 0;
          int padR = 0;
  
          final boolean nest = mLayerState.mPaddingMode == PADDING_MODE_NEST;
          final boolean isLayoutRtl = getLayoutDirection() == LayoutDirection.RTL;
          final ChildDrawable[] array = mLayerState.mChildren;
          final int N = mLayerState.mNumChildren;
          for (int i = 0; i < N; i++) {
              final ChildDrawable r = array[i];
              if (r.mDrawable == null) {
                  continue;
              }
  
              // Take the resolved layout direction into account. If start / end
              // padding are defined, they will be resolved (hence overriding) to
              // left / right or right / left depending on the resolved layout
              // direction. If start / end padding are not defined, use the
              // left / right ones.
              final int insetRtlL = isLayoutRtl ? r.mInsetE : r.mInsetS;
              final int insetRtlR = isLayoutRtl ? r.mInsetS : r.mInsetE;
              final int insetL = insetRtlL == INSET_UNDEFINED ? r.mInsetL : insetRtlL;
              final int insetR = insetRtlR == INSET_UNDEFINED ? r.mInsetR : insetRtlR;
  
              // Don't apply padding and insets for children that don't have
              // an intrinsic dimension.
              final int minWidth = r.mWidth < 0 ? r.mDrawable.getIntrinsicWidth() : r.mWidth;
              final int w = minWidth < 0 ? -1 : minWidth + insetL + insetR + padL + padR;
              if (w > width) {
                  width = w;
              }
  
              if (nest) {
                  padL += mPaddingL[i];
                  padR += mPaddingR[i];
              }
          }
  
          return width;
      }
  
      @Override
      public int getIntrinsicHeight() {
          int height = -1;
          int padT = 0;
          int padB = 0;
  
          final boolean nest = mLayerState.mPaddingMode == PADDING_MODE_NEST;
          final ChildDrawable[] array = mLayerState.mChildren;
          final int N = mLayerState.mNumChildren;
          for (int i = 0; i < N; i++) {
              final ChildDrawable r = array[i];
              if (r.mDrawable == null) {
                  continue;
              }
  
              // Don't apply padding and insets for children that don't have
              // an intrinsic dimension.
              final int minHeight = r.mHeight < 0 ? r.mDrawable.getIntrinsicHeight() : r.mHeight;
              final int h = minHeight < 0 ? -1 : minHeight + r.mInsetT + r.mInsetB + padT + padB;
              if (h > height) {
                  height = h;
              }
  
              if (nest) {
                  padT += mPaddingT[i];
                  padB += mPaddingB[i];
              }
          }
  
          return height;
      }
  
      /**
       * Refreshes the cached padding values for the specified child.
       *
       * @return true if the child's padding has changed
       */
      private boolean refreshChildPadding(int i, ChildDrawable r) {
          if (r.mDrawable != null) {
              final Rect rect = mTmpRect;
              r.mDrawable.getPadding(rect);
              if (rect.left != mPaddingL[i] || rect.top != mPaddingT[i]
                      || rect.right != mPaddingR[i] || rect.bottom != mPaddingB[i]) {
                  mPaddingL[i] = rect.left;
                  mPaddingT[i] = rect.top;
                  mPaddingR[i] = rect.right;
                  mPaddingB[i] = rect.bottom;
                  return true;
              }
          }
          return false;
      }
  
      /**
       * Ensures the child padding caches are large enough.
       */
      @UnsupportedAppUsage
      void ensurePadding() {
          final int N = mLayerState.mNumChildren;
          if (mPaddingL != null && mPaddingL.length >= N) {
              return;
          }
  
          mPaddingL = new int[N];
          mPaddingT = new int[N];
          mPaddingR = new int[N];
          mPaddingB = new int[N];
      }
  
      void refreshPadding() {
          final int N = mLayerState.mNumChildren;
          final ChildDrawable[] array = mLayerState.mChildren;
          for (int i = 0; i < N; i++) {
              refreshChildPadding(i, array[i]);
          }
      }
  
      @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);
              final ChildDrawable[] array = mLayerState.mChildren;
              final int N = mLayerState.mNumChildren;
              for (int i = 0; i < N; i++) {
                  final Drawable dr = array[i].mDrawable;
                  if (dr != null) {
                      dr.mutate();
                  }
              }
              mMutated = true;
          }
          return this;
      }
  
      /**
       * @hide
       */
      public void clearMutated() {
          super.clearMutated();
  
          final ChildDrawable[] array = mLayerState.mChildren;
          final int N = mLayerState.mNumChildren;
          for (int i = 0; i < N; i++) {
              final Drawable dr = array[i].mDrawable;
              if (dr != null) {
                  dr.clearMutated();
              }
          }
          mMutated = false;
      }
  
      @Override
      public boolean onLayoutDirectionChanged(@View.ResolvedLayoutDir int layoutDirection) {
          boolean changed = false;
  
          final ChildDrawable[] array = mLayerState.mChildren;
          final int N = mLayerState.mNumChildren;
          for (int i = 0; i < N; i++) {
              final Drawable dr = array[i].mDrawable;
              if (dr != null) {
                  changed |= dr.setLayoutDirection(layoutDirection);
              }
          }
  
          updateLayerBounds(getBounds());
          return changed;
      }
  
      static class ChildDrawable {
          @UnsupportedAppUsage
          public Drawable mDrawable;
          public int[] mThemeAttrs;
          public int mDensity = DisplayMetrics.DENSITY_DEFAULT;
          public int mInsetL, mInsetT, mInsetR, mInsetB;
          public int mInsetS = INSET_UNDEFINED;
          public int mInsetE = INSET_UNDEFINED;
          public int mWidth = -1;
          public int mHeight = -1;
          public int mGravity = Gravity.NO_GRAVITY;
          public int mId = View.NO_ID;
  
          ChildDrawable(int density) {
              mDensity = density;
          }
  
          ChildDrawable(@NonNull ChildDrawable orig, @NonNull LayerDrawable 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;
                      if (dr.getCallback() != null) {
                          // This drawable already has an owner.
                          Log.w(LOG_TAG, "Invalid drawable added to LayerDrawable! Drawable already "
                                  + "belongs to another owner but does not expose a constant state.",
                                  new RuntimeException());
                      }
                  } else if (res != null) {
                      clone = cs.newDrawable(res);
                  } else {
                      clone = cs.newDrawable();
                  }
                  clone.setLayoutDirection(dr.getLayoutDirection());
                  clone.setBounds(dr.getBounds());
                  clone.setLevel(dr.getLevel());
  
                  // Set the callback last to prevent invalidation from
                  // propagating before the constant state has been set.
                  clone.setCallback(owner);
              } else {
                  clone = null;
              }
  
              mDrawable = clone;
              mThemeAttrs = orig.mThemeAttrs;
              mInsetL = orig.mInsetL;
              mInsetT = orig.mInsetT;
              mInsetR = orig.mInsetR;
              mInsetB = orig.mInsetB;
              mInsetS = orig.mInsetS;
              mInsetE = orig.mInsetE;
              mWidth = orig.mWidth;
              mHeight = orig.mHeight;
              mGravity = orig.mGravity;
              mId = orig.mId;
  
              mDensity = Drawable.resolveDensity(res, orig.mDensity);
              if (orig.mDensity != mDensity) {
                  applyDensityScaling(orig.mDensity, mDensity);
              }
          }
  
          public boolean canApplyTheme() {
              return mThemeAttrs != null
                      || (mDrawable != null && mDrawable.canApplyTheme());
          }
  
          public final void setDensity(int targetDensity) {
              if (mDensity != targetDensity) {
                  final int sourceDensity = mDensity;
                  mDensity = targetDensity;
  
                  applyDensityScaling(sourceDensity, targetDensity);
              }
          }
  
          private void applyDensityScaling(int sourceDensity, int targetDensity) {
              mInsetL = Drawable.scaleFromDensity(mInsetL, sourceDensity, targetDensity, false);
              mInsetT = Drawable.scaleFromDensity(mInsetT, sourceDensity, targetDensity, false);
              mInsetR = Drawable.scaleFromDensity(mInsetR, sourceDensity, targetDensity, false);
              mInsetB = Drawable.scaleFromDensity(mInsetB, sourceDensity, targetDensity, false);
              if (mInsetS != INSET_UNDEFINED) {
                  mInsetS = Drawable.scaleFromDensity(mInsetS, sourceDensity, targetDensity, false);
              }
              if (mInsetE != INSET_UNDEFINED) {
                  mInsetE = Drawable.scaleFromDensity(mInsetE, sourceDensity, targetDensity, false);
              }
              if (mWidth > 0) {
                  mWidth = Drawable.scaleFromDensity(mWidth, sourceDensity, targetDensity, true);
              }
              if (mHeight > 0) {
                  mHeight = Drawable.scaleFromDensity(mHeight, sourceDensity, targetDensity, true);
              }
          }
      }
  
      static class LayerState extends ConstantState {
          private int[] mThemeAttrs;
  
          int mNumChildren;
          @UnsupportedAppUsage
          ChildDrawable[] mChildren;
  
          int mDensity;
  
          // These values all correspond to mDensity.
          int mPaddingTop = -1;
          int mPaddingBottom = -1;
          int mPaddingLeft = -1;
          int mPaddingRight = -1;
          int mPaddingStart = -1;
          int mPaddingEnd = -1;
          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;
  
          private int mPaddingMode = PADDING_MODE_NEST;
  
          LayerState(@Nullable LayerState orig, @NonNull LayerDrawable owner,
                  @Nullable Resources res) {
              mDensity = Drawable.resolveDensity(res, orig != null ? orig.mDensity : 0);
  
              if (orig != null) {
                  final ChildDrawable[] origChildDrawable = orig.mChildren;
                  final int N = orig.mNumChildren;
  
                  mNumChildren = N;
                  mChildren = new ChildDrawable[N];
  
                  mChangingConfigurations = orig.mChangingConfigurations;
                  mChildrenChangingConfigurations = orig.mChildrenChangingConfigurations;
  
                  for (int i = 0; i < N; 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;
                  mPaddingMode = orig.mPaddingMode;
                  mThemeAttrs = orig.mThemeAttrs;
                  mPaddingTop = orig.mPaddingTop;
                  mPaddingBottom = orig.mPaddingBottom;
                  mPaddingLeft = orig.mPaddingLeft;
                  mPaddingRight = orig.mPaddingRight;
                  mPaddingStart = orig.mPaddingStart;
                  mPaddingEnd = orig.mPaddingEnd;
                  mOpacityOverride = orig.mOpacityOverride;
  
                  if (orig.mDensity != mDensity) {
                      applyDensityScaling(orig.mDensity, mDensity);
                  }
              } else {
                  mNumChildren = 0;
                  mChildren = null;
              }
          }
  
          public final void setDensity(int targetDensity) {
              if (mDensity != targetDensity) {
                  final int sourceDensity = mDensity;
                  mDensity = targetDensity;
  
                  onDensityChanged(sourceDensity, targetDensity);
              }
          }
  
          protected void onDensityChanged(int sourceDensity, int targetDensity) {
              applyDensityScaling(sourceDensity, targetDensity);
          }
  
          private void applyDensityScaling(int sourceDensity, int targetDensity) {
              if (mPaddingLeft > 0) {
                  mPaddingLeft = Drawable.scaleFromDensity(
                          mPaddingLeft, sourceDensity, targetDensity, false);
              }
              if (mPaddingTop > 0) {
                  mPaddingTop = Drawable.scaleFromDensity(
                          mPaddingTop, sourceDensity, targetDensity, false);
              }
              if (mPaddingRight > 0) {
                  mPaddingRight = Drawable.scaleFromDensity(
                          mPaddingRight, sourceDensity, targetDensity, false);
              }
              if (mPaddingBottom > 0) {
                  mPaddingBottom = Drawable.scaleFromDensity(
                          mPaddingBottom, sourceDensity, targetDensity, false);
              }
              if (mPaddingStart > 0) {
                  mPaddingStart = Drawable.scaleFromDensity(
                          mPaddingStart, sourceDensity, targetDensity, false);
              }
              if (mPaddingEnd > 0) {
                  mPaddingEnd = Drawable.scaleFromDensity(
                          mPaddingEnd, sourceDensity, targetDensity, false);
              }
          }
  
          @Override
          public boolean canApplyTheme() {
              if (mThemeAttrs != null || super.canApplyTheme()) {
                  return true;
              }
  
              final ChildDrawable[] array = mChildren;
              final int N = mNumChildren;
              for (int i = 0; i < N; i++) {
                  final ChildDrawable layer = array[i];
                  if (layer.canApplyTheme()) {
                      return true;
                  }
              }
  
              return false;
          }
  
          @Override
          public Drawable newDrawable() {
              return new LayerDrawable(this, null);
          }
  
          @Override
          public Drawable newDrawable(@Nullable Resources res) {
              return new LayerDrawable(this, res);
          }
  
          @Override
          public @Config int getChangingConfigurations() {
              return mChangingConfigurations
                      | mChildrenChangingConfigurations;
          }
  
          public final int getOpacity() {
              if (mCheckedOpacity) {
                  return mOpacity;
              }
  
              final int N = mNumChildren;
              final ChildDrawable[] array = mChildren;
  
              // Seek to the first non-null drawable.
              int firstIndex = -1;
              for (int i = 0; i < N; 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; 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 int N = mNumChildren;
              final ChildDrawable[] array = mChildren;
              boolean isStateful = false;
              for (int i = 0; i < N; 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 int N = mNumChildren;
              final ChildDrawable[] array = mChildren;
              for (int i = 0; i < N; i++) {
                  final Drawable dr = array[i].mDrawable;
                  if (dr != null && dr.hasFocusStateSpecified()) {
                      return true;
                  }
              }
              return false;
          }
  
          public final boolean canConstantState() {
              final ChildDrawable[] array = mChildren;
              final int N = mNumChildren;
              for (int i = 0; i < N; 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;
          }
  
          /**
           * Invalidates the cached opacity and statefulness.
           */
          void invalidateCache() {
              mCheckedOpacity = false;
              mCheckedStateful = false;
          }
  
      }
  }
  
  