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 com.android.internal.R;
  
  import org.xmlpull.v1.XmlPullParser;
  import org.xmlpull.v1.XmlPullParserException;
  
  import android.annotation.NonNull;
  import android.annotation.Nullable;
  import android.annotation.UnsupportedAppUsage;
  import android.content.res.Resources;
  import android.content.res.Resources.Theme;
  import android.content.res.TypedArray;
  import android.graphics.Canvas;
  import android.graphics.PixelFormat;
  import android.graphics.Rect;
  import android.util.AttributeSet;
  import android.util.TypedValue;
  import android.view.Gravity;
  
  import java.io.IOException;
  
  /**
   * A Drawable that changes the size of another Drawable based on its current
   * level value. You can control how much the child Drawable changes in width
   * and height based on the level, as well as a gravity to control where it is
   * placed in its overall container. Most often used to implement things like
   * progress bars.
   * <p>
   * The default level may be specified from XML using the
   * {@link android.R.styleable#ScaleDrawable_level android:level} property. When
   * this property is not specified, the default level is 0, which corresponds to
   * zero height and/or width depending on the values specified for
   * {@code android.R.styleable#ScaleDrawable_scaleWidth scaleWidth} and
   * {@code android.R.styleable#ScaleDrawable_scaleHeight scaleHeight}. At run
   * time, the level may be set via {@link #setLevel(int)}.
   * <p>
   * A scale drawable may be defined in an XML file with the {@code <scale>}
   * element. For more information, see the guide to
   * <a href="{@docRoot}guide/topics/resources/drawable-resource.html">Drawable
   * Resources</a>.
   *
   * @attr ref android.R.styleable#ScaleDrawable_scaleWidth
   * @attr ref android.R.styleable#ScaleDrawable_scaleHeight
   * @attr ref android.R.styleable#ScaleDrawable_scaleGravity
   * @attr ref android.R.styleable#ScaleDrawable_drawable
   * @attr ref android.R.styleable#ScaleDrawable_level
   */
  public class ScaleDrawable extends DrawableWrapper {
      private static final int MAX_LEVEL = 10000;
  
      private final Rect mTmpRect = new Rect();
  
      @UnsupportedAppUsage
      private ScaleState mState;
  
      ScaleDrawable() {
          this(new ScaleState(null, null), null);
      }
  
      /**
       * Creates a new scale drawable with the specified gravity and scale
       * properties.
       *
       * @param drawable the drawable to scale
       * @param gravity gravity constant (see {@link Gravity} used to position
       *                the scaled drawable within the parent container
       * @param scaleWidth width scaling factor [0...1] to use then the level is
       *                   at the maximum value, or -1 to not scale width
       * @param scaleHeight height scaling factor [0...1] to use then the level
       *                    is at the maximum value, or -1 to not scale height
       */
      public ScaleDrawable(Drawable drawable, int gravity, float scaleWidth, float scaleHeight) {
          this(new ScaleState(null, null), null);
  
          mState.mGravity = gravity;
          mState.mScaleWidth = scaleWidth;
          mState.mScaleHeight = scaleHeight;
  
          setDrawable(drawable);
      }
  
      @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.ScaleDrawable);
  
          // Inflation will advance the XmlPullParser and AttributeSet.
          super.inflate(r, parser, attrs, theme);
  
          updateStateFromTypedArray(a);
          verifyRequiredAttributes(a);
          a.recycle();
  
          updateLocalState();
      }
  
      @Override
      public void applyTheme(@NonNull Theme t) {
          super.applyTheme(t);
  
          final ScaleState state = mState;
          if (state == null) {
              return;
          }
  
          if (state.mThemeAttrs != null) {
              final TypedArray a = t.resolveAttributes(state.mThemeAttrs, R.styleable.ScaleDrawable);
              try {
                  updateStateFromTypedArray(a);
                  verifyRequiredAttributes(a);
              } catch (XmlPullParserException e) {
                  rethrowAsRuntimeException(e);
              } finally {
                  a.recycle();
              }
          }
  
          updateLocalState();
      }
  
      private void verifyRequiredAttributes(@NonNull TypedArray a) throws XmlPullParserException {
          // If we're not waiting on a theme, verify required attributes.
          if (getDrawable() == null && (mState.mThemeAttrs == null
                  || mState.mThemeAttrs[R.styleable.ScaleDrawable_drawable] == 0)) {
              throw new XmlPullParserException(a.getPositionDescription()
                      + ": <scale> tag requires a 'drawable' attribute or "
                      + "child tag defining a drawable");
          }
      }
  
      private void updateStateFromTypedArray(@NonNull TypedArray a) {
          final ScaleState state = mState;
          if (state == null) {
              return;
          }
  
          // Account for any configuration changes.
          state.mChangingConfigurations |= a.getChangingConfigurations();
  
          // Extract the theme attributes, if any.
          state.mThemeAttrs = a.extractThemeAttrs();
  
          state.mScaleWidth = getPercent(a,
                  R.styleable.ScaleDrawable_scaleWidth, state.mScaleWidth);
          state.mScaleHeight = getPercent(a,
                  R.styleable.ScaleDrawable_scaleHeight, state.mScaleHeight);
          state.mGravity = a.getInt(
                  R.styleable.ScaleDrawable_scaleGravity, state.mGravity);
          state.mUseIntrinsicSizeAsMin = a.getBoolean(
                  R.styleable.ScaleDrawable_useIntrinsicSizeAsMinimum, state.mUseIntrinsicSizeAsMin);
          state.mInitialLevel = a.getInt(
                  R.styleable.ScaleDrawable_level, state.mInitialLevel);
      }
  
      private static float getPercent(TypedArray a, int index, float defaultValue) {
          final int type = a.getType(index);
          if (type == TypedValue.TYPE_FRACTION || type == TypedValue.TYPE_NULL) {
              return a.getFraction(index, 1, 1, defaultValue);
          }
  
          // Coerce to float.
          final String s = a.getString(index);
          if (s != null) {
              if (s.endsWith("%")) {
                  final String f = s.substring(0, s.length() - 1);
                  return Float.parseFloat(f) / 100.0f;
              }
          }
  
          return defaultValue;
      }
  
      @Override
      public void draw(Canvas canvas) {
          final Drawable d = getDrawable();
          if (d != null && d.getLevel() != 0) {
              d.draw(canvas);
          }
      }
  
      @Override
      public int getOpacity() {
          final Drawable d = getDrawable();
          if (d.getLevel() == 0) {
              return PixelFormat.TRANSPARENT;
          }
  
          final int opacity = d.getOpacity();
          if (opacity == PixelFormat.OPAQUE && d.getLevel() < MAX_LEVEL) {
              return PixelFormat.TRANSLUCENT;
          }
  
          return opacity;
      }
  
      @Override
      protected boolean onLevelChange(int level) {
          super.onLevelChange(level);
          onBoundsChange(getBounds());
          invalidateSelf();
          return true;
      }
  
      @Override
      protected void onBoundsChange(Rect bounds) {
          final Drawable d = getDrawable();
          final Rect r = mTmpRect;
          final boolean min = mState.mUseIntrinsicSizeAsMin;
          final int level = getLevel();
  
          int w = bounds.width();
          if (mState.mScaleWidth > 0) {
              final int iw = min ? d.getIntrinsicWidth() : 0;
              w -= (int) ((w - iw) * (MAX_LEVEL - level) * mState.mScaleWidth / MAX_LEVEL);
          }
  
          int h = bounds.height();
          if (mState.mScaleHeight > 0) {
              final int ih = min ? d.getIntrinsicHeight() : 0;
              h -= (int) ((h - ih) * (MAX_LEVEL - level) * mState.mScaleHeight / MAX_LEVEL);
          }
  
          final int layoutDirection = getLayoutDirection();
          Gravity.apply(mState.mGravity, w, h, bounds, r, layoutDirection);
  
          if (w > 0 && h > 0) {
              d.setBounds(r.left, r.top, r.right, r.bottom);
          }
      }
  
      @Override
      DrawableWrapperState mutateConstantState() {
          mState = new ScaleState(mState, null);
          return mState;
      }
  
      static final class ScaleState extends DrawableWrapper.DrawableWrapperState {
          /** Constant used to disable scaling for a particular dimension. */
          private static final float DO_NOT_SCALE = -1.0f;
  
          private int[] mThemeAttrs;
  
          float mScaleWidth = DO_NOT_SCALE;
          float mScaleHeight = DO_NOT_SCALE;
          int mGravity = Gravity.LEFT;
          boolean mUseIntrinsicSizeAsMin = false;
          int mInitialLevel = 0;
  
          ScaleState(ScaleState orig, Resources res) {
              super(orig, res);
  
              if (orig != null) {
                  mScaleWidth = orig.mScaleWidth;
                  mScaleHeight = orig.mScaleHeight;
                  mGravity = orig.mGravity;
                  mUseIntrinsicSizeAsMin = orig.mUseIntrinsicSizeAsMin;
                  mInitialLevel = orig.mInitialLevel;
              }
          }
  
          @Override
          public Drawable newDrawable(Resources res) {
              return new ScaleDrawable(this, res);
          }
      }
  
      /**
       * Creates a new ScaleDrawable based on the specified constant state.
       * <p>
       * The resulting drawable is guaranteed to have a new constant state.
       *
       * @param state constant state from which the drawable inherits
       */
      private ScaleDrawable(ScaleState state, Resources res) {
          super(state, res);
  
          mState = state;
  
          updateLocalState();
      }
  
      private void updateLocalState() {
          setLevel(mState.mInitialLevel);
      }
  }
  
  