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.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.graphics.BlendMode;
  import android.graphics.Canvas;
  import android.graphics.ColorFilter;
  import android.graphics.Insets;
  import android.graphics.Outline;
  import android.graphics.PixelFormat;
  import android.graphics.Rect;
  import android.os.Build;
  import android.os.SystemClock;
  import android.util.DisplayMetrics;
  import android.util.LayoutDirection;
  import android.util.SparseArray;
  import android.view.View;
  
  /**
   * A helper class that contains several {@link Drawable}s and selects which one to use.
   *
   * You can subclass it to create your own DrawableContainers or directly use one its child classes.
   */
  public class DrawableContainer extends Drawable implements Drawable.Callback {
      private static final boolean DEBUG = false;
      private static final String TAG = "DrawableContainer";
  
      /**
       * To be proper, we should have a getter for dither (and alpha, etc.)
       * so that proxy classes like this can save/restore their delegates'
       * values, but we don't have getters. Since we do have setters
       * (e.g. setDither), which this proxy forwards on, we have to have some
       * default/initial setting.
       *
       * The initial setting for dither is now true, since it almost always seems
       * to improve the quality at negligible cost.
       */
      private static final boolean DEFAULT_DITHER = true;
      @UnsupportedAppUsage
      private DrawableContainerState mDrawableContainerState;
      private Rect mHotspotBounds;
      private Drawable mCurrDrawable;
      @UnsupportedAppUsage
      private Drawable mLastDrawable;
      private int mAlpha = 0xFF;
  
      /** Whether setAlpha() has been called at least once. */
      private boolean mHasAlpha;
  
      private int mCurIndex = -1;
      private int mLastIndex = -1;
      private boolean mMutated;
  
      // Animations.
      private Runnable mAnimationRunnable;
      private long mEnterAnimationEnd;
      private long mExitAnimationEnd;
  
      /** Callback that blocks invalidation. Used for drawable initialization. */
      private BlockInvalidateCallback mBlockInvalidateCallback;
  
      // overrides from Drawable
  
      @Override
      public void draw(Canvas canvas) {
          if (mCurrDrawable != null) {
              mCurrDrawable.draw(canvas);
          }
          if (mLastDrawable != null) {
              mLastDrawable.draw(canvas);
          }
      }
  
      @Override
      public @Config int getChangingConfigurations() {
          return super.getChangingConfigurations()
                  | mDrawableContainerState.getChangingConfigurations();
      }
  
      private boolean needsMirroring() {
          return isAutoMirrored() && getLayoutDirection() == LayoutDirection.RTL;
      }
  
      @Override
      public boolean getPadding(Rect padding) {
          final Rect r = mDrawableContainerState.getConstantPadding();
          boolean result;
          if (r != null) {
              padding.set(r);
              result = (r.left | r.top | r.bottom | r.right) != 0;
          } else {
              if (mCurrDrawable != null) {
                  result = mCurrDrawable.getPadding(padding);
              } else {
                  result = super.getPadding(padding);
              }
          }
          if (needsMirroring()) {
              final int left = padding.left;
              final int right = padding.right;
              padding.left = right;
              padding.right = left;
          }
          return result;
      }
  
      @Override
      public Insets getOpticalInsets() {
          if (mCurrDrawable != null) {
              return mCurrDrawable.getOpticalInsets();
          }
          return Insets.NONE;
      }
  
      @Override
      public void getOutline(@NonNull Outline outline) {
          if (mCurrDrawable != null) {
              mCurrDrawable.getOutline(outline);
          }
      }
  
      @Override
      public void setAlpha(int alpha) {
          if (!mHasAlpha || mAlpha != alpha) {
              mHasAlpha = true;
              mAlpha = alpha;
              if (mCurrDrawable != null) {
                  if (mEnterAnimationEnd == 0) {
                      mCurrDrawable.setAlpha(alpha);
                  } else {
                      animate(false);
                  }
              }
          }
      }
  
      @Override
      public int getAlpha() {
          return mAlpha;
      }
  
      @Override
      public void setDither(boolean dither) {
          if (mDrawableContainerState.mDither != dither) {
              mDrawableContainerState.mDither = dither;
              if (mCurrDrawable != null) {
                  mCurrDrawable.setDither(mDrawableContainerState.mDither);
              }
          }
      }
  
      @Override
      public void setColorFilter(ColorFilter colorFilter) {
          mDrawableContainerState.mHasColorFilter = true;
  
          if (mDrawableContainerState.mColorFilter != colorFilter) {
              mDrawableContainerState.mColorFilter = colorFilter;
  
              if (mCurrDrawable != null) {
                  mCurrDrawable.setColorFilter(colorFilter);
              }
          }
      }
  
      @Override
      public void setTintList(ColorStateList tint) {
          mDrawableContainerState.mHasTintList = true;
  
          if (mDrawableContainerState.mTintList != tint) {
              mDrawableContainerState.mTintList = tint;
  
              if (mCurrDrawable != null) {
                  mCurrDrawable.setTintList(tint);
              }
          }
      }
  
      @Override
      public void setTintBlendMode(@NonNull BlendMode blendMode) {
          mDrawableContainerState.mHasTintMode = true;
  
          if (mDrawableContainerState.mBlendMode != blendMode) {
              mDrawableContainerState.mBlendMode = blendMode;
  
              if (mCurrDrawable != null) {
                  mCurrDrawable.setTintBlendMode(blendMode);
              }
          }
      }
  
      /**
       * Change the global fade duration when a new drawable is entering
       * the scene.
       *
       * @param ms The amount of time to fade in milliseconds.
       */
      public void setEnterFadeDuration(int ms) {
          mDrawableContainerState.mEnterFadeDuration = ms;
      }
  
      /**
       * Change the global fade duration when a new drawable is leaving
       * the scene.
       *
       * @param ms The amount of time to fade in milliseconds.
       */
      public void setExitFadeDuration(int ms) {
          mDrawableContainerState.mExitFadeDuration = ms;
      }
  
      @Override
      protected void onBoundsChange(Rect bounds) {
          if (mLastDrawable != null) {
              mLastDrawable.setBounds(bounds);
          }
          if (mCurrDrawable != null) {
              mCurrDrawable.setBounds(bounds);
          }
      }
  
      @Override
      public boolean isStateful() {
          return mDrawableContainerState.isStateful();
      }
  
      /** @hide */
      @Override
      public boolean hasFocusStateSpecified() {
          if (mCurrDrawable != null) {
              return mCurrDrawable.hasFocusStateSpecified();
          }
          if (mLastDrawable != null) {
              return mLastDrawable.hasFocusStateSpecified();
          }
          return false;
      }
  
      @Override
      public void setAutoMirrored(boolean mirrored) {
          if (mDrawableContainerState.mAutoMirrored != mirrored) {
              mDrawableContainerState.mAutoMirrored = mirrored;
              if (mCurrDrawable != null) {
                  mCurrDrawable.setAutoMirrored(mDrawableContainerState.mAutoMirrored);
              }
          }
      }
  
      @Override
      public boolean isAutoMirrored() {
          return mDrawableContainerState.mAutoMirrored;
      }
  
      @Override
      public void jumpToCurrentState() {
          boolean changed = false;
          if (mLastDrawable != null) {
              mLastDrawable.jumpToCurrentState();
              mLastDrawable = null;
              mLastIndex = -1;
              changed = true;
          }
          if (mCurrDrawable != null) {
              mCurrDrawable.jumpToCurrentState();
              if (mHasAlpha) {
                  mCurrDrawable.setAlpha(mAlpha);
              }
          }
          if (mExitAnimationEnd != 0) {
              mExitAnimationEnd = 0;
              changed = true;
          }
          if (mEnterAnimationEnd != 0) {
              mEnterAnimationEnd = 0;
              changed = true;
          }
          if (changed) {
              invalidateSelf();
          }
      }
  
      @Override
      public void setHotspot(float x, float y) {
          if (mCurrDrawable != null) {
              mCurrDrawable.setHotspot(x, y);
          }
      }
  
      @Override
      public void setHotspotBounds(int left, int top, int right, int bottom) {
          if (mHotspotBounds == null) {
              mHotspotBounds = new Rect(left, top, right, bottom);
          } else {
              mHotspotBounds.set(left, top, right, bottom);
          }
  
          if (mCurrDrawable != null) {
              mCurrDrawable.setHotspotBounds(left, top, right, bottom);
          }
      }
  
      @Override
      public void getHotspotBounds(Rect outRect) {
          if (mHotspotBounds != null) {
              outRect.set(mHotspotBounds);
          } else {
              super.getHotspotBounds(outRect);
          }
      }
  
      @Override
      protected boolean onStateChange(int[] state) {
          if (mLastDrawable != null) {
              return mLastDrawable.setState(state);
          }
          if (mCurrDrawable != null) {
              return mCurrDrawable.setState(state);
          }
          return false;
      }
  
      @Override
      protected boolean onLevelChange(int level) {
          if (mLastDrawable != null) {
              return mLastDrawable.setLevel(level);
          }
          if (mCurrDrawable != null) {
              return mCurrDrawable.setLevel(level);
          }
          return false;
      }
  
      @Override
      public boolean onLayoutDirectionChanged(@View.ResolvedLayoutDir int layoutDirection) {
          // Let the container handle setting its own layout direction. Otherwise,
          // we're accessing potentially unused states.
          return mDrawableContainerState.setLayoutDirection(layoutDirection, getCurrentIndex());
      }
  
      @Override
      public int getIntrinsicWidth() {
          if (mDrawableContainerState.isConstantSize()) {
              return mDrawableContainerState.getConstantWidth();
          }
          return mCurrDrawable != null ? mCurrDrawable.getIntrinsicWidth() : -1;
      }
  
      @Override
      public int getIntrinsicHeight() {
          if (mDrawableContainerState.isConstantSize()) {
              return mDrawableContainerState.getConstantHeight();
          }
          return mCurrDrawable != null ? mCurrDrawable.getIntrinsicHeight() : -1;
      }
  
      @Override
      public int getMinimumWidth() {
          if (mDrawableContainerState.isConstantSize()) {
              return mDrawableContainerState.getConstantMinimumWidth();
          }
          return mCurrDrawable != null ? mCurrDrawable.getMinimumWidth() : 0;
      }
  
      @Override
      public int getMinimumHeight() {
          if (mDrawableContainerState.isConstantSize()) {
              return mDrawableContainerState.getConstantMinimumHeight();
          }
          return mCurrDrawable != null ? mCurrDrawable.getMinimumHeight() : 0;
      }
  
      @Override
      public void invalidateDrawable(@NonNull Drawable who) {
          // 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.
          if (mDrawableContainerState != null) {
              mDrawableContainerState.invalidateCache();
          }
  
          if (who == mCurrDrawable && getCallback() != null) {
              getCallback().invalidateDrawable(this);
          }
      }
  
      @Override
      public void scheduleDrawable(@NonNull Drawable who, @NonNull Runnable what, long when) {
          if (who == mCurrDrawable && getCallback() != null) {
              getCallback().scheduleDrawable(this, what, when);
          }
      }
  
      @Override
      public void unscheduleDrawable(@NonNull Drawable who, @NonNull Runnable what) {
          if (who == mCurrDrawable && getCallback() != null) {
              getCallback().unscheduleDrawable(this, what);
          }
      }
  
      @Override
      public boolean setVisible(boolean visible, boolean restart) {
          boolean changed = super.setVisible(visible, restart);
          if (mLastDrawable != null) {
              mLastDrawable.setVisible(visible, restart);
          }
          if (mCurrDrawable != null) {
              mCurrDrawable.setVisible(visible, restart);
          }
          return changed;
      }
  
      @Override
      public int getOpacity() {
          return mCurrDrawable == null || !mCurrDrawable.isVisible() ? PixelFormat.TRANSPARENT :
                  mDrawableContainerState.getOpacity();
      }
  
      /** @hide */
      public void setCurrentIndex(int index) {
          selectDrawable(index);
      }
  
      /** @hide */
      public int getCurrentIndex() {
          return mCurIndex;
      }
  
      /**
       * Sets the currently displayed drawable by index.
       * <p>
       * If an invalid index is specified, the current drawable will be set to
       * {@code null} and the index will be set to {@code -1}.
       *
       * @param index the index of the drawable to display
       * @return {@code true} if the drawable changed, {@code false} otherwise
       */
      public boolean selectDrawable(int index) {
          if (index == mCurIndex) {
              return false;
          }
  
          final long now = SystemClock.uptimeMillis();
  
          if (DEBUG) android.util.Log.i(TAG, toString() + " from " + mCurIndex + " to " + index
                  + ": exit=" + mDrawableContainerState.mExitFadeDuration
                  + " enter=" + mDrawableContainerState.mEnterFadeDuration);
  
          if (mDrawableContainerState.mExitFadeDuration > 0) {
              if (mLastDrawable != null) {
                  mLastDrawable.setVisible(false, false);
              }
              if (mCurrDrawable != null) {
                  mLastDrawable = mCurrDrawable;
                  mLastIndex = mCurIndex;
                  mExitAnimationEnd = now + mDrawableContainerState.mExitFadeDuration;
              } else {
                  mLastDrawable = null;
                  mLastIndex = -1;
                  mExitAnimationEnd = 0;
              }
          } else if (mCurrDrawable != null) {
              mCurrDrawable.setVisible(false, false);
          }
  
          if (index >= 0 && index < mDrawableContainerState.mNumChildren) {
              final Drawable d = mDrawableContainerState.getChild(index);
              mCurrDrawable = d;
              mCurIndex = index;
              if (d != null) {
                  if (mDrawableContainerState.mEnterFadeDuration > 0) {
                      mEnterAnimationEnd = now + mDrawableContainerState.mEnterFadeDuration;
                  }
                  initializeDrawableForDisplay(d);
              }
          } else {
              mCurrDrawable = null;
              mCurIndex = -1;
          }
  
          if (mEnterAnimationEnd != 0 || mExitAnimationEnd != 0) {
              if (mAnimationRunnable == null) {
                  mAnimationRunnable = new Runnable() {
                      @Override public void run() {
                          animate(true);
                          invalidateSelf();
                      }
                  };
              } else {
                  unscheduleSelf(mAnimationRunnable);
              }
              // Compute first frame and schedule next animation.
              animate(true);
          }
  
          invalidateSelf();
  
          return true;
      }
  
      /**
       * Initializes a drawable for display in this container.
       *
       * @param d The drawable to initialize.
       */
      private void initializeDrawableForDisplay(Drawable d) {
          if (mBlockInvalidateCallback == null) {
              mBlockInvalidateCallback = new BlockInvalidateCallback();
          }
  
          // Temporary fix for suspending callbacks during initialization. We
          // don't want any of these setters causing an invalidate() since that
          // may call back into DrawableContainer.
          d.setCallback(mBlockInvalidateCallback.wrap(d.getCallback()));
  
          try {
              if (mDrawableContainerState.mEnterFadeDuration <= 0 && mHasAlpha) {
                  d.setAlpha(mAlpha);
              }
  
              if (mDrawableContainerState.mHasColorFilter) {
                  // Color filter always overrides tint.
                  d.setColorFilter(mDrawableContainerState.mColorFilter);
              } else {
                  if (mDrawableContainerState.mHasTintList) {
                      d.setTintList(mDrawableContainerState.mTintList);
                  }
                  if (mDrawableContainerState.mHasTintMode) {
                      d.setTintBlendMode(mDrawableContainerState.mBlendMode);
                  }
              }
  
              d.setVisible(isVisible(), true);
              d.setDither(mDrawableContainerState.mDither);
              d.setState(getState());
              d.setLevel(getLevel());
              d.setBounds(getBounds());
              d.setLayoutDirection(getLayoutDirection());
              d.setAutoMirrored(mDrawableContainerState.mAutoMirrored);
  
              final Rect hotspotBounds = mHotspotBounds;
              if (hotspotBounds != null) {
                  d.setHotspotBounds(hotspotBounds.left, hotspotBounds.top,
                          hotspotBounds.right, hotspotBounds.bottom);
              }
          } finally {
              d.setCallback(mBlockInvalidateCallback.unwrap());
          }
      }
  
      void animate(boolean schedule) {
          mHasAlpha = true;
  
          final long now = SystemClock.uptimeMillis();
          boolean animating = false;
          if (mCurrDrawable != null) {
              if (mEnterAnimationEnd != 0) {
                  if (mEnterAnimationEnd <= now) {
                      mCurrDrawable.setAlpha(mAlpha);
                      mEnterAnimationEnd = 0;
                  } else {
                      int animAlpha = (int)((mEnterAnimationEnd-now)*255)
                              / mDrawableContainerState.mEnterFadeDuration;
                      mCurrDrawable.setAlpha(((255-animAlpha)*mAlpha)/255);
                      animating = true;
                  }
              }
          } else {
              mEnterAnimationEnd = 0;
          }
          if (mLastDrawable != null) {
              if (mExitAnimationEnd != 0) {
                  if (mExitAnimationEnd <= now) {
                      mLastDrawable.setVisible(false, false);
                      mLastDrawable = null;
                      mLastIndex = -1;
                      mExitAnimationEnd = 0;
                  } else {
                      int animAlpha = (int)((mExitAnimationEnd-now)*255)
                              / mDrawableContainerState.mExitFadeDuration;
                      mLastDrawable.setAlpha((animAlpha*mAlpha)/255);
                      animating = true;
                  }
              }
          } else {
              mExitAnimationEnd = 0;
          }
  
          if (schedule && animating) {
              scheduleSelf(mAnimationRunnable, now + 1000 / 60);
          }
      }
  
      @Override
      public Drawable getCurrent() {
          return mCurrDrawable;
      }
  
      /**
       * Updates the source density based on the resources used to inflate
       * density-dependent values. Implementing classes should call this method
       * during inflation.
       *
       * @param res the resources used to inflate density-dependent values
       * @hide
       */
      protected final void updateDensity(Resources res) {
          mDrawableContainerState.updateDensity(res);
      }
  
      @Override
      public void applyTheme(Theme theme) {
          mDrawableContainerState.applyTheme(theme);
      }
  
      @Override
      public boolean canApplyTheme() {
          return mDrawableContainerState.canApplyTheme();
      }
  
      @Override
      public ConstantState getConstantState() {
          if (mDrawableContainerState.canConstantState()) {
              mDrawableContainerState.mChangingConfigurations = getChangingConfigurations();
              return mDrawableContainerState;
          }
          return null;
      }
  
      @Override
      public Drawable mutate() {
          if (!mMutated && super.mutate() == this) {
              final DrawableContainerState clone = cloneConstantState();
              clone.mutate();
              setConstantState(clone);
              mMutated = true;
          }
          return this;
      }
  
      /**
       * Returns a shallow copy of the container's constant state to be used as
       * the base state for {@link #mutate()}.
       *
       * @return a shallow copy of the constant state
       */
      DrawableContainerState cloneConstantState() {
          return mDrawableContainerState;
      }
  
      /**
       * @hide
       */
      public void clearMutated() {
          super.clearMutated();
          mDrawableContainerState.clearMutated();
          mMutated = false;
      }
  
      /**
       * A ConstantState that can contain several {@link Drawable}s.
       *
       * This class was made public to enable testing, and its visibility may change in a future
       * release.
       */
      public abstract static class DrawableContainerState extends ConstantState {
          final DrawableContainer mOwner;
  
          Resources mSourceRes;
          int mDensity = DisplayMetrics.DENSITY_DEFAULT;
          @Config int mChangingConfigurations;
          @Config int mChildrenChangingConfigurations;
  
          SparseArray<ConstantState> mDrawableFutures;
          @UnsupportedAppUsage
          Drawable[] mDrawables;
          int mNumChildren;
  
          boolean mVariablePadding = false;
          boolean mCheckedPadding;
          @UnsupportedAppUsage
          Rect mConstantPadding;
  
          boolean mConstantSize = false;
          boolean mCheckedConstantSize;
          int mConstantWidth;
          int mConstantHeight;
          int mConstantMinimumWidth;
          int mConstantMinimumHeight;
  
          boolean mCheckedOpacity;
          int mOpacity;
  
          boolean mCheckedStateful;
          boolean mStateful;
  
          boolean mCheckedConstantState;
          boolean mCanConstantState;
  
          boolean mDither = DEFAULT_DITHER;
  
          boolean mMutated;
          int mLayoutDirection;
  
          int mEnterFadeDuration = 0;
          int mExitFadeDuration = 0;
  
          boolean mAutoMirrored;
  
          ColorFilter mColorFilter;
          @UnsupportedAppUsage
          boolean mHasColorFilter;
  
          ColorStateList mTintList;
          BlendMode mBlendMode;
          boolean mHasTintList;
          boolean mHasTintMode;
  
          /**
           * @hide
           */
          @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
          protected DrawableContainerState(DrawableContainerState orig, DrawableContainer owner,
                  Resources res) {
              mOwner = owner;
              mSourceRes = res != null ? res : (orig != null ? orig.mSourceRes : null);
              mDensity = Drawable.resolveDensity(res, orig != null ? orig.mDensity : 0);
  
              if (orig != null) {
                  mChangingConfigurations = orig.mChangingConfigurations;
                  mChildrenChangingConfigurations = orig.mChildrenChangingConfigurations;
  
                  mCheckedConstantState = true;
                  mCanConstantState = true;
  
                  mVariablePadding = orig.mVariablePadding;
                  mConstantSize = orig.mConstantSize;
                  mDither = orig.mDither;
                  mMutated = orig.mMutated;
                  mLayoutDirection = orig.mLayoutDirection;
                  mEnterFadeDuration = orig.mEnterFadeDuration;
                  mExitFadeDuration = orig.mExitFadeDuration;
                  mAutoMirrored = orig.mAutoMirrored;
                  mColorFilter = orig.mColorFilter;
                  mHasColorFilter = orig.mHasColorFilter;
                  mTintList = orig.mTintList;
                  mBlendMode = orig.mBlendMode;
                  mHasTintList = orig.mHasTintList;
                  mHasTintMode = orig.mHasTintMode;
  
                  if (orig.mDensity == mDensity) {
                      if (orig.mCheckedPadding) {
                          mConstantPadding = new Rect(orig.mConstantPadding);
                          mCheckedPadding = true;
                      }
  
                      if (orig.mCheckedConstantSize) {
                          mConstantWidth = orig.mConstantWidth;
                          mConstantHeight = orig.mConstantHeight;
                          mConstantMinimumWidth = orig.mConstantMinimumWidth;
                          mConstantMinimumHeight = orig.mConstantMinimumHeight;
                          mCheckedConstantSize = true;
                      }
                  }
  
                  if (orig.mCheckedOpacity) {
                      mOpacity = orig.mOpacity;
                      mCheckedOpacity = true;
                  }
  
                  if (orig.mCheckedStateful) {
                      mStateful = orig.mStateful;
                      mCheckedStateful = true;
                  }
  
                  // Postpone cloning children and futures until we're absolutely
                  // sure that we're done computing values for the original state.
                  final Drawable[] origDr = orig.mDrawables;
                  mDrawables = new Drawable[origDr.length];
                  mNumChildren = orig.mNumChildren;
  
                  final SparseArray<ConstantState> origDf = orig.mDrawableFutures;
                  if (origDf != null) {
                      mDrawableFutures = origDf.clone();
                  } else {
                      mDrawableFutures = new SparseArray<>(mNumChildren);
                  }
  
                  // Create futures for drawables with constant states. If a
                  // drawable doesn't have a constant state, then we can't clone
                  // it and we'll have to reference the original.
                  final int N = mNumChildren;
                  for (int i = 0; i < N; i++) {
                      if (origDr[i] != null) {
                          final ConstantState cs = origDr[i].getConstantState();
                          if (cs != null) {
                              mDrawableFutures.put(i, cs);
                          } else {
                              mDrawables[i] = origDr[i];
                          }
                      }
                  }
              } else {
                  mDrawables = new Drawable[10];
                  mNumChildren = 0;
              }
          }
  
          @Override
          public @Config int getChangingConfigurations() {
              return mChangingConfigurations | mChildrenChangingConfigurations;
          }
  
          /**
           * Adds the drawable to the end of the list of contained drawables.
           *
           * @param dr the drawable to add
           * @return the position of the drawable within the container
           */
          public final int addChild(Drawable dr) {
              final int pos = mNumChildren;
              if (pos >= mDrawables.length) {
                  growArray(pos, pos+10);
              }
  
              dr.mutate();
              dr.setVisible(false, true);
              dr.setCallback(mOwner);
  
              mDrawables[pos] = dr;
              mNumChildren++;
              mChildrenChangingConfigurations |= dr.getChangingConfigurations();
  
              invalidateCache();
  
              mConstantPadding = null;
              mCheckedPadding = false;
              mCheckedConstantSize = false;
              mCheckedConstantState = false;
  
              return pos;
          }
  
          /**
           * Invalidates the cached opacity and statefulness.
           */
          void invalidateCache() {
              mCheckedOpacity = false;
              mCheckedStateful = false;
          }
  
          final int getCapacity() {
              return mDrawables.length;
          }
  
          private void createAllFutures() {
              if (mDrawableFutures != null) {
                  final int futureCount = mDrawableFutures.size();
                  for (int keyIndex = 0; keyIndex < futureCount; keyIndex++) {
                      final int index = mDrawableFutures.keyAt(keyIndex);
                      final ConstantState cs = mDrawableFutures.valueAt(keyIndex);
                      mDrawables[index] = prepareDrawable(cs.newDrawable(mSourceRes));
                  }
  
                  mDrawableFutures = null;
              }
          }
  
          private Drawable prepareDrawable(Drawable child) {
              child.setLayoutDirection(mLayoutDirection);
              child = child.mutate();
              child.setCallback(mOwner);
              return child;
          }
  
          public final int getChildCount() {
              return mNumChildren;
          }
  
          /*
           * @deprecated Use {@link #getChild} instead.
           */
          public final Drawable[] getChildren() {
              // Create all futures for backwards compatibility.
              createAllFutures();
  
              return mDrawables;
          }
  
          public final Drawable getChild(int index) {
              final Drawable result = mDrawables[index];
              if (result != null) {
                  return result;
              }
  
              // Prepare future drawable if necessary.
              if (mDrawableFutures != null) {
                  final int keyIndex = mDrawableFutures.indexOfKey(index);
                  if (keyIndex >= 0) {
                      final ConstantState cs = mDrawableFutures.valueAt(keyIndex);
                      final Drawable prepared = prepareDrawable(cs.newDrawable(mSourceRes));
                      mDrawables[index] = prepared;
                      mDrawableFutures.removeAt(keyIndex);
                      if (mDrawableFutures.size() == 0) {
                          mDrawableFutures = null;
                      }
                      return prepared;
                  }
              }
  
              return null;
          }
  
          final boolean setLayoutDirection(int layoutDirection, int currentIndex) {
              boolean changed = false;
  
              // No need to call createAllFutures, since future drawables will
              // change layout direction when they are prepared.
              final int N = mNumChildren;
              final Drawable[] drawables = mDrawables;
              for (int i = 0; i < N; i++) {
                  if (drawables[i] != null) {
                      final boolean childChanged = drawables[i].setLayoutDirection(layoutDirection);
                      if (i == currentIndex) {
                          changed = childChanged;
                      }
                  }
              }
  
              mLayoutDirection = layoutDirection;
  
              return changed;
          }
  
          /**
           * Updates the source density based on the resources used to inflate
           * density-dependent values.
           *
           * @param res the resources used to inflate density-dependent values
           */
          final void updateDensity(Resources res) {
              if (res != null) {
                  mSourceRes = res;
  
                  // The density may have changed since the last update (if any). Any
                  // dimension-type attributes will need their default values scaled.
                  final int targetDensity = Drawable.resolveDensity(res, mDensity);
                  final int sourceDensity = mDensity;
                  mDensity = targetDensity;
  
                  if (sourceDensity != targetDensity) {
                      mCheckedConstantSize = false;
                      mCheckedPadding = false;
                  }
              }
          }
  
          final void applyTheme(Theme theme) {
              if (theme != null) {
                  createAllFutures();
  
                  final int N = mNumChildren;
                  final Drawable[] drawables = mDrawables;
                  for (int i = 0; i < N; i++) {
                      if (drawables[i] != null && drawables[i].canApplyTheme()) {
                          drawables[i].applyTheme(theme);
  
                          // Update cached mask of child changing configurations.
                          mChildrenChangingConfigurations |= drawables[i].getChangingConfigurations();
                      }
                  }
  
                  updateDensity(theme.getResources());
              }
          }
  
          @Override
          public boolean canApplyTheme() {
              final int N = mNumChildren;
              final Drawable[] drawables = mDrawables;
              for (int i = 0; i < N; i++) {
                  final Drawable d = drawables[i];
                  if (d != null) {
                      if (d.canApplyTheme()) {
                          return true;
                      }
                  } else {
                      final ConstantState future = mDrawableFutures.get(i);
                      if (future != null && future.canApplyTheme()) {
                          return true;
                      }
                  }
              }
  
              return false;
          }
  
          private void mutate() {
              // No need to call createAllFutures, since future drawables will
              // mutate when they are prepared.
              final int N = mNumChildren;
              final Drawable[] drawables = mDrawables;
              for (int i = 0; i < N; i++) {
                  if (drawables[i] != null) {
                      drawables[i].mutate();
                  }
              }
  
              mMutated = true;
          }
  
          final void clearMutated() {
              final int N = mNumChildren;
              final Drawable[] drawables = mDrawables;
              for (int i = 0; i < N; i++) {
                  if (drawables[i] != null) {
                      drawables[i].clearMutated();
                  }
              }
  
              mMutated = false;
          }
  
          /**
           * A boolean value indicating whether to use the maximum padding value
           * of all frames in the set (false), or to use the padding value of the
           * frame being shown (true). Default value is false.
           */
          public final void setVariablePadding(boolean variable) {
              mVariablePadding = variable;
          }
  
          public final Rect getConstantPadding() {
              if (mVariablePadding) {
                  return null;
              }
  
              if ((mConstantPadding != null) || mCheckedPadding) {
                  return mConstantPadding;
              }
  
              createAllFutures();
  
              Rect r = null;
              final Rect t = new Rect();
              final int N = mNumChildren;
              final Drawable[] drawables = mDrawables;
              for (int i = 0; i < N; i++) {
                  if (drawables[i].getPadding(t)) {
                      if (r == null) r = new Rect(0, 0, 0, 0);
                      if (t.left > r.left) r.left = t.left;
                      if (t.top > r.top) r.top = t.top;
                      if (t.right > r.right) r.right = t.right;
                      if (t.bottom > r.bottom) r.bottom = t.bottom;
                  }
              }
  
              mCheckedPadding = true;
              return (mConstantPadding = r);
          }
  
          public final void setConstantSize(boolean constant) {
              mConstantSize = constant;
          }
  
          public final boolean isConstantSize() {
              return mConstantSize;
          }
  
          public final int getConstantWidth() {
              if (!mCheckedConstantSize) {
                  computeConstantSize();
              }
  
              return mConstantWidth;
          }
  
          public final int getConstantHeight() {
              if (!mCheckedConstantSize) {
                  computeConstantSize();
              }
  
              return mConstantHeight;
          }
  
          public final int getConstantMinimumWidth() {
              if (!mCheckedConstantSize) {
                  computeConstantSize();
              }
  
              return mConstantMinimumWidth;
          }
  
          public final int getConstantMinimumHeight() {
              if (!mCheckedConstantSize) {
                  computeConstantSize();
              }
  
              return mConstantMinimumHeight;
          }
  
          protected void computeConstantSize() {
              mCheckedConstantSize = true;
  
              createAllFutures();
  
              final int N = mNumChildren;
              final Drawable[] drawables = mDrawables;
              mConstantWidth = mConstantHeight = -1;
              mConstantMinimumWidth = mConstantMinimumHeight = 0;
              for (int i = 0; i < N; i++) {
                  final Drawable dr = drawables[i];
                  int s = dr.getIntrinsicWidth();
                  if (s > mConstantWidth) mConstantWidth = s;
                  s = dr.getIntrinsicHeight();
                  if (s > mConstantHeight) mConstantHeight = s;
                  s = dr.getMinimumWidth();
                  if (s > mConstantMinimumWidth) mConstantMinimumWidth = s;
                  s = dr.getMinimumHeight();
                  if (s > mConstantMinimumHeight) mConstantMinimumHeight = s;
              }
          }
  
          public final void setEnterFadeDuration(int duration) {
              mEnterFadeDuration = duration;
          }
  
          public final int getEnterFadeDuration() {
              return mEnterFadeDuration;
          }
  
          public final void setExitFadeDuration(int duration) {
              mExitFadeDuration = duration;
          }
  
          public final int getExitFadeDuration() {
              return mExitFadeDuration;
          }
  
          public final int getOpacity() {
              if (mCheckedOpacity) {
                  return mOpacity;
              }
  
              createAllFutures();
  
              final int N = mNumChildren;
              final Drawable[] drawables = mDrawables;
              int op = (N > 0) ? drawables[0].getOpacity() : PixelFormat.TRANSPARENT;
              for (int i = 1; i < N; i++) {
                  op = Drawable.resolveOpacity(op, drawables[i].getOpacity());
              }
  
              mOpacity = op;
              mCheckedOpacity = true;
              return op;
          }
  
          public final boolean isStateful() {
              if (mCheckedStateful) {
                  return mStateful;
              }
  
              createAllFutures();
  
              final int N = mNumChildren;
              final Drawable[] drawables = mDrawables;
              boolean isStateful = false;
              for (int i = 0; i < N; i++) {
                  if (drawables[i].isStateful()) {
                      isStateful = true;
                      break;
                  }
              }
  
              mStateful = isStateful;
              mCheckedStateful = true;
              return isStateful;
          }
  
          public void growArray(int oldSize, int newSize) {
              Drawable[] newDrawables = new Drawable[newSize];
              System.arraycopy(mDrawables, 0, newDrawables, 0, oldSize);
              mDrawables = newDrawables;
          }
  
          public synchronized boolean canConstantState() {
              if (mCheckedConstantState) {
                  return mCanConstantState;
              }
  
              createAllFutures();
  
              mCheckedConstantState = true;
  
              final int N = mNumChildren;
              final Drawable[] drawables = mDrawables;
              for (int i = 0; i < N; i++) {
                  if (drawables[i].getConstantState() == null) {
                      mCanConstantState = false;
                      return false;
                  }
              }
  
              mCanConstantState = true;
              return true;
          }
  
      }
  
      protected void setConstantState(DrawableContainerState state) {
          mDrawableContainerState = state;
  
          // The locally cached drawables may have changed.
          if (mCurIndex >= 0) {
              mCurrDrawable = state.getChild(mCurIndex);
              if (mCurrDrawable != null) {
                  initializeDrawableForDisplay(mCurrDrawable);
              }
          }
  
          // Clear out the last drawable. We don't have enough information to
          // propagate local state from the past.
          mLastIndex = -1;
          mLastDrawable = null;
      }
  
      /**
       * Callback that blocks drawable invalidation.
       */
      private static class BlockInvalidateCallback implements Drawable.Callback {
          private Drawable.Callback mCallback;
  
          public BlockInvalidateCallback wrap(Drawable.Callback callback) {
              mCallback = callback;
              return this;
          }
  
          public Drawable.Callback unwrap() {
              final Drawable.Callback callback = mCallback;
              mCallback = null;
              return callback;
          }
  
          @Override
          public void invalidateDrawable(@NonNull Drawable who) {
              // Ignore invalidation.
          }
  
          @Override
          public void scheduleDrawable(@NonNull Drawable who, @NonNull Runnable what, long when) {
              if (mCallback != null) {
                  mCallback.scheduleDrawable(who, what, when);
              }
          }
  
          @Override
          public void unscheduleDrawable(@NonNull Drawable who, @NonNull Runnable what) {
              if (mCallback != null) {
                  mCallback.unscheduleDrawable(who, what);
              }
          }
      }
  }
  