content: /*
   * Copyright (C) 2016 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 com.android.internal.graphics.drawable;
  
  import android.animation.ValueAnimator;
  import android.annotation.NonNull;
  import android.annotation.Nullable;
  import android.content.res.Resources;
  import android.content.res.Resources.Theme;
  import android.content.res.TypedArray;
  import android.graphics.drawable.Animatable;
  import android.graphics.drawable.Drawable;
  import android.graphics.drawable.DrawableContainer;
  import android.util.AttributeSet;
  
  import com.android.internal.R;
  
  import org.xmlpull.v1.XmlPullParser;
  import org.xmlpull.v1.XmlPullParserException;
  
  import java.io.IOException;
  
  /**
   * An internal DrawableContainer class, used to draw different things depending on animation scale.
   * i.e: animation scale can be 0 in battery saver mode.
   * This class contains 2 drawable, one is animatable, the other is static. When animation scale is
   * not 0, the animatable drawable will the drawn. Otherwise, the static drawable will be drawn.
   * <p>This class implements Animatable since ProgressBar can pick this up similarly as an
   * AnimatedVectorDrawable.
   * <p>It can be defined in an XML file with the {@code <AnimationScaleListDrawable>}
   * element.
   */
  public class AnimationScaleListDrawable extends DrawableContainer implements Animatable {
      private static final String TAG = "AnimationScaleListDrawable";
      private AnimationScaleListState mAnimationScaleListState;
      private boolean mMutated;
  
      public AnimationScaleListDrawable() {
          this(null, null);
      }
  
      private AnimationScaleListDrawable(@Nullable AnimationScaleListState state,
              @Nullable Resources res) {
          // Every scale list drawable has its own constant state.
          final AnimationScaleListState newState = new AnimationScaleListState(state, this, res);
          setConstantState(newState);
          onStateChange(getState());
      }
  
      /**
       * Set the current drawable according to the animation scale. If scale is 0, then pick the
       * static drawable, otherwise, pick the animatable drawable.
       */
      @Override
      protected boolean onStateChange(int[] stateSet) {
          final boolean changed = super.onStateChange(stateSet);
          int idx = mAnimationScaleListState.getCurrentDrawableIndexBasedOnScale();
          return selectDrawable(idx) || changed;
      }
  
  
      @Override
      public void inflate(@NonNull Resources r, @NonNull XmlPullParser parser,
              @NonNull AttributeSet attrs, @Nullable Theme theme)
              throws XmlPullParserException, IOException {
          final TypedArray a = obtainAttributes(r, theme, attrs,
                  R.styleable.AnimationScaleListDrawable);
          updateDensity(r);
          a.recycle();
  
          inflateChildElements(r, parser, attrs, theme);
  
          onStateChange(getState());
      }
  
      /**
       * Inflates child elements from XML.
       */
      private void inflateChildElements(@NonNull Resources r, @NonNull XmlPullParser parser,
              @NonNull AttributeSet attrs, @Nullable Theme theme)
              throws XmlPullParserException, IOException {
          final AnimationScaleListState state = mAnimationScaleListState;
          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;
              }
  
              // Either pick up the android:drawable attribute.
              final TypedArray a = obtainAttributes(r, theme, attrs,
                      R.styleable.AnimationScaleListDrawableItem);
              Drawable dr = a.getDrawable(R.styleable.AnimationScaleListDrawableItem_drawable);
              a.recycle();
  
              // Or parse the child element under <item>.
              if (dr == null) {
                  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");
                  }
                  dr = Drawable.createFromXmlInner(r, parser, attrs, theme);
              }
  
              state.addDrawable(dr);
          }
      }
  
      @Override
      public Drawable mutate() {
          if (!mMutated && super.mutate() == this) {
              mAnimationScaleListState.mutate();
              mMutated = true;
          }
          return this;
      }
  
      @Override
      public void clearMutated() {
          super.clearMutated();
          mMutated = false;
      }
  
      @Override
      public void start() {
          Drawable dr = getCurrent();
          if (dr != null && dr instanceof Animatable) {
              ((Animatable) dr).start();
          }
      }
  
      @Override
      public void stop() {
          Drawable dr = getCurrent();
          if (dr != null && dr instanceof Animatable) {
              ((Animatable) dr).stop();
          }
      }
  
      @Override
      public boolean isRunning() {
          boolean result = false;
          Drawable dr = getCurrent();
          if (dr != null && dr instanceof Animatable) {
              result = ((Animatable) dr).isRunning();
          }
          return result;
      }
  
      static class AnimationScaleListState extends DrawableContainerState {
          int[] mThemeAttrs = null;
          // The index of the last static drawable.
          int mStaticDrawableIndex = -1;
          // The index of the last animatable drawable.
          int mAnimatableDrawableIndex = -1;
  
          AnimationScaleListState(AnimationScaleListState orig, AnimationScaleListDrawable owner,
                  Resources res) {
              super(orig, owner, res);
  
              if (orig != null) {
                  // Perform a shallow copy and rely on mutate() to deep-copy.
                  mThemeAttrs = orig.mThemeAttrs;
  
                  mStaticDrawableIndex = orig.mStaticDrawableIndex;
                  mAnimatableDrawableIndex = orig.mAnimatableDrawableIndex;
              }
  
          }
  
          void mutate() {
              mThemeAttrs = mThemeAttrs != null ? mThemeAttrs.clone() : null;
          }
  
          /**
           * Add the drawable into the container.
           * This class only keep track one animatable drawable, and one static. If there are multiple
           * defined in the XML, then pick the last one.
           */
          int addDrawable(Drawable drawable) {
              final int pos = addChild(drawable);
              if (drawable instanceof Animatable) {
                  mAnimatableDrawableIndex = pos;
              } else {
                  mStaticDrawableIndex = pos;
              }
              return pos;
          }
  
          @Override
          public Drawable newDrawable() {
              return new AnimationScaleListDrawable(this, null);
          }
  
          @Override
          public Drawable newDrawable(Resources res) {
              return new AnimationScaleListDrawable(this, res);
          }
  
          @Override
          public boolean canApplyTheme() {
              return mThemeAttrs != null || super.canApplyTheme();
          }
  
          public int getCurrentDrawableIndexBasedOnScale() {
              if (ValueAnimator.getDurationScale() == 0) {
                  return mStaticDrawableIndex;
              }
              return mAnimatableDrawableIndex;
          }
      }
  
      @Override
      public void applyTheme(@NonNull Theme theme) {
          super.applyTheme(theme);
  
          onStateChange(getState());
      }
  
      @Override
      protected void setConstantState(@NonNull DrawableContainerState state) {
          super.setConstantState(state);
  
          if (state instanceof AnimationScaleListState) {
              mAnimationScaleListState = (AnimationScaleListState) state;
          }
      }
  }
  
  