ResourceHelper.java revision 789c4b4b14880621f05e7750f594b24bc93fcff9
1/* 2 * Copyright (C) 2008 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17package com.android.layoutlib.bridge.impl; 18 19import com.android.SdkConstants; 20import com.android.ide.common.rendering.api.DensityBasedResourceValue; 21import com.android.ide.common.rendering.api.LayoutLog; 22import com.android.ide.common.rendering.api.RenderResources; 23import com.android.ide.common.rendering.api.ResourceValue; 24import com.android.internal.util.XmlUtils; 25import com.android.layoutlib.bridge.Bridge; 26import com.android.layoutlib.bridge.android.BridgeContext; 27import com.android.layoutlib.bridge.android.BridgeXmlBlockParser; 28import com.android.layoutlib.bridge.android.RenderParamsFlags; 29import com.android.ninepatch.NinePatch; 30import com.android.ninepatch.NinePatchChunk; 31import com.android.resources.Density; 32 33import org.xmlpull.v1.XmlPullParser; 34import org.xmlpull.v1.XmlPullParserException; 35 36import android.annotation.NonNull; 37import android.annotation.Nullable; 38import android.content.res.ColorStateList; 39import android.content.res.ComplexColor; 40import android.content.res.ComplexColor_Accessor; 41import android.content.res.FontResourcesParser; 42import android.content.res.GradientColor; 43import android.content.res.Resources.Theme; 44import android.graphics.Bitmap; 45import android.graphics.Bitmap_Delegate; 46import android.graphics.NinePatch_Delegate; 47import android.graphics.Rect; 48import android.graphics.Typeface; 49import android.graphics.Typeface_Accessor; 50import android.graphics.drawable.BitmapDrawable; 51import android.graphics.drawable.ColorDrawable; 52import android.graphics.drawable.Drawable; 53import android.graphics.drawable.NinePatchDrawable; 54import android.text.FontConfig; 55import android.util.TypedValue; 56 57import java.io.File; 58import java.io.FileInputStream; 59import java.io.FileNotFoundException; 60import java.io.IOException; 61import java.io.InputStream; 62import java.net.MalformedURLException; 63import java.util.regex.Matcher; 64import java.util.regex.Pattern; 65 66/** 67 * Helper class to provide various conversion method used in handling android resources. 68 */ 69public final class ResourceHelper { 70 71 private final static Pattern sFloatPattern = Pattern.compile("(-?[0-9]+(?:\\.[0-9]+)?)(.*)"); 72 private final static float[] sFloatOut = new float[1]; 73 74 private final static TypedValue mValue = new TypedValue(); 75 76 /** 77 * Returns the color value represented by the given string value 78 * @param value the color value 79 * @return the color as an int 80 * @throws NumberFormatException if the conversion failed. 81 */ 82 public static int getColor(String value) { 83 if (value != null) { 84 value = value.trim(); 85 if (!value.startsWith("#")) { 86 if (value.startsWith(SdkConstants.PREFIX_THEME_REF)) { 87 throw new NumberFormatException(String.format( 88 "Attribute '%s' not found. Are you using the right theme?", value)); 89 } 90 throw new NumberFormatException( 91 String.format("Color value '%s' must start with #", value)); 92 } 93 94 value = value.substring(1); 95 96 // make sure it's not longer than 32bit 97 if (value.length() > 8) { 98 throw new NumberFormatException(String.format( 99 "Color value '%s' is too long. Format is either" + 100 "#AARRGGBB, #RRGGBB, #RGB, or #ARGB", 101 value)); 102 } 103 104 if (value.length() == 3) { // RGB format 105 char[] color = new char[8]; 106 color[0] = color[1] = 'F'; 107 color[2] = color[3] = value.charAt(0); 108 color[4] = color[5] = value.charAt(1); 109 color[6] = color[7] = value.charAt(2); 110 value = new String(color); 111 } else if (value.length() == 4) { // ARGB format 112 char[] color = new char[8]; 113 color[0] = color[1] = value.charAt(0); 114 color[2] = color[3] = value.charAt(1); 115 color[4] = color[5] = value.charAt(2); 116 color[6] = color[7] = value.charAt(3); 117 value = new String(color); 118 } else if (value.length() == 6) { 119 value = "FF" + value; 120 } 121 122 // this is a RRGGBB or AARRGGBB value 123 124 // Integer.parseInt will fail to parse strings like "ff191919", so we use 125 // a Long, but cast the result back into an int, since we know that we're only 126 // dealing with 32 bit values. 127 return (int)Long.parseLong(value, 16); 128 } 129 130 throw new NumberFormatException(); 131 } 132 133 /** 134 * Returns a {@link ComplexColor} from the given {@link ResourceValue} 135 * 136 * @param resValue the value containing a color value or a file path to a complex color 137 * definition 138 * @param context the current context 139 * @param theme the theme to use when resolving the complex color 140 * @param allowGradients when false, only {@link ColorStateList} will be returned. If a {@link 141 * GradientColor} is found, null will be returned. 142 */ 143 @Nullable 144 private static ComplexColor getInternalComplexColor(@NonNull ResourceValue resValue, 145 @NonNull BridgeContext context, @Nullable Theme theme, boolean allowGradients) { 146 String value = resValue.getValue(); 147 if (value == null || RenderResources.REFERENCE_NULL.equals(value)) { 148 return null; 149 } 150 151 XmlPullParser parser = null; 152 // first check if the value is a file (xml most likely) 153 Boolean psiParserSupport = context.getLayoutlibCallback().getFlag( 154 RenderParamsFlags.FLAG_KEY_XML_FILE_PARSER_SUPPORT); 155 if (psiParserSupport != null && psiParserSupport) { 156 parser = context.getLayoutlibCallback().getXmlFileParser(value); 157 } 158 if (parser == null) { 159 File f = new File(value); 160 if (f.isFile()) { 161 // let the framework inflate the color from the XML file, by 162 // providing an XmlPullParser 163 try { 164 parser = ParserFactory.create(f); 165 } catch (XmlPullParserException | FileNotFoundException e) { 166 Bridge.getLog().error(LayoutLog.TAG_RESOURCES_READ, 167 "Failed to parse file " + value, e, null /*data*/); 168 } 169 } 170 } 171 172 if (parser != null) { 173 try { 174 BridgeXmlBlockParser blockParser = new BridgeXmlBlockParser( 175 parser, context, resValue.isFramework()); 176 try { 177 // Advance the parser to the first element so we can detect if it's a 178 // color list or a gradient color 179 int type; 180 //noinspection StatementWithEmptyBody 181 while ((type = blockParser.next()) != XmlPullParser.START_TAG 182 && type != XmlPullParser.END_DOCUMENT) { 183 // Seek parser to start tag. 184 } 185 186 if (type != XmlPullParser.START_TAG) { 187 throw new XmlPullParserException("No start tag found"); 188 } 189 190 final String name = blockParser.getName(); 191 if (allowGradients && "gradient".equals(name)) { 192 return ComplexColor_Accessor.createGradientColorFromXmlInner( 193 context.getResources(), 194 blockParser, blockParser, 195 theme); 196 } else if ("selector".equals(name)) { 197 return ComplexColor_Accessor.createColorStateListFromXmlInner( 198 context.getResources(), 199 blockParser, blockParser, 200 theme); 201 } 202 } finally { 203 blockParser.ensurePopped(); 204 } 205 } catch (XmlPullParserException e) { 206 Bridge.getLog().error(LayoutLog.TAG_BROKEN, 207 "Failed to configure parser for " + value, e, null /*data*/); 208 // we'll return null below. 209 } catch (Exception e) { 210 // this is an error and not warning since the file existence is 211 // checked before attempting to parse it. 212 Bridge.getLog().error(LayoutLog.TAG_RESOURCES_READ, 213 "Failed to parse file " + value, e, null /*data*/); 214 215 return null; 216 } 217 } else { 218 // try to load the color state list from an int 219 try { 220 int color = getColor(value); 221 return ColorStateList.valueOf(color); 222 } catch (NumberFormatException e) { 223 Bridge.getLog().error(LayoutLog.TAG_RESOURCES_FORMAT, 224 "Failed to convert " + value + " into a ColorStateList", e, 225 null /*data*/); 226 } 227 } 228 229 return null; 230 } 231 232 /** 233 * Returns a {@link ColorStateList} from the given {@link ResourceValue} 234 * 235 * @param resValue the value containing a color value or a file path to a complex color 236 * definition 237 * @param context the current context 238 */ 239 @Nullable 240 public static ColorStateList getColorStateList(@NonNull ResourceValue resValue, 241 @NonNull BridgeContext context) { 242 return (ColorStateList) getInternalComplexColor(resValue, context, context.getTheme(), 243 false); 244 } 245 246 /** 247 * Returns a {@link ComplexColor} from the given {@link ResourceValue} 248 * 249 * @param resValue the value containing a color value or a file path to a complex color 250 * definition 251 * @param context the current context 252 */ 253 @Nullable 254 public static ComplexColor getComplexColor(@NonNull ResourceValue resValue, 255 @NonNull BridgeContext context) { 256 return getInternalComplexColor(resValue, context, context.getTheme(), true); 257 } 258 259 /** 260 * Returns a drawable from the given value. 261 * @param value The value that contains a path to a 9 patch, a bitmap or a xml based drawable, 262 * or an hexadecimal color 263 * @param context the current context 264 */ 265 public static Drawable getDrawable(ResourceValue value, BridgeContext context) { 266 return getDrawable(value, context, null); 267 } 268 269 /** 270 * Returns a drawable from the given value. 271 * @param value The value that contains a path to a 9 patch, a bitmap or a xml based drawable, 272 * or an hexadecimal color 273 * @param context the current context 274 * @param theme the theme to be used to inflate the drawable. 275 */ 276 public static Drawable getDrawable(ResourceValue value, BridgeContext context, Theme theme) { 277 if (value == null) { 278 return null; 279 } 280 String stringValue = value.getValue(); 281 if (RenderResources.REFERENCE_NULL.equals(stringValue)) { 282 return null; 283 } 284 285 String lowerCaseValue = stringValue.toLowerCase(); 286 287 Density density = Density.MEDIUM; 288 if (value instanceof DensityBasedResourceValue) { 289 density = 290 ((DensityBasedResourceValue)value).getResourceDensity(); 291 } 292 293 294 if (lowerCaseValue.endsWith(NinePatch.EXTENSION_9PATCH)) { 295 File file = new File(stringValue); 296 if (file.isFile()) { 297 try { 298 return getNinePatchDrawable( 299 new FileInputStream(file), density, value.isFramework(), 300 stringValue, context); 301 } catch (IOException e) { 302 // failed to read the file, we'll return null below. 303 Bridge.getLog().error(LayoutLog.TAG_RESOURCES_READ, 304 "Failed lot load " + file.getAbsolutePath(), e, null /*data*/); 305 } 306 } 307 308 return null; 309 } else if (lowerCaseValue.endsWith(".xml")) { 310 // create a block parser for the file 311 File f = new File(stringValue); 312 if (f.isFile()) { 313 try { 314 // let the framework inflate the Drawable from the XML file. 315 XmlPullParser parser = ParserFactory.create(f); 316 317 BridgeXmlBlockParser blockParser = new BridgeXmlBlockParser( 318 parser, context, value.isFramework()); 319 try { 320 return Drawable.createFromXml(context.getResources(), blockParser, theme); 321 } finally { 322 blockParser.ensurePopped(); 323 } 324 } catch (Exception e) { 325 // this is an error and not warning since the file existence is checked before 326 // attempting to parse it. 327 Bridge.getLog().error(null, "Failed to parse file " + stringValue, 328 e, null /*data*/); 329 } 330 } else { 331 Bridge.getLog().error(LayoutLog.TAG_BROKEN, 332 String.format("File %s does not exist (or is not a file)", stringValue), 333 null /*data*/); 334 } 335 336 return null; 337 } else { 338 File bmpFile = new File(stringValue); 339 if (bmpFile.isFile()) { 340 try { 341 Bitmap bitmap = Bridge.getCachedBitmap(stringValue, 342 value.isFramework() ? null : context.getProjectKey()); 343 344 if (bitmap == null) { 345 bitmap = Bitmap_Delegate.createBitmap(bmpFile, false /*isMutable*/, 346 density); 347 Bridge.setCachedBitmap(stringValue, bitmap, 348 value.isFramework() ? null : context.getProjectKey()); 349 } 350 351 return new BitmapDrawable(context.getResources(), bitmap); 352 } catch (IOException e) { 353 // we'll return null below 354 Bridge.getLog().error(LayoutLog.TAG_RESOURCES_READ, 355 "Failed lot load " + bmpFile.getAbsolutePath(), e, null /*data*/); 356 } 357 } else { 358 // attempt to get a color from the value 359 try { 360 int color = getColor(stringValue); 361 return new ColorDrawable(color); 362 } catch (NumberFormatException e) { 363 // we'll return null below. 364 Bridge.getLog().error(LayoutLog.TAG_RESOURCES_FORMAT, 365 "Failed to convert " + stringValue + " into a drawable", e, 366 null /*data*/); 367 } 368 } 369 } 370 371 return null; 372 } 373 374 /** 375 * Returns a {@link Typeface} given a font name. The font name, can be a system font family 376 * (like sans-serif) or a full path if the font is to be loaded from resources. 377 */ 378 public static Typeface getFont(String fontName, BridgeContext context, Theme theme, boolean 379 isFramework) { 380 if (fontName == null) { 381 return null; 382 } 383 384 if (Typeface_Accessor.isSystemFont(fontName)) { 385 // Shortcut for the case where we are asking for a system font name. Those are not 386 // loaded using external resources. 387 return null; 388 } 389 390 // Check if this is an asset that we've already loaded dynamically 391 Typeface typeface = Typeface.findFromCache(context.getAssets(), fontName); 392 if (typeface != null) { 393 return typeface; 394 } 395 396 String lowerCaseValue = fontName.toLowerCase(); 397 if (lowerCaseValue.endsWith(".xml")) { 398 // create a block parser for the file 399 Boolean psiParserSupport = context.getLayoutlibCallback().getFlag( 400 RenderParamsFlags.FLAG_KEY_XML_FILE_PARSER_SUPPORT); 401 XmlPullParser parser = null; 402 if (psiParserSupport != null && psiParserSupport) { 403 parser = context.getLayoutlibCallback().getXmlFileParser(fontName); 404 } 405 else { 406 File f = new File(fontName); 407 if (f.isFile()) { 408 try { 409 parser = ParserFactory.create(f); 410 } catch (XmlPullParserException | FileNotFoundException e) { 411 // this is an error and not warning since the file existence is checked before 412 // attempting to parse it. 413 Bridge.getLog().error(null, "Failed to parse file " + fontName, 414 e, null /*data*/); 415 } 416 } 417 } 418 419 if (parser != null) { 420 BridgeXmlBlockParser blockParser = new BridgeXmlBlockParser( 421 parser, context, isFramework); 422 try { 423 FontConfig config = FontResourcesParser.parse(blockParser, context 424 .getResources()); 425 typeface = Typeface.createFromResources(config, context.getAssets(), 426 fontName); 427 } catch (XmlPullParserException | IOException e) { 428 Bridge.getLog().error(null, "Failed to parse file " + fontName, 429 e, null /*data*/); 430 } finally { 431 blockParser.ensurePopped(); 432 } 433 } else { 434 Bridge.getLog().error(LayoutLog.TAG_BROKEN, 435 String.format("File %s does not exist (or is not a file)", fontName), 436 null /*data*/); 437 } 438 } else { 439 typeface = Typeface.createFromResources(context.getAssets(), fontName, 0); 440 } 441 442 return typeface; 443 } 444 445 /** 446 * Returns a {@link Typeface} given a font name. The font name, can be a system font family 447 * (like sans-serif) or a full path if the font is to be loaded from resources. 448 */ 449 public static Typeface getFont(ResourceValue value, BridgeContext context, Theme theme) { 450 if (value == null) { 451 return null; 452 } 453 454 return getFont(value.getValue(), context, theme, value.isFramework()); 455 } 456 457 private static Drawable getNinePatchDrawable(InputStream inputStream, Density density, 458 boolean isFramework, String cacheKey, BridgeContext context) throws IOException { 459 // see if we still have both the chunk and the bitmap in the caches 460 NinePatchChunk chunk = Bridge.getCached9Patch(cacheKey, 461 isFramework ? null : context.getProjectKey()); 462 Bitmap bitmap = Bridge.getCachedBitmap(cacheKey, 463 isFramework ? null : context.getProjectKey()); 464 465 // if either chunk or bitmap is null, then we reload the 9-patch file. 466 if (chunk == null || bitmap == null) { 467 try { 468 NinePatch ninePatch = NinePatch.load(inputStream, true /*is9Patch*/, 469 false /* convert */); 470 if (ninePatch != null) { 471 if (chunk == null) { 472 chunk = ninePatch.getChunk(); 473 474 Bridge.setCached9Patch(cacheKey, chunk, 475 isFramework ? null : context.getProjectKey()); 476 } 477 478 if (bitmap == null) { 479 bitmap = Bitmap_Delegate.createBitmap(ninePatch.getImage(), 480 false /*isMutable*/, 481 density); 482 483 Bridge.setCachedBitmap(cacheKey, bitmap, 484 isFramework ? null : context.getProjectKey()); 485 } 486 } 487 } catch (MalformedURLException e) { 488 // URL is wrong, we'll return null below 489 } 490 } 491 492 if (chunk != null && bitmap != null) { 493 int[] padding = chunk.getPadding(); 494 Rect paddingRect = new Rect(padding[0], padding[1], padding[2], padding[3]); 495 496 return new NinePatchDrawable(context.getResources(), bitmap, 497 NinePatch_Delegate.serialize(chunk), 498 paddingRect, null); 499 } 500 501 return null; 502 } 503 504 /** 505 * Looks for an attribute in the current theme. 506 * 507 * @param resources the render resources 508 * @param name the name of the attribute 509 * @param defaultValue the default value. 510 * @param isFrameworkAttr if the attribute is in android namespace 511 * @return the value of the attribute or the default one if not found. 512 */ 513 public static boolean getBooleanThemeValue(@NonNull RenderResources resources, String name, 514 boolean isFrameworkAttr, boolean defaultValue) { 515 ResourceValue value = resources.findItemInTheme(name, isFrameworkAttr); 516 value = resources.resolveResValue(value); 517 if (value == null) { 518 return defaultValue; 519 } 520 return XmlUtils.convertValueToBoolean(value.getValue(), defaultValue); 521 } 522 523 // ------- TypedValue stuff 524 // This is taken from //device/libs/utils/ResourceTypes.cpp 525 526 private static final class UnitEntry { 527 String name; 528 int type; 529 int unit; 530 float scale; 531 532 UnitEntry(String name, int type, int unit, float scale) { 533 this.name = name; 534 this.type = type; 535 this.unit = unit; 536 this.scale = scale; 537 } 538 } 539 540 private final static UnitEntry[] sUnitNames = new UnitEntry[] { 541 new UnitEntry("px", TypedValue.TYPE_DIMENSION, TypedValue.COMPLEX_UNIT_PX, 1.0f), 542 new UnitEntry("dip", TypedValue.TYPE_DIMENSION, TypedValue.COMPLEX_UNIT_DIP, 1.0f), 543 new UnitEntry("dp", TypedValue.TYPE_DIMENSION, TypedValue.COMPLEX_UNIT_DIP, 1.0f), 544 new UnitEntry("sp", TypedValue.TYPE_DIMENSION, TypedValue.COMPLEX_UNIT_SP, 1.0f), 545 new UnitEntry("pt", TypedValue.TYPE_DIMENSION, TypedValue.COMPLEX_UNIT_PT, 1.0f), 546 new UnitEntry("in", TypedValue.TYPE_DIMENSION, TypedValue.COMPLEX_UNIT_IN, 1.0f), 547 new UnitEntry("mm", TypedValue.TYPE_DIMENSION, TypedValue.COMPLEX_UNIT_MM, 1.0f), 548 new UnitEntry("%", TypedValue.TYPE_FRACTION, TypedValue.COMPLEX_UNIT_FRACTION, 1.0f/100), 549 new UnitEntry("%p", TypedValue.TYPE_FRACTION, TypedValue.COMPLEX_UNIT_FRACTION_PARENT, 1.0f/100), 550 }; 551 552 /** 553 * Returns the raw value from the given attribute float-type value string. 554 * This object is only valid until the next call on to {@link ResourceHelper}. 555 */ 556 public static TypedValue getValue(String attribute, String value, boolean requireUnit) { 557 if (parseFloatAttribute(attribute, value, mValue, requireUnit)) { 558 return mValue; 559 } 560 561 return null; 562 } 563 564 /** 565 * Parse a float attribute and return the parsed value into a given TypedValue. 566 * @param attribute the name of the attribute. Can be null if <var>requireUnit</var> is false. 567 * @param value the string value of the attribute 568 * @param outValue the TypedValue to receive the parsed value 569 * @param requireUnit whether the value is expected to contain a unit. 570 * @return true if success. 571 */ 572 public static boolean parseFloatAttribute(String attribute, @NonNull String value, 573 TypedValue outValue, boolean requireUnit) { 574 assert !requireUnit || attribute != null; 575 576 // remove the space before and after 577 value = value.trim(); 578 int len = value.length(); 579 580 if (len <= 0) { 581 return false; 582 } 583 584 // check that there's no non ascii characters. 585 char[] buf = value.toCharArray(); 586 for (int i = 0 ; i < len ; i++) { 587 if (buf[i] > 255) { 588 return false; 589 } 590 } 591 592 // check the first character 593 if ((buf[0] < '0' || buf[0] > '9') && buf[0] != '.' && buf[0] != '-' && buf[0] != '+') { 594 return false; 595 } 596 597 // now look for the string that is after the float... 598 Matcher m = sFloatPattern.matcher(value); 599 if (m.matches()) { 600 String f_str = m.group(1); 601 String end = m.group(2); 602 603 float f; 604 try { 605 f = Float.parseFloat(f_str); 606 } catch (NumberFormatException e) { 607 // this shouldn't happen with the regexp above. 608 return false; 609 } 610 611 if (end.length() > 0 && end.charAt(0) != ' ') { 612 // Might be a unit... 613 if (parseUnit(end, outValue, sFloatOut)) { 614 computeTypedValue(outValue, f, sFloatOut[0]); 615 return true; 616 } 617 return false; 618 } 619 620 // make sure it's only spaces at the end. 621 end = end.trim(); 622 623 if (end.length() == 0) { 624 if (outValue != null) { 625 if (!requireUnit) { 626 outValue.type = TypedValue.TYPE_FLOAT; 627 outValue.data = Float.floatToIntBits(f); 628 } else { 629 // no unit when required? Use dp and out an error. 630 applyUnit(sUnitNames[1], outValue, sFloatOut); 631 computeTypedValue(outValue, f, sFloatOut[0]); 632 633 Bridge.getLog().error(LayoutLog.TAG_RESOURCES_RESOLVE, 634 String.format( 635 "Dimension \"%1$s\" in attribute \"%2$s\" is missing unit!", 636 value, attribute), 637 null); 638 } 639 return true; 640 } 641 } 642 } 643 644 return false; 645 } 646 647 private static void computeTypedValue(TypedValue outValue, float value, float scale) { 648 value *= scale; 649 boolean neg = value < 0; 650 if (neg) { 651 value = -value; 652 } 653 long bits = (long)(value*(1<<23)+.5f); 654 int radix; 655 int shift; 656 if ((bits&0x7fffff) == 0) { 657 // Always use 23p0 if there is no fraction, just to make 658 // things easier to read. 659 radix = TypedValue.COMPLEX_RADIX_23p0; 660 shift = 23; 661 } else if ((bits&0xffffffffff800000L) == 0) { 662 // Magnitude is zero -- can fit in 0 bits of precision. 663 radix = TypedValue.COMPLEX_RADIX_0p23; 664 shift = 0; 665 } else if ((bits&0xffffffff80000000L) == 0) { 666 // Magnitude can fit in 8 bits of precision. 667 radix = TypedValue.COMPLEX_RADIX_8p15; 668 shift = 8; 669 } else if ((bits&0xffffff8000000000L) == 0) { 670 // Magnitude can fit in 16 bits of precision. 671 radix = TypedValue.COMPLEX_RADIX_16p7; 672 shift = 16; 673 } else { 674 // Magnitude needs entire range, so no fractional part. 675 radix = TypedValue.COMPLEX_RADIX_23p0; 676 shift = 23; 677 } 678 int mantissa = (int)( 679 (bits>>shift) & TypedValue.COMPLEX_MANTISSA_MASK); 680 if (neg) { 681 mantissa = (-mantissa) & TypedValue.COMPLEX_MANTISSA_MASK; 682 } 683 outValue.data |= 684 (radix<<TypedValue.COMPLEX_RADIX_SHIFT) 685 | (mantissa<<TypedValue.COMPLEX_MANTISSA_SHIFT); 686 } 687 688 private static boolean parseUnit(String str, TypedValue outValue, float[] outScale) { 689 str = str.trim(); 690 691 for (UnitEntry unit : sUnitNames) { 692 if (unit.name.equals(str)) { 693 applyUnit(unit, outValue, outScale); 694 return true; 695 } 696 } 697 698 return false; 699 } 700 701 private static void applyUnit(UnitEntry unit, TypedValue outValue, float[] outScale) { 702 outValue.type = unit.type; 703 // COMPLEX_UNIT_SHIFT is 0 and hence intelliJ complains about it. Suppress the warning. 704 //noinspection PointlessBitwiseExpression 705 outValue.data = unit.unit << TypedValue.COMPLEX_UNIT_SHIFT; 706 outScale[0] = unit.scale; 707 } 708} 709 710