content: /*
   * Copyright (C) 2008 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.layoutlib.bridge.impl;
  
  import com.android.SdkConstants;
  import com.android.ide.common.rendering.api.AssetRepository;
  import com.android.ide.common.rendering.api.DensityBasedResourceValue;
  import com.android.ide.common.rendering.api.ILayoutPullParser;
  import com.android.ide.common.rendering.api.LayoutLog;
  import com.android.ide.common.rendering.api.LayoutlibCallback;
  import com.android.ide.common.rendering.api.RenderResources;
  import com.android.ide.common.rendering.api.ResourceNamespace;
  import com.android.ide.common.rendering.api.ResourceReference;
  import com.android.ide.common.rendering.api.ResourceValue;
  import com.android.internal.util.XmlUtils;
  import com.android.layoutlib.bridge.Bridge;
  import com.android.layoutlib.bridge.android.BridgeContext;
  import com.android.layoutlib.bridge.android.BridgeContext.Key;
  import com.android.layoutlib.bridge.android.BridgeXmlBlockParser;
  import com.android.ninepatch.NinePatch;
  import com.android.ninepatch.NinePatchChunk;
  import com.android.resources.Density;
  import com.android.resources.ResourceType;
  
  import org.xmlpull.v1.XmlPullParser;
  import org.xmlpull.v1.XmlPullParserException;
  
  import android.annotation.NonNull;
  import android.annotation.Nullable;
  import android.content.res.BridgeAssetManager;
  import android.content.res.ColorStateList;
  import android.content.res.ComplexColor;
  import android.content.res.ComplexColor_Accessor;
  import android.content.res.GradientColor;
  import android.content.res.Resources;
  import android.content.res.Resources.Theme;
  import android.graphics.Bitmap;
  import android.graphics.Bitmap_Delegate;
  import android.graphics.NinePatch_Delegate;
  import android.graphics.Rect;
  import android.graphics.Typeface;
  import android.graphics.Typeface_Accessor;
  import android.graphics.Typeface_Delegate;
  import android.graphics.drawable.BitmapDrawable;
  import android.graphics.drawable.ColorDrawable;
  import android.graphics.drawable.Drawable;
  import android.graphics.drawable.NinePatchDrawable;
  import android.util.TypedValue;
  
  import java.io.FileNotFoundException;
  import java.io.IOException;
  import java.io.InputStream;
  import java.net.MalformedURLException;
  import java.util.HashSet;
  import java.util.Set;
  import java.util.regex.Matcher;
  import java.util.regex.Pattern;
  
  import static android.content.res.AssetManager.ACCESS_STREAMING;
  
  /**
   * Helper class to provide various conversion method used in handling android resources.
   */
  public final class ResourceHelper {
      private static final Key<Set<ResourceValue>> KEY_GET_DRAWABLE =
              Key.create("ResourceHelper.getDrawable");
      private static final Pattern sFloatPattern = Pattern.compile("(-?[0-9]+(?:\\.[0-9]+)?)(.*)");
      private static final float[] sFloatOut = new float[1];
  
      private static final TypedValue mValue = new TypedValue();
  
      /**
       * Returns the color value represented by the given string value.
       *
       * @param value the color value
       * @return the color as an int
       * @throws NumberFormatException if the conversion failed.
       */
      public static int getColor(@Nullable String value) {
          if (value == null) {
              throw new NumberFormatException("null value");
          }
  
          value = value.trim();
          int len = value.length();
  
          // make sure it's not longer than 32bit or smaller than the RGB format
          if (len < 2 || len > 9) {
              throw new NumberFormatException(String.format(
                      "Color value '%s' has wrong size. Format is either" +
                              "#AARRGGBB, #RRGGBB, #RGB, or #ARGB",
                      value));
          }
  
          if (value.charAt(0) != '#') {
              if (value.startsWith(SdkConstants.PREFIX_THEME_REF)) {
                  throw new NumberFormatException(String.format(
                          "Attribute '%s' not found. Are you using the right theme?", value));
              }
              throw new NumberFormatException(
                      String.format("Color value '%s' must start with #", value));
          }
  
          value = value.substring(1);
  
          if (len == 4) { // RGB format
              char[] color = new char[8];
              color[0] = color[1] = 'F';
              color[2] = color[3] = value.charAt(0);
              color[4] = color[5] = value.charAt(1);
              color[6] = color[7] = value.charAt(2);
              value = new String(color);
          } else if (len == 5) { // ARGB format
              char[] color = new char[8];
              color[0] = color[1] = value.charAt(0);
              color[2] = color[3] = value.charAt(1);
              color[4] = color[5] = value.charAt(2);
              color[6] = color[7] = value.charAt(3);
              value = new String(color);
          } else if (len == 7) {
              value = "FF" + value;
          }
  
          // this is a RRGGBB or AARRGGBB value
  
          // Integer.parseInt will fail to parse strings like "ff191919", so we use
          // a Long, but cast the result back into an int, since we know that we're only
          // dealing with 32 bit values.
          return (int)Long.parseLong(value, 16);
      }
  
      /**
       * Returns a {@link ComplexColor} from the given {@link ResourceValue}
       *
       * @param resValue the value containing a color value or a file path to a complex color
       * definition
       * @param context the current context
       * @param theme the theme to use when resolving the complex color
       * @param allowGradients when false, only {@link ColorStateList} will be returned. If a {@link
       * GradientColor} is found, null will be returned.
       */
      @Nullable
      private static ComplexColor getInternalComplexColor(@NonNull ResourceValue resValue,
              @NonNull BridgeContext context, @Nullable Theme theme, boolean allowGradients) {
          String value = resValue.getValue();
          if (value == null || RenderResources.REFERENCE_NULL.equals(value)) {
              return null;
          }
  
          // try to load the color state list from an int
          try {
              int color = getColor(value);
              return ColorStateList.valueOf(color);
          } catch (NumberFormatException ignored) {
          }
  
          try {
              BridgeXmlBlockParser blockParser = getXmlBlockParser(context, resValue);
              if (blockParser != null) {
                  try {
                      // Advance the parser to the first element so we can detect if it's a
                      // color list or a gradient color
                      int type;
                      //noinspection StatementWithEmptyBody
                      while ((type = blockParser.next()) != XmlPullParser.START_TAG
                              && type != XmlPullParser.END_DOCUMENT) {
                          // Seek parser to start tag.
                      }
  
                      if (type != XmlPullParser.START_TAG) {
                          assert false : "No start tag found";
                          return null;
                      }
  
                      final String name = blockParser.getName();
                      if (allowGradients && "gradient".equals(name)) {
                          return ComplexColor_Accessor.createGradientColorFromXmlInner(
                                  context.getResources(),
                                  blockParser, blockParser,
                                  theme);
                      } else if ("selector".equals(name)) {
                          return ComplexColor_Accessor.createColorStateListFromXmlInner(
                                  context.getResources(),
                                  blockParser, blockParser,
                                  theme);
                      }
                  } finally {
                      blockParser.ensurePopped();
                  }
              }
          } catch (XmlPullParserException e) {
              Bridge.getLog().error(LayoutLog.TAG_BROKEN,
                      "Failed to configure parser for " + value, e, null /*data*/);
              // we'll return null below.
          } catch (Exception e) {
              // this is an error and not warning since the file existence is
              // checked before attempting to parse it.
              Bridge.getLog().error(LayoutLog.TAG_RESOURCES_READ,
                      "Failed to parse file " + value, e, null /*data*/);
  
              return null;
          }
  
          return null;
      }
  
      /**
       * Returns a {@link ColorStateList} from the given {@link ResourceValue}
       *
       * @param resValue the value containing a color value or a file path to a complex color
       * definition
       * @param context the current context
       */
      @Nullable
      public static ColorStateList getColorStateList(@NonNull ResourceValue resValue,
              @NonNull BridgeContext context, @Nullable Resources.Theme theme) {
          return (ColorStateList) getInternalComplexColor(resValue, context,
                  theme != null ? theme : context.getTheme(),
                  false);
      }
  
      /**
       * Returns a {@link ComplexColor} from the given {@link ResourceValue}
       *
       * @param resValue the value containing a color value or a file path to a complex color
       * definition
       * @param context the current context
       */
      @Nullable
      public static ComplexColor getComplexColor(@NonNull ResourceValue resValue,
              @NonNull BridgeContext context, @Nullable Resources.Theme theme) {
          return getInternalComplexColor(resValue, context,
                  theme != null ? theme : context.getTheme(),
                  true);
      }
  
      /**
       * Returns a drawable from the given value.
       *
       * @param value The value that contains a path to a 9 patch, a bitmap or a xml based drawable,
       *     or an hexadecimal color
       * @param context the current context
       */
      @Nullable
      public static Drawable getDrawable(ResourceValue value, BridgeContext context) {
          return getDrawable(value, context, null);
      }
  
      /**
       * Returns a {@link BridgeXmlBlockParser} to parse the given {@link ResourceValue}. The passed
       * value must point to an XML resource.
       */
      @Nullable
      public static BridgeXmlBlockParser getXmlBlockParser(@NonNull BridgeContext context,
              @NonNull ResourceValue value) throws XmlPullParserException {
          String stringValue = value.getValue();
          if (RenderResources.REFERENCE_NULL.equals(stringValue)) {
              return null;
          }
  
          XmlPullParser parser = null;
          ResourceNamespace namespace;
  
          LayoutlibCallback layoutlibCallback = context.getLayoutlibCallback();
          // Framework values never need a PSI parser. They do not change and the do not contain
          // aapt:attr attributes.
          if (!value.isFramework()) {
              parser = layoutlibCallback.getParser(value);
          }
  
          if (parser != null) {
              namespace = ((ILayoutPullParser) parser).getLayoutNamespace();
          } else {
              parser = ParserFactory.create(stringValue);
              namespace = value.getNamespace();
          }
  
          return parser == null
                  ? null
                  : new BridgeXmlBlockParser(parser, context, namespace);
      }
  
      /**
       * Returns a drawable from the given value.
       *
       * @param value The value that contains a path to a 9 patch, a bitmap or a xml based drawable,
       *     or an hexadecimal color
       * @param context the current context
       * @param theme the theme to be used to inflate the drawable.
       */
      @Nullable
      public static Drawable getDrawable(ResourceValue value, BridgeContext context, Theme theme) {
          if (value == null) {
              return null;
          }
          String stringValue = value.getValue();
          if (RenderResources.REFERENCE_NULL.equals(stringValue)) {
              return null;
          }
  
          String lowerCaseValue = stringValue.toLowerCase();
          // try the simple case first. Attempt to get a color from the value
          try {
              int color = getColor(stringValue);
              return new ColorDrawable(color);
          } catch (NumberFormatException ignore) {
          }
  
          Density density = Density.MEDIUM;
          if (value instanceof DensityBasedResourceValue) {
              density = ((DensityBasedResourceValue) value).getResourceDensity();
              if (density == Density.NODPI || density == Density.ANYDPI) {
                  density = Density.getEnum(context.getConfiguration().densityDpi);
              }
          }
  
          if (lowerCaseValue.endsWith(NinePatch.EXTENSION_9PATCH)) {
              try {
                  return getNinePatchDrawable(density, value.isFramework(), stringValue, context);
              } catch (IOException e) {
                  // failed to read the file, we'll return null below.
                  Bridge.getLog().error(LayoutLog.TAG_RESOURCES_READ,
                          "Failed to load " + stringValue, e, null /*data*/);
              }
  
              return null;
          } else if (lowerCaseValue.endsWith(".xml") ||
                  value.getResourceType() == ResourceType.AAPT) {
              // create a block parser for the file
              try {
                  BridgeXmlBlockParser blockParser = getXmlBlockParser(context, value);
                  if (blockParser != null) {
                      Set<ResourceValue> visitedValues = context.getUserData(KEY_GET_DRAWABLE);
                      if (visitedValues == null) {
                          visitedValues = new HashSet<>();
                          context.putUserData(KEY_GET_DRAWABLE, visitedValues);
                      }
                      if (!visitedValues.add(value)) {
                          Bridge.getLog().error(null, "Cyclic dependency in " + stringValue, null);
                          return null;
                      }
  
                      try {
                          return Drawable.createFromXml(context.getResources(), blockParser, theme);
                      } finally {
                          visitedValues.remove(value);
                          blockParser.ensurePopped();
                      }
                  }
              } catch (Exception e) {
                  // this is an error and not warning since the file existence is checked before
                  // attempting to parse it.
                  Bridge.getLog().error(null, "Failed to parse file " + stringValue, e,
                          null /*data*/);
              }
  
              return null;
          } else {
              AssetRepository repository = getAssetRepository(context);
              if (repository.isFileResource(stringValue)) {
                  try {
                      Bitmap bitmap = Bridge.getCachedBitmap(stringValue,
                              value.isFramework() ? null : context.getProjectKey());
  
                      if (bitmap == null) {
                          InputStream stream;
                          try {
                              stream = repository.openNonAsset(0, stringValue, ACCESS_STREAMING);
  
                          } catch (FileNotFoundException e) {
                              stream = null;
                          }
                          bitmap =
                                  Bitmap_Delegate.createBitmap(stream, false /*isMutable*/, density);
                          Bridge.setCachedBitmap(stringValue, bitmap,
                                  value.isFramework() ? null : context.getProjectKey());
                      }
  
                      return new BitmapDrawable(context.getResources(), bitmap);
                  } catch (IOException e) {
                      // we'll return null below
                      Bridge.getLog().error(LayoutLog.TAG_RESOURCES_READ,
                              "Failed to load " + stringValue, e, null /*data*/);
                  }
              }
          }
  
          return null;
      }
  
      private static AssetRepository getAssetRepository(@NonNull BridgeContext context) {
          BridgeAssetManager assetManager = context.getAssets();
          return assetManager.getAssetRepository();
      }
  
      /**
       * Returns a {@link Typeface} given a font name. The font name, can be a system font family
       * (like sans-serif) or a full path if the font is to be loaded from resources.
       */
      public static Typeface getFont(String fontName, BridgeContext context, Theme theme, boolean
              isFramework) {
          if (fontName == null) {
              return null;
          }
  
          if (Typeface_Accessor.isSystemFont(fontName)) {
              // Shortcut for the case where we are asking for a system font name. Those are not
              // loaded using external resources.
              return null;
          }
  
  
          return Typeface_Delegate.createFromDisk(context, fontName, isFramework);
      }
  
      /**
       * Returns a {@link Typeface} given a font name. The font name, can be a system font family
       * (like sans-serif) or a full path if the font is to be loaded from resources.
       */
      public static Typeface getFont(ResourceValue value, BridgeContext context, Theme theme) {
          if (value == null) {
              return null;
          }
  
          return getFont(value.getValue(), context, theme, value.isFramework());
      }
  
      private static Drawable getNinePatchDrawable(Density density, boolean isFramework,
              String path, BridgeContext context) throws IOException {
          // see if we still have both the chunk and the bitmap in the caches
          NinePatchChunk chunk = Bridge.getCached9Patch(path,
                  isFramework ? null : context.getProjectKey());
          Bitmap bitmap = Bridge.getCachedBitmap(path,
                  isFramework ? null : context.getProjectKey());
  
          // if either chunk or bitmap is null, then we reload the 9-patch file.
          if (chunk == null || bitmap == null) {
              try {
                  AssetRepository repository = getAssetRepository(context);
                  if (!repository.isFileResource(path)) {
                      return null;
                  }
                  InputStream stream = repository.openNonAsset(0, path, ACCESS_STREAMING);
                  NinePatch ninePatch = NinePatch.load(stream, true /*is9Patch*/,
                          false /* convert */);
                  if (ninePatch != null) {
                      if (chunk == null) {
                          chunk = ninePatch.getChunk();
  
                          Bridge.setCached9Patch(path, chunk,
                                  isFramework ? null : context.getProjectKey());
                      }
  
                      if (bitmap == null) {
                          bitmap = Bitmap_Delegate.createBitmap(ninePatch.getImage(),
                                  false /*isMutable*/,
                                  density);
  
                          Bridge.setCachedBitmap(path, bitmap,
                                  isFramework ? null : context.getProjectKey());
                      }
                  }
              } catch (MalformedURLException e) {
                  // URL is wrong, we'll return null below
              }
          }
  
          if (chunk != null && bitmap != null) {
              int[] padding = chunk.getPadding();
              Rect paddingRect = new Rect(padding[0], padding[1], padding[2], padding[3]);
  
              return new NinePatchDrawable(context.getResources(), bitmap,
                      NinePatch_Delegate.serialize(chunk),
                      paddingRect, null);
          }
  
          return null;
      }
  
      /**
       * Looks for an attribute in the current theme.
       *
       * @param resources the render resources
       * @param attr the attribute reference
       * @param defaultValue the default value.
       * @return the value of the attribute or the default one if not found.
       */
      public static boolean getBooleanThemeValue(@NonNull RenderResources resources,
              @NonNull ResourceReference attr, boolean defaultValue) {
          ResourceValue value = resources.findItemInTheme(attr);
          value = resources.resolveResValue(value);
          if (value == null) {
              return defaultValue;
          }
          return XmlUtils.convertValueToBoolean(value.getValue(), defaultValue);
      }
  
      /**
       * Looks for a framework attribute in the current theme.
       *
       * @param resources the render resources
       * @param name the name of the attribute
       * @param defaultValue the default value.
       * @return the value of the attribute or the default one if not found.
       */
      public static boolean getBooleanThemeFrameworkAttrValue(@NonNull RenderResources resources,
              @NonNull String name, boolean defaultValue) {
          ResourceReference attrRef = BridgeContext.createFrameworkAttrReference(name);
          return getBooleanThemeValue(resources, attrRef, defaultValue);
      }
  
      // ------- TypedValue stuff
      // This is taken from //device/libs/utils/ResourceTypes.cpp
  
      private static final class UnitEntry {
          String name;
          int type;
          int unit;
          float scale;
  
          UnitEntry(String name, int type, int unit, float scale) {
              this.name = name;
              this.type = type;
              this.unit = unit;
              this.scale = scale;
          }
      }
  
      private static final UnitEntry[] sUnitNames = new UnitEntry[] {
          new UnitEntry("px", TypedValue.TYPE_DIMENSION, TypedValue.COMPLEX_UNIT_PX, 1.0f),
          new UnitEntry("dip", TypedValue.TYPE_DIMENSION, TypedValue.COMPLEX_UNIT_DIP, 1.0f),
          new UnitEntry("dp", TypedValue.TYPE_DIMENSION, TypedValue.COMPLEX_UNIT_DIP, 1.0f),
          new UnitEntry("sp", TypedValue.TYPE_DIMENSION, TypedValue.COMPLEX_UNIT_SP, 1.0f),
          new UnitEntry("pt", TypedValue.TYPE_DIMENSION, TypedValue.COMPLEX_UNIT_PT, 1.0f),
          new UnitEntry("in", TypedValue.TYPE_DIMENSION, TypedValue.COMPLEX_UNIT_IN, 1.0f),
          new UnitEntry("mm", TypedValue.TYPE_DIMENSION, TypedValue.COMPLEX_UNIT_MM, 1.0f),
          new UnitEntry("%", TypedValue.TYPE_FRACTION, TypedValue.COMPLEX_UNIT_FRACTION, 1.0f/100),
          new UnitEntry("%p", TypedValue.TYPE_FRACTION, TypedValue.COMPLEX_UNIT_FRACTION_PARENT, 1.0f/100),
      };
  
      /**
       * Returns the raw value from the given attribute float-type value string.
       * This object is only valid until the next call on to {@link ResourceHelper}.
       */
      public static TypedValue getValue(String attribute, String value, boolean requireUnit) {
          if (parseFloatAttribute(attribute, value, mValue, requireUnit)) {
              return mValue;
          }
  
          return null;
      }
  
      /**
       * Parse a float attribute and return the parsed value into a given TypedValue.
       * @param attribute the name of the attribute. Can be null if <var>requireUnit</var> is false.
       * @param value the string value of the attribute
       * @param outValue the TypedValue to receive the parsed value
       * @param requireUnit whether the value is expected to contain a unit.
       * @return true if success.
       */
      public static boolean parseFloatAttribute(String attribute, @NonNull String value,
              TypedValue outValue, boolean requireUnit) {
          assert !requireUnit || attribute != null;
  
          // remove the space before and after
          value = value.trim();
          int len = value.length();
  
          if (len <= 0) {
              return false;
          }
  
          // check that there's no non ascii characters.
          char[] buf = value.toCharArray();
          for (int i = 0 ; i < len ; i++) {
              if (buf[i] > 255) {
                  return false;
              }
          }
  
          // check the first character
          if ((buf[0] < '0' || buf[0] > '9') && buf[0] != '.' && buf[0] != '-' && buf[0] != '+') {
              return false;
          }
  
          // now look for the string that is after the float...
          Matcher m = sFloatPattern.matcher(value);
          if (m.matches()) {
              String f_str = m.group(1);
              String end = m.group(2);
  
              float f;
              try {
                  f = Float.parseFloat(f_str);
              } catch (NumberFormatException e) {
                  // this shouldn't happen with the regexp above.
                  return false;
              }
  
              if (end.length() > 0 && end.charAt(0) != ' ') {
                  // Might be a unit...
                  if (parseUnit(end, outValue, sFloatOut)) {
                      computeTypedValue(outValue, f, sFloatOut[0]);
                      return true;
                  }
                  return false;
              }
  
              // make sure it's only spaces at the end.
              end = end.trim();
  
              if (end.length() == 0) {
                  if (outValue != null) {
                      if (!requireUnit) {
                          outValue.type = TypedValue.TYPE_FLOAT;
                          outValue.data = Float.floatToIntBits(f);
                      } else {
                          // no unit when required? Use dp and out an error.
                          applyUnit(sUnitNames[1], outValue, sFloatOut);
                          computeTypedValue(outValue, f, sFloatOut[0]);
  
                          Bridge.getLog().error(LayoutLog.TAG_RESOURCES_RESOLVE,
                                  String.format(
                                          "Dimension \"%1$s\" in attribute \"%2$s\" is missing unit!",
                                          value, attribute),
                                  null);
                      }
                      return true;
                  }
              }
          }
  
          return false;
      }
  
      private static void computeTypedValue(TypedValue outValue, float value, float scale) {
          value *= scale;
          boolean neg = value < 0;
          if (neg) {
              value = -value;
          }
          long bits = (long)(value*(1<<23)+.5f);
          int radix;
          int shift;
          if ((bits&0x7fffff) == 0) {
              // Always use 23p0 if there is no fraction, just to make
              // things easier to read.
              radix = TypedValue.COMPLEX_RADIX_23p0;
              shift = 23;
          } else if ((bits&0xffffffffff800000L) == 0) {
              // Magnitude is zero -- can fit in 0 bits of precision.
              radix = TypedValue.COMPLEX_RADIX_0p23;
              shift = 0;
          } else if ((bits&0xffffffff80000000L) == 0) {
              // Magnitude can fit in 8 bits of precision.
              radix = TypedValue.COMPLEX_RADIX_8p15;
              shift = 8;
          } else if ((bits&0xffffff8000000000L) == 0) {
              // Magnitude can fit in 16 bits of precision.
              radix = TypedValue.COMPLEX_RADIX_16p7;
              shift = 16;
          } else {
              // Magnitude needs entire range, so no fractional part.
              radix = TypedValue.COMPLEX_RADIX_23p0;
              shift = 23;
          }
          int mantissa = (int)(
              (bits>>shift) & TypedValue.COMPLEX_MANTISSA_MASK);
          if (neg) {
              mantissa = (-mantissa) & TypedValue.COMPLEX_MANTISSA_MASK;
          }
          outValue.data |=
              (radix<<TypedValue.COMPLEX_RADIX_SHIFT)
              | (mantissa<<TypedValue.COMPLEX_MANTISSA_SHIFT);
      }
  
      private static boolean parseUnit(String str, TypedValue outValue, float[] outScale) {
          str = str.trim();
  
          for (UnitEntry unit : sUnitNames) {
              if (unit.name.equals(str)) {
                  applyUnit(unit, outValue, outScale);
                  return true;
              }
          }
  
          return false;
      }
  
      private static void applyUnit(UnitEntry unit, TypedValue outValue, float[] outScale) {
          outValue.type = unit.type;
          // COMPLEX_UNIT_SHIFT is 0 and hence intelliJ complains about it. Suppress the warning.
          //noinspection PointlessBitwiseExpression
          outValue.data = unit.unit << TypedValue.COMPLEX_UNIT_SHIFT;
          outScale[0] = unit.scale;
      }
  }
  
  