content: /*
   * Copyright (C) 2016 Bilibili
   *
   * 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.bilibili.magicasakura.utils;
  
  import android.content.Context;
  import android.content.res.TypedArray;
  import android.graphics.Color;
  import android.graphics.Rect;
  import android.graphics.drawable.Drawable;
  import android.graphics.drawable.GradientDrawable;
  import android.os.Build;
  import android.support.v4.graphics.ColorUtils;
  import android.util.AttributeSet;
  import android.util.Log;
  import android.util.TypedValue;
  
  import org.xmlpull.v1.XmlPullParser;
  import org.xmlpull.v1.XmlPullParserException;
  
  import java.io.IOException;
  import java.lang.reflect.Field;
  
  /**
   * @author xyczero617@gmail.com
   * @time 16/2/22
   */
  public class GradientDrawableUtils extends DrawableUtils {
      private static Field sPaddingField;
      private static Field sStPaddingField;
      private static Field sStGradientPositions;
      private static Field sStGradientAngle;
  
      @Override
      protected Drawable inflateDrawable(Context context, XmlPullParser parser, AttributeSet attrs) throws XmlPullParserException, IOException {
          GradientDrawable gradientDrawable = new GradientDrawable();
          inflateGradientRootElement(context, attrs, gradientDrawable);
  
          int type;
          final int innerDepth = parser.getDepth() + 1;
          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) {
                  continue;
              }
  
              String name = parser.getName();
  
              if (name.equals("size")) {
                  final int width = getAttrDimensionPixelSize(context, attrs, android.R.attr.width);
                  final int height = getAttrDimensionPixelSize(context, attrs, android.R.attr.height);
                  gradientDrawable.setSize(width, height);
              } else if (name.equals("gradient") && Build.VERSION.SDK_INT > Build.VERSION_CODES.ICE_CREAM_SANDWICH_MR1) {
                  final float centerX = getAttrFloatOrFraction(context, attrs, android.R.attr.centerX, 0.5f, 1.0f, 1.0f);
                  final float centerY = getAttrFloatOrFraction(context, attrs, android.R.attr.centerY, 0.5f, 1.0f, 1.0f);
                  gradientDrawable.setGradientCenter(centerX, centerY);
                  final boolean useLevel = getAttrBoolean(context, attrs, android.R.attr.useLevel, false);
                  gradientDrawable.setUseLevel(useLevel);
                  final int gradientType = getAttrInt(context, attrs, android.R.attr.type, 0);
                  gradientDrawable.setGradientType(gradientType);
                  final int startColor = getAttrColor(context, attrs, android.R.attr.startColor, Color.TRANSPARENT);
                  final int centerColor = getAttrColor(context, attrs, android.R.attr.centerColor, Color.TRANSPARENT);
                  final int endColor = getAttrColor(context, attrs, android.R.attr.endColor, Color.TRANSPARENT);
                  if (!getAttrHasValue(context, attrs, android.R.attr.centerColor)) {
                      gradientDrawable.setColors(new int[]{startColor, endColor});
                  } else {
                      gradientDrawable.setColors(new int[]{startColor, centerColor, endColor});
                      setStGradientPositions(gradientDrawable.getConstantState(), 0.0f, centerX != 0.5f ? centerX : centerY, 1f);
                  }
  
                  if (gradientType == GradientDrawable.LINEAR_GRADIENT) {
                      int angle = (int) getAttrFloat(context, attrs, android.R.attr.angle, 0.0f);
                      angle %= 360;
  
                      if (angle % 45 != 0) {
                          throw new XmlPullParserException("<gradient> tag requires"
                                  + "'angle' attribute to "
                                  + "be a multiple of 45");
                      }
  
                      setStGradientAngle(gradientDrawable.getConstantState(), angle);
  
                      switch (angle) {
                          case 0:
                              gradientDrawable.setOrientation(GradientDrawable.Orientation.LEFT_RIGHT);
                              break;
                          case 45:
                              gradientDrawable.setOrientation(GradientDrawable.Orientation.BL_TR);
                              break;
                          case 90:
                              gradientDrawable.setOrientation(GradientDrawable.Orientation.BOTTOM_TOP);
                              break;
                          case 135:
                              gradientDrawable.setOrientation(GradientDrawable.Orientation.BR_TL);
                              break;
                          case 180:
                              gradientDrawable.setOrientation(GradientDrawable.Orientation.RIGHT_LEFT);
                              break;
                          case 225:
                              gradientDrawable.setOrientation(GradientDrawable.Orientation.TR_BL);
                              break;
                          case 270:
                              gradientDrawable.setOrientation(GradientDrawable.Orientation.TOP_BOTTOM);
                              break;
                          case 315:
                              gradientDrawable.setOrientation(GradientDrawable.Orientation.TL_BR);
                              break;
                      }
                  } else {
                      setGradientRadius(context, attrs, gradientDrawable, gradientType);
                  }
              } else if (name.equals("solid")) {
                  int color = getAttrColor(context, attrs, android.R.attr.color, Color.TRANSPARENT);
                  gradientDrawable.setColor(getAlphaColor(color, getAttrFloat(context, attrs, android.R.attr.alpha, 1.0f)));
              } else if (name.equals("stroke")) {
                  final float alphaMod = getAttrFloat(context, attrs, android.R.attr.alpha, 1.0f);
                  final int strokeColor = getAttrColor(context, attrs, android.R.attr.color, Color.TRANSPARENT);
                  final int strokeWidth = getAttrDimensionPixelSize(context, attrs, android.R.attr.width);
                  final float dashWidth = getAttrDimension(context, attrs, android.R.attr.dashWidth);
                  if (dashWidth != 0.0f) {
                      final float dashGap = getAttrDimension(context, attrs, android.R.attr.dashGap);
                      gradientDrawable.setStroke(strokeWidth, getAlphaColor(strokeColor, alphaMod), dashWidth, dashGap);
                  } else {
                      gradientDrawable.setStroke(strokeWidth, getAlphaColor(strokeColor, alphaMod));
                  }
              } else if (name.equals("corners")) {
                  final int radius = getAttrDimensionPixelSize(context, attrs, android.R.attr.radius);
                  gradientDrawable.setCornerRadius(radius);
  
                  final int topLeftRadius = getAttrDimensionPixelSize(context, attrs, android.R.attr.topLeftRadius, radius);
                  final int topRightRadius = getAttrDimensionPixelSize(context, attrs, android.R.attr.topRightRadius, radius);
                  final int bottomLeftRadius = getAttrDimensionPixelSize(context, attrs, android.R.attr.bottomLeftRadius, radius);
                  final int bottomRightRadius = getAttrDimensionPixelSize(context, attrs, android.R.attr.bottomRightRadius, radius);
                  if (topLeftRadius != radius || topRightRadius != radius ||
                          bottomLeftRadius != radius || bottomRightRadius != radius) {
                      // The corner radii are specified in clockwise order (see Path.addRoundRect())
                      gradientDrawable.setCornerRadii(new float[]{
                              topLeftRadius, topLeftRadius,
                              topRightRadius, topRightRadius,
                              bottomRightRadius, bottomRightRadius,
                              bottomLeftRadius, bottomLeftRadius
                      });
                  }
              } else if (name.equals("padding")) {
                  final int paddingLeft = getAttrDimensionPixelOffset(context, attrs, android.R.attr.left);
                  final int paddingTop = getAttrDimensionPixelOffset(context, attrs, android.R.attr.top);
                  final int paddingRight = getAttrDimensionPixelOffset(context, attrs, android.R.attr.right);
                  final int paddingBottom = getAttrDimensionPixelOffset(context, attrs, android.R.attr.bottom);
                  if (paddingLeft != 0 || paddingTop != 0 || paddingRight != 0 || paddingBottom != 0) {
                      final Rect pad = new Rect();
                      pad.set(paddingLeft, paddingTop, paddingRight, paddingBottom);
                      try {
                          if (sPaddingField == null) {
                              sPaddingField = GradientDrawable.class.getDeclaredField("mPadding");
                              sPaddingField.setAccessible(true);
                          }
                          sPaddingField.set(gradientDrawable, pad);
                          if (sStPaddingField == null) {
                              sStPaddingField = Class.forName("android.graphics.drawable.GradientDrawable$GradientState").getDeclaredField("mPadding");
                              sStPaddingField.setAccessible(true);
                          }
                          sStPaddingField.set(gradientDrawable.getConstantState(), pad);
                      } catch (NoSuchFieldException e) {
                          e.printStackTrace();
                      } catch (IllegalAccessException e) {
                          e.printStackTrace();
                      } catch (ClassNotFoundException e) {
                          e.printStackTrace();
                      }
                  }
              } else {
                  Log.w("drawable", "Bad element under <shape>: " + name);
              }
          }
          return gradientDrawable;
      }
  
      void inflateGradientRootElement(Context context, AttributeSet attrs, GradientDrawable gradientDrawable) {
          int shape = getAttrInt(context, attrs, android.R.attr.shape, GradientDrawable.RECTANGLE);
          gradientDrawable.setShape(shape);
          boolean dither = getAttrBoolean(context, attrs, android.R.attr.dither, false);
          gradientDrawable.setDither(dither);
      }
  
      void setGradientRadius(Context context, AttributeSet attrs, GradientDrawable drawable, int gradientType) throws XmlPullParserException {
          TypedArray a = obtainAttributes(context.getResources(), context.getTheme(), attrs, new int[]{android.R.attr.gradientRadius});
          TypedValue value = a.peekValue(0);
          if (value != null) {
              boolean radiusRel = value.type == TypedValue.TYPE_FRACTION;
              drawable.setGradientRadius(radiusRel ? value.getFraction(1.0f, 1.0f) : value.getFloat());
          } else if (gradientType == GradientDrawable.RADIAL_GRADIENT) {
              throw new XmlPullParserException(
                      "<gradient> tag requires 'gradientRadius' "
                              + "attribute with radial type");
          }
          a.recycle();
      }
  
      void setStGradientAngle(Drawable.ConstantState constantState, int angle) {
          try {
              if (sStGradientAngle == null) {
                  sStGradientAngle = Class.forName("android.graphics.drawable.GradientDrawable$GradientState").getDeclaredField("mAngle");
                  sStGradientAngle.setAccessible(true);
              }
              sStGradientAngle.set(constantState, angle);
          } catch (NoSuchFieldException e) {
              e.printStackTrace();
          } catch (ClassNotFoundException e) {
              e.printStackTrace();
          } catch (IllegalAccessException e) {
              e.printStackTrace();
          }
      }
  
      void setStGradientPositions(Drawable.ConstantState constantState, float... positions) {
          try {
              if (sStGradientPositions == null) {
                  sStGradientPositions = Class.forName("android.graphics.drawable.GradientDrawable$GradientState").getDeclaredField("mPositions");
                  sStGradientPositions.setAccessible(true);
              }
              sStGradientPositions.set(constantState, positions);
          } catch (NoSuchFieldException e) {
              e.printStackTrace();
          } catch (ClassNotFoundException e) {
              e.printStackTrace();
          } catch (IllegalAccessException e) {
              e.printStackTrace();
          }
      }
  
      float getAttrFloatOrFraction(Context context, AttributeSet attrs, int attr, float defaultValue, float base, float pbase) {
          TypedArray a = obtainAttributes(context.getResources(), context.getTheme(), attrs, new int[]{attr});
          TypedValue tv = a.peekValue(0);
          float v = defaultValue;
          if (tv != null) {
              boolean isFraction = tv.type == TypedValue.TYPE_FRACTION;
              v = isFraction ? tv.getFraction(base, pbase) : tv.getFloat();
          }
          a.recycle();
          return v;
      }
  
      int getAlphaColor(int baseColor, float alpha) {
          return alpha != 1.0f
                  ? ColorUtils.setAlphaComponent(baseColor, Math.round(Color.alpha(baseColor) * alpha))
                  : baseColor;
      }
  }
  