1 /* 2 * Copyright (C) 2016 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 17 package android.content.res; 18 19 import com.android.SdkConstants; 20 import com.android.ide.common.rendering.api.ArrayResourceValue; 21 import com.android.ide.common.rendering.api.DensityBasedResourceValue; 22 import com.android.ide.common.rendering.api.LayoutLog; 23 import com.android.ide.common.rendering.api.LayoutlibCallback; 24 import com.android.ide.common.rendering.api.PluralsResourceValue; 25 import com.android.ide.common.rendering.api.RenderResources; 26 import com.android.ide.common.rendering.api.ResourceValue; 27 import com.android.layoutlib.bridge.Bridge; 28 import com.android.layoutlib.bridge.BridgeConstants; 29 import com.android.layoutlib.bridge.android.BridgeContext; 30 import com.android.layoutlib.bridge.android.BridgeXmlBlockParser; 31 import com.android.layoutlib.bridge.impl.ParserFactory; 32 import com.android.layoutlib.bridge.impl.ResourceHelper; 33 import com.android.layoutlib.bridge.util.NinePatchInputStream; 34 import com.android.ninepatch.NinePatch; 35 import com.android.resources.ResourceType; 36 import com.android.tools.layoutlib.annotations.LayoutlibDelegate; 37 import com.android.util.Pair; 38 39 import org.xmlpull.v1.XmlPullParser; 40 import org.xmlpull.v1.XmlPullParserException; 41 42 import android.annotation.NonNull; 43 import android.annotation.Nullable; 44 import android.content.res.Resources.NotFoundException; 45 import android.content.res.Resources.Theme; 46 import android.graphics.Typeface; 47 import android.graphics.drawable.Drawable; 48 import android.icu.text.PluralRules; 49 import android.util.AttributeSet; 50 import android.util.DisplayMetrics; 51 import android.util.LruCache; 52 import android.util.TypedValue; 53 import android.view.DisplayAdjustments; 54 import android.view.ViewGroup.LayoutParams; 55 56 import java.io.File; 57 import java.io.FileInputStream; 58 import java.io.FileNotFoundException; 59 import java.io.InputStream; 60 import java.util.Iterator; 61 62 @SuppressWarnings("deprecation") 63 public class Resources_Delegate { 64 65 private static boolean[] mPlatformResourceFlag = new boolean[1]; 66 // TODO: This cache is cleared every time a render session is disposed. Look into making this 67 // more long lived. 68 private static LruCache<String, Drawable.ConstantState> sDrawableCache = new LruCache<>(50); 69 initSystem(BridgeContext context, AssetManager assets, DisplayMetrics metrics, Configuration config, LayoutlibCallback layoutlibCallback)70 public static Resources initSystem(BridgeContext context, 71 AssetManager assets, 72 DisplayMetrics metrics, 73 Configuration config, 74 LayoutlibCallback layoutlibCallback) { 75 Resources resources = new Resources(Resources_Delegate.class.getClassLoader()); 76 resources.setImpl(new ResourcesImpl(assets, metrics, config, new DisplayAdjustments())); 77 resources.mContext = context; 78 resources.mLayoutlibCallback = layoutlibCallback; 79 return Resources.mSystem = resources; 80 } 81 82 /** 83 * Disposes the static {@link Resources#mSystem} to make sure we don't leave objects around that 84 * would prevent us from unloading the library. 85 */ disposeSystem()86 public static void disposeSystem() { 87 sDrawableCache.evictAll(); 88 Resources.mSystem.mContext = null; 89 Resources.mSystem.mLayoutlibCallback = null; 90 Resources.mSystem = null; 91 } 92 newTypeArray(Resources resources, int numEntries, boolean platformFile)93 public static BridgeTypedArray newTypeArray(Resources resources, int numEntries, 94 boolean platformFile) { 95 return new BridgeTypedArray(resources, resources.mContext, numEntries, platformFile); 96 } 97 getResourceInfo(Resources resources, int id, boolean[] platformResFlag_out)98 private static Pair<ResourceType, String> getResourceInfo(Resources resources, int id, 99 boolean[] platformResFlag_out) { 100 // first get the String related to this id in the framework 101 Pair<ResourceType, String> resourceInfo = Bridge.resolveResourceId(id); 102 103 // Set the layoutlib callback and context for resources 104 if (resources != Resources.mSystem && resources.mLayoutlibCallback == null) { 105 resources.mLayoutlibCallback = Resources.mSystem.mLayoutlibCallback; 106 resources.mContext = Resources.mSystem.mContext; 107 } 108 109 if (resourceInfo != null) { 110 platformResFlag_out[0] = true; 111 return resourceInfo; 112 } 113 114 // didn't find a match in the framework? look in the project. 115 if (resources.mLayoutlibCallback != null) { 116 resourceInfo = resources.mLayoutlibCallback.resolveResourceId(id); 117 118 if (resourceInfo != null) { 119 platformResFlag_out[0] = false; 120 return resourceInfo; 121 } 122 } 123 return null; 124 } 125 getResourceValue(Resources resources, int id, boolean[] platformResFlag_out)126 private static Pair<String, ResourceValue> getResourceValue(Resources resources, int id, 127 boolean[] platformResFlag_out) { 128 Pair<ResourceType, String> resourceInfo = 129 getResourceInfo(resources, id, platformResFlag_out); 130 131 if (resourceInfo != null) { 132 String attributeName = resourceInfo.getSecond(); 133 RenderResources renderResources = resources.mContext.getRenderResources(); 134 ResourceValue value = platformResFlag_out[0] ? 135 renderResources.getFrameworkResource(resourceInfo.getFirst(), attributeName) : 136 renderResources.getProjectResource(resourceInfo.getFirst(), attributeName); 137 138 if (value == null) { 139 // Unable to resolve the attribute, just leave the unresolved value 140 value = new ResourceValue(resourceInfo.getFirst(), attributeName, attributeName, 141 platformResFlag_out[0]); 142 } 143 return Pair.of(attributeName, value); 144 } 145 146 return null; 147 } 148 149 @LayoutlibDelegate getDrawable(Resources resources, int id)150 static Drawable getDrawable(Resources resources, int id) { 151 return getDrawable(resources, id, null); 152 } 153 154 @LayoutlibDelegate getDrawable(Resources resources, int id, Theme theme)155 static Drawable getDrawable(Resources resources, int id, Theme theme) { 156 Pair<String, ResourceValue> value = getResourceValue(resources, id, mPlatformResourceFlag); 157 if (value != null) { 158 String key = value.getSecond().getValue(); 159 160 Drawable.ConstantState constantState = key != null ? sDrawableCache.get(key) : null; 161 Drawable drawable; 162 if (constantState != null) { 163 drawable = constantState.newDrawable(resources, theme); 164 } else { 165 drawable = 166 ResourceHelper.getDrawable(value.getSecond(), resources.mContext, theme); 167 168 if (key != null) { 169 sDrawableCache.put(key, drawable.getConstantState()); 170 } 171 } 172 173 return drawable; 174 } 175 176 // id was not found or not resolved. Throw a NotFoundException. 177 throwException(resources, id); 178 179 // this is not used since the method above always throws 180 return null; 181 } 182 183 @LayoutlibDelegate getColor(Resources resources, int id)184 static int getColor(Resources resources, int id) { 185 return getColor(resources, id, null); 186 } 187 188 @LayoutlibDelegate getColor(Resources resources, int id, Theme theme)189 static int getColor(Resources resources, int id, Theme theme) throws NotFoundException { 190 Pair<String, ResourceValue> value = getResourceValue(resources, id, mPlatformResourceFlag); 191 192 if (value != null) { 193 ResourceValue resourceValue = value.getSecond(); 194 try { 195 return ResourceHelper.getColor(resourceValue.getValue()); 196 } catch (NumberFormatException e) { 197 // Check if the value passed is a file. If it is, mostly likely, user is referencing 198 // a color state list from a place where they should reference only a pure color. 199 String message; 200 if (new File(resourceValue.getValue()).isFile()) { 201 String resource = (resourceValue.isFramework() ? "@android:" : "@") + "color/" 202 + resourceValue.getName(); 203 message = "Hexadecimal color expected, found Color State List for " + resource; 204 } else { 205 message = e.getMessage(); 206 } 207 Bridge.getLog().error(LayoutLog.TAG_RESOURCES_FORMAT, message, e, null); 208 return 0; 209 } 210 } 211 212 // Suppress possible NPE. getColorStateList will never return null, it will instead 213 // throw an exception, but intelliJ can't figure that out 214 //noinspection ConstantConditions 215 return getColorStateList(resources, id, theme).getDefaultColor(); 216 } 217 218 @LayoutlibDelegate getColorStateList(Resources resources, int id)219 static ColorStateList getColorStateList(Resources resources, int id) throws NotFoundException { 220 return getColorStateList(resources, id, null); 221 } 222 223 @LayoutlibDelegate getColorStateList(Resources resources, int id, Theme theme)224 static ColorStateList getColorStateList(Resources resources, int id, Theme theme) 225 throws NotFoundException { 226 Pair<String, ResourceValue> resValue = 227 getResourceValue(resources, id, mPlatformResourceFlag); 228 229 if (resValue != null) { 230 ColorStateList stateList = ResourceHelper.getColorStateList(resValue.getSecond(), 231 resources.mContext); 232 if (stateList != null) { 233 return stateList.obtainForTheme(theme); 234 } 235 } 236 237 // id was not found or not resolved. Throw a NotFoundException. 238 throwException(resources, id); 239 240 // this is not used since the method above always throws 241 return null; 242 } 243 244 @LayoutlibDelegate getText(Resources resources, int id, CharSequence def)245 static CharSequence getText(Resources resources, int id, CharSequence def) { 246 Pair<String, ResourceValue> value = getResourceValue(resources, id, mPlatformResourceFlag); 247 248 if (value != null) { 249 ResourceValue resValue = value.getSecond(); 250 251 assert resValue != null; 252 if (resValue != null) { 253 String v = resValue.getValue(); 254 if (v != null) { 255 return v; 256 } 257 } 258 } 259 260 return def; 261 } 262 263 @LayoutlibDelegate getText(Resources resources, int id)264 static CharSequence getText(Resources resources, int id) throws NotFoundException { 265 Pair<String, ResourceValue> value = getResourceValue(resources, id, mPlatformResourceFlag); 266 267 if (value != null) { 268 ResourceValue resValue = value.getSecond(); 269 270 assert resValue != null; 271 if (resValue != null) { 272 String v = resValue.getValue(); 273 if (v != null) { 274 return v; 275 } 276 } 277 } 278 279 // id was not found or not resolved. Throw a NotFoundException. 280 throwException(resources, id); 281 282 // this is not used since the method above always throws 283 return null; 284 } 285 286 @LayoutlibDelegate getTextArray(Resources resources, int id)287 static CharSequence[] getTextArray(Resources resources, int id) throws NotFoundException { 288 ResourceValue resValue = getArrayResourceValue(resources, id); 289 if (resValue == null) { 290 // Error already logged by getArrayResourceValue. 291 return new CharSequence[0]; 292 } else if (!(resValue instanceof ArrayResourceValue)) { 293 return new CharSequence[]{ 294 resolveReference(resources, resValue.getValue(), resValue.isFramework())}; 295 } 296 ArrayResourceValue arv = ((ArrayResourceValue) resValue); 297 return fillValues(resources, arv, new CharSequence[arv.getElementCount()]); 298 } 299 300 @LayoutlibDelegate getStringArray(Resources resources, int id)301 static String[] getStringArray(Resources resources, int id) throws NotFoundException { 302 ResourceValue resValue = getArrayResourceValue(resources, id); 303 if (resValue == null) { 304 // Error already logged by getArrayResourceValue. 305 return new String[0]; 306 } else if (!(resValue instanceof ArrayResourceValue)) { 307 return new String[]{ 308 resolveReference(resources, resValue.getValue(), resValue.isFramework())}; 309 } 310 ArrayResourceValue arv = ((ArrayResourceValue) resValue); 311 return fillValues(resources, arv, new String[arv.getElementCount()]); 312 } 313 314 /** 315 * Resolve each element in resValue and copy them to {@code values}. The values copied are 316 * always Strings. The ideal signature for the method should be <T super String>, but java 317 * generics don't support it. 318 */ fillValues(Resources resources, ArrayResourceValue resValue, T[] values)319 static <T extends CharSequence> T[] fillValues(Resources resources, ArrayResourceValue resValue, 320 T[] values) { 321 int i = 0; 322 for (Iterator<String> iterator = resValue.iterator(); iterator.hasNext(); i++) { 323 @SuppressWarnings("unchecked") 324 T s = (T) resolveReference(resources, iterator.next(), resValue.isFramework()); 325 values[i] = s; 326 } 327 return values; 328 } 329 330 @LayoutlibDelegate getIntArray(Resources resources, int id)331 static int[] getIntArray(Resources resources, int id) throws NotFoundException { 332 ResourceValue rv = getArrayResourceValue(resources, id); 333 if (rv == null) { 334 // Error already logged by getArrayResourceValue. 335 return new int[0]; 336 } else if (!(rv instanceof ArrayResourceValue)) { 337 // This is an older IDE that can only give us the first element of the array. 338 String firstValue = resolveReference(resources, rv.getValue(), rv.isFramework()); 339 try { 340 return new int[]{getInt(firstValue)}; 341 } catch (NumberFormatException e) { 342 Bridge.getLog().error(LayoutLog.TAG_RESOURCES_FORMAT, 343 "Integer resource array contains non-integer value: " + 344 firstValue, null); 345 return new int[1]; 346 } 347 } 348 ArrayResourceValue resValue = ((ArrayResourceValue) rv); 349 int[] values = new int[resValue.getElementCount()]; 350 int i = 0; 351 for (Iterator<String> iterator = resValue.iterator(); iterator.hasNext(); i++) { 352 String element = resolveReference(resources, iterator.next(), resValue.isFramework()); 353 try { 354 values[i] = getInt(element); 355 } catch (NumberFormatException e) { 356 Bridge.getLog().error(LayoutLog.TAG_RESOURCES_FORMAT, 357 "Integer resource array contains non-integer value: " + element, null); 358 } 359 } 360 return values; 361 } 362 363 /** 364 * Try to find the ArrayResourceValue for the given id. 365 * <p/> 366 * If the ResourceValue found is not of type {@link ResourceType#ARRAY}, the method logs an 367 * error and return null. However, if the ResourceValue found has type {@code 368 * ResourceType.ARRAY}, but the value is not an instance of {@link ArrayResourceValue}, the 369 * method returns the ResourceValue. This happens on older versions of the IDE, which did not 370 * parse the array resources properly. 371 * <p/> 372 * 373 * @throws NotFoundException if no resource if found 374 */ 375 @Nullable getArrayResourceValue(Resources resources, int id)376 private static ResourceValue getArrayResourceValue(Resources resources, int id) 377 throws NotFoundException { 378 Pair<String, ResourceValue> v = getResourceValue(resources, id, mPlatformResourceFlag); 379 380 if (v != null) { 381 ResourceValue resValue = v.getSecond(); 382 383 assert resValue != null; 384 if (resValue != null) { 385 final ResourceType type = resValue.getResourceType(); 386 if (type != ResourceType.ARRAY) { 387 Bridge.getLog().error(LayoutLog.TAG_RESOURCES_RESOLVE, 388 String.format( 389 "Resource with id 0x%1$X is not an array resource, but %2$s", 390 id, type == null ? "null" : type.getDisplayName()), 391 null); 392 return null; 393 } 394 if (!(resValue instanceof ArrayResourceValue)) { 395 Bridge.getLog().warning(LayoutLog.TAG_UNSUPPORTED, 396 "Obtaining resource arrays via getTextArray, getStringArray or getIntArray is not fully supported in this version of the IDE.", 397 null); 398 } 399 return resValue; 400 } 401 } 402 403 // id was not found or not resolved. Throw a NotFoundException. 404 throwException(resources, id); 405 406 // this is not used since the method above always throws 407 return null; 408 } 409 410 @NonNull resolveReference(Resources resources, @NonNull String ref, boolean forceFrameworkOnly)411 private static String resolveReference(Resources resources, @NonNull String ref, 412 boolean forceFrameworkOnly) { 413 if (ref.startsWith(SdkConstants.PREFIX_RESOURCE_REF) || ref.startsWith 414 (SdkConstants.PREFIX_THEME_REF)) { 415 ResourceValue rv = 416 resources.mContext.getRenderResources().findResValue(ref, forceFrameworkOnly); 417 rv = resources.mContext.getRenderResources().resolveResValue(rv); 418 if (rv != null) { 419 return rv.getValue(); 420 } 421 } 422 // Not a reference. 423 return ref; 424 } 425 426 @LayoutlibDelegate getLayout(Resources resources, int id)427 static XmlResourceParser getLayout(Resources resources, int id) throws NotFoundException { 428 Pair<String, ResourceValue> v = getResourceValue(resources, id, mPlatformResourceFlag); 429 430 if (v != null) { 431 ResourceValue value = v.getSecond(); 432 XmlPullParser parser = null; 433 434 try { 435 // check if the current parser can provide us with a custom parser. 436 if (!mPlatformResourceFlag[0]) { 437 parser = resources.mLayoutlibCallback.getParser(value); 438 } 439 440 // create a new one manually if needed. 441 if (parser == null) { 442 File xml = new File(value.getValue()); 443 if (xml.isFile()) { 444 // we need to create a pull parser around the layout XML file, and then 445 // give that to our XmlBlockParser 446 parser = ParserFactory.create(xml, true); 447 } 448 } 449 450 if (parser != null) { 451 return new BridgeXmlBlockParser(parser, resources.mContext, 452 mPlatformResourceFlag[0]); 453 } 454 } catch (XmlPullParserException e) { 455 Bridge.getLog().error(LayoutLog.TAG_BROKEN, 456 "Failed to configure parser for " + value.getValue(), e, null /*data*/); 457 // we'll return null below. 458 } catch (FileNotFoundException e) { 459 // this shouldn't happen since we check above. 460 } 461 462 } 463 464 // id was not found or not resolved. Throw a NotFoundException. 465 throwException(resources, id); 466 467 // this is not used since the method above always throws 468 return null; 469 } 470 471 @LayoutlibDelegate getAnimation(Resources resources, int id)472 static XmlResourceParser getAnimation(Resources resources, int id) throws NotFoundException { 473 Pair<String, ResourceValue> v = getResourceValue(resources, id, mPlatformResourceFlag); 474 475 if (v != null) { 476 ResourceValue value = v.getSecond(); 477 XmlPullParser parser; 478 479 try { 480 File xml = new File(value.getValue()); 481 if (xml.isFile()) { 482 // we need to create a pull parser around the layout XML file, and then 483 // give that to our XmlBlockParser 484 parser = ParserFactory.create(xml); 485 486 return new BridgeXmlBlockParser(parser, resources.mContext, 487 mPlatformResourceFlag[0]); 488 } 489 } catch (XmlPullParserException e) { 490 Bridge.getLog().error(LayoutLog.TAG_BROKEN, 491 "Failed to configure parser for " + value.getValue(), e, null /*data*/); 492 // we'll return null below. 493 } catch (FileNotFoundException e) { 494 // this shouldn't happen since we check above. 495 } 496 497 } 498 499 // id was not found or not resolved. Throw a NotFoundException. 500 throwException(resources, id); 501 502 // this is not used since the method above always throws 503 return null; 504 } 505 506 @LayoutlibDelegate obtainAttributes(Resources resources, AttributeSet set, int[] attrs)507 static TypedArray obtainAttributes(Resources resources, AttributeSet set, int[] attrs) { 508 return resources.mContext.obtainStyledAttributes(set, attrs); 509 } 510 511 @LayoutlibDelegate obtainAttributes(Resources resources, Resources.Theme theme, AttributeSet set, int[] attrs)512 static TypedArray obtainAttributes(Resources resources, Resources.Theme theme, AttributeSet 513 set, int[] attrs) { 514 return Resources.obtainAttributes_Original(resources, theme, set, attrs); 515 } 516 517 @LayoutlibDelegate obtainTypedArray(Resources resources, int id)518 static TypedArray obtainTypedArray(Resources resources, int id) throws NotFoundException { 519 throw new UnsupportedOperationException(); 520 } 521 522 @LayoutlibDelegate getDimension(Resources resources, int id)523 static float getDimension(Resources resources, int id) throws NotFoundException { 524 Pair<String, ResourceValue> value = getResourceValue(resources, id, mPlatformResourceFlag); 525 526 if (value != null) { 527 ResourceValue resValue = value.getSecond(); 528 529 assert resValue != null; 530 if (resValue != null) { 531 String v = resValue.getValue(); 532 if (v != null) { 533 if (v.equals(BridgeConstants.MATCH_PARENT) || 534 v.equals(BridgeConstants.FILL_PARENT)) { 535 return LayoutParams.MATCH_PARENT; 536 } else if (v.equals(BridgeConstants.WRAP_CONTENT)) { 537 return LayoutParams.WRAP_CONTENT; 538 } 539 TypedValue tmpValue = new TypedValue(); 540 if (ResourceHelper.parseFloatAttribute( 541 value.getFirst(), v, tmpValue, true /*requireUnit*/) && 542 tmpValue.type == TypedValue.TYPE_DIMENSION) { 543 return tmpValue.getDimension(resources.getDisplayMetrics()); 544 } 545 } 546 } 547 } 548 549 // id was not found or not resolved. Throw a NotFoundException. 550 throwException(resources, id); 551 552 // this is not used since the method above always throws 553 return 0; 554 } 555 556 @LayoutlibDelegate getDimensionPixelOffset(Resources resources, int id)557 static int getDimensionPixelOffset(Resources resources, int id) throws NotFoundException { 558 Pair<String, ResourceValue> value = getResourceValue(resources, id, mPlatformResourceFlag); 559 560 if (value != null) { 561 ResourceValue resValue = value.getSecond(); 562 563 assert resValue != null; 564 if (resValue != null) { 565 String v = resValue.getValue(); 566 if (v != null) { 567 TypedValue tmpValue = new TypedValue(); 568 if (ResourceHelper.parseFloatAttribute( 569 value.getFirst(), v, tmpValue, true /*requireUnit*/) && 570 tmpValue.type == TypedValue.TYPE_DIMENSION) { 571 return TypedValue.complexToDimensionPixelOffset(tmpValue.data, 572 resources.getDisplayMetrics()); 573 } 574 } 575 } 576 } 577 578 // id was not found or not resolved. Throw a NotFoundException. 579 throwException(resources, id); 580 581 // this is not used since the method above always throws 582 return 0; 583 } 584 585 @LayoutlibDelegate getDimensionPixelSize(Resources resources, int id)586 static int getDimensionPixelSize(Resources resources, int id) throws NotFoundException { 587 Pair<String, ResourceValue> value = getResourceValue(resources, id, mPlatformResourceFlag); 588 589 if (value != null) { 590 ResourceValue resValue = value.getSecond(); 591 592 assert resValue != null; 593 if (resValue != null) { 594 String v = resValue.getValue(); 595 if (v != null) { 596 TypedValue tmpValue = new TypedValue(); 597 if (ResourceHelper.parseFloatAttribute( 598 value.getFirst(), v, tmpValue, true /*requireUnit*/) && 599 tmpValue.type == TypedValue.TYPE_DIMENSION) { 600 return TypedValue.complexToDimensionPixelSize(tmpValue.data, 601 resources.getDisplayMetrics()); 602 } 603 } 604 } 605 } 606 607 // id was not found or not resolved. Throw a NotFoundException. 608 throwException(resources, id); 609 610 // this is not used since the method above always throws 611 return 0; 612 } 613 614 @LayoutlibDelegate getInteger(Resources resources, int id)615 static int getInteger(Resources resources, int id) throws NotFoundException { 616 Pair<String, ResourceValue> value = getResourceValue(resources, id, mPlatformResourceFlag); 617 618 if (value != null) { 619 ResourceValue resValue = value.getSecond(); 620 621 assert resValue != null; 622 if (resValue != null) { 623 String v = resValue.getValue(); 624 if (v != null) { 625 try { 626 return getInt(v); 627 } catch (NumberFormatException e) { 628 // return exception below 629 } 630 } 631 } 632 } 633 634 // id was not found or not resolved. Throw a NotFoundException. 635 throwException(resources, id); 636 637 // this is not used since the method above always throws 638 return 0; 639 } 640 641 @LayoutlibDelegate getBoolean(Resources resources, int id)642 static boolean getBoolean(Resources resources, int id) throws NotFoundException { 643 Pair<String, ResourceValue> value = getResourceValue(resources, id, mPlatformResourceFlag); 644 645 if (value != null) { 646 ResourceValue resValue = value.getSecond(); 647 648 if (resValue != null) { 649 String v = resValue.getValue(); 650 if (v != null) { 651 return Boolean.parseBoolean(v); 652 } 653 } 654 } 655 656 // id was not found or not resolved. Throw a NotFoundException. 657 throwException(resources, id); 658 659 // this is not used since the method above always throws 660 return false; 661 } 662 663 @LayoutlibDelegate getResourceEntryName(Resources resources, int resid)664 static String getResourceEntryName(Resources resources, int resid) throws NotFoundException { 665 Pair<ResourceType, String> resourceInfo = getResourceInfo(resources, resid, new boolean[1]); 666 if (resourceInfo != null) { 667 return resourceInfo.getSecond(); 668 } 669 throwException(resid, null); 670 return null; 671 672 } 673 674 @LayoutlibDelegate getResourceName(Resources resources, int resid)675 static String getResourceName(Resources resources, int resid) throws NotFoundException { 676 boolean[] platformOut = new boolean[1]; 677 Pair<ResourceType, String> resourceInfo = getResourceInfo(resources, resid, platformOut); 678 String packageName; 679 if (resourceInfo != null) { 680 if (platformOut[0]) { 681 packageName = SdkConstants.ANDROID_NS_NAME; 682 } else { 683 packageName = resources.mContext.getPackageName(); 684 packageName = packageName == null ? SdkConstants.APP_PREFIX : packageName; 685 } 686 return packageName + ':' + resourceInfo.getFirst().getName() + '/' + 687 resourceInfo.getSecond(); 688 } 689 throwException(resid, null); 690 return null; 691 } 692 693 @LayoutlibDelegate getResourcePackageName(Resources resources, int resid)694 static String getResourcePackageName(Resources resources, int resid) throws NotFoundException { 695 boolean[] platformOut = new boolean[1]; 696 Pair<ResourceType, String> resourceInfo = getResourceInfo(resources, resid, platformOut); 697 if (resourceInfo != null) { 698 if (platformOut[0]) { 699 return SdkConstants.ANDROID_NS_NAME; 700 } 701 String packageName = resources.mContext.getPackageName(); 702 return packageName == null ? SdkConstants.APP_PREFIX : packageName; 703 } 704 throwException(resid, null); 705 return null; 706 } 707 708 @LayoutlibDelegate getResourceTypeName(Resources resources, int resid)709 static String getResourceTypeName(Resources resources, int resid) throws NotFoundException { 710 Pair<ResourceType, String> resourceInfo = getResourceInfo(resources, resid, new boolean[1]); 711 if (resourceInfo != null) { 712 return resourceInfo.getFirst().getName(); 713 } 714 throwException(resid, null); 715 return null; 716 } 717 718 @LayoutlibDelegate getString(Resources resources, int id, Object... formatArgs)719 static String getString(Resources resources, int id, Object... formatArgs) 720 throws NotFoundException { 721 String s = getString(resources, id); 722 if (s != null) { 723 return String.format(s, formatArgs); 724 725 } 726 727 // id was not found or not resolved. Throw a NotFoundException. 728 throwException(resources, id); 729 730 // this is not used since the method above always throws 731 return null; 732 } 733 734 @LayoutlibDelegate getString(Resources resources, int id)735 static String getString(Resources resources, int id) throws NotFoundException { 736 Pair<String, ResourceValue> value = getResourceValue(resources, id, mPlatformResourceFlag); 737 738 if (value != null && value.getSecond().getValue() != null) { 739 return value.getSecond().getValue(); 740 } 741 742 // id was not found or not resolved. Throw a NotFoundException. 743 throwException(resources, id); 744 745 // this is not used since the method above always throws 746 return null; 747 } 748 749 @LayoutlibDelegate getQuantityString(Resources resources, int id, int quantity)750 static String getQuantityString(Resources resources, int id, int quantity) throws 751 NotFoundException { 752 Pair<String, ResourceValue> value = getResourceValue(resources, id, mPlatformResourceFlag); 753 754 if (value != null) { 755 if (value.getSecond() instanceof PluralsResourceValue) { 756 PluralsResourceValue pluralsResourceValue = (PluralsResourceValue) value.getSecond(); 757 PluralRules pluralRules = PluralRules.forLocale(resources.getConfiguration().getLocales() 758 .get(0)); 759 String strValue = pluralsResourceValue.getValue(pluralRules.select(quantity)); 760 if (strValue == null) { 761 strValue = pluralsResourceValue.getValue(PluralRules.KEYWORD_OTHER); 762 } 763 764 return strValue; 765 } 766 else { 767 return value.getSecond().getValue(); 768 } 769 } 770 771 // id was not found or not resolved. Throw a NotFoundException. 772 throwException(resources, id); 773 774 // this is not used since the method above always throws 775 return null; 776 } 777 778 @LayoutlibDelegate getQuantityString(Resources resources, int id, int quantity, Object... formatArgs)779 static String getQuantityString(Resources resources, int id, int quantity, Object... formatArgs) 780 throws NotFoundException { 781 String raw = getQuantityString(resources, id, quantity); 782 return String.format(resources.getConfiguration().getLocales().get(0), raw, formatArgs); 783 } 784 785 @LayoutlibDelegate getQuantityText(Resources resources, int id, int quantity)786 static CharSequence getQuantityText(Resources resources, int id, int quantity) throws 787 NotFoundException { 788 return getQuantityString(resources, id, quantity); 789 } 790 791 @LayoutlibDelegate getFont(Resources resources, int id)792 static Typeface getFont(Resources resources, int id) throws 793 NotFoundException { 794 Pair<String, ResourceValue> value = getResourceValue(resources, id, mPlatformResourceFlag); 795 if (value != null) { 796 return ResourceHelper.getFont(value.getSecond(), resources.mContext, null); 797 } 798 799 throwException(resources, id); 800 801 // this is not used since the method above always throws 802 return null; 803 } 804 805 @LayoutlibDelegate getFont(Resources resources, TypedValue outValue, int id)806 static Typeface getFont(Resources resources, TypedValue outValue, int id) throws 807 NotFoundException { 808 Resources_Delegate.getValue(resources, id, outValue, true); 809 if (outValue.string != null) { 810 return ResourceHelper.getFont(outValue.string.toString(), resources.mContext, null, 811 mPlatformResourceFlag[0]); 812 } 813 814 throwException(resources, id); 815 816 // this is not used since the method above always throws 817 return null; 818 } 819 820 @LayoutlibDelegate getValue(Resources resources, int id, TypedValue outValue, boolean resolveRefs)821 static void getValue(Resources resources, int id, TypedValue outValue, boolean resolveRefs) 822 throws NotFoundException { 823 Pair<String, ResourceValue> value = getResourceValue(resources, id, mPlatformResourceFlag); 824 825 if (value != null) { 826 ResourceValue resVal = value.getSecond(); 827 String v = resVal.getValue(); 828 829 if (v != null) { 830 if (ResourceHelper.parseFloatAttribute(value.getFirst(), v, outValue, 831 false /*requireUnit*/)) { 832 return; 833 } 834 if (resVal instanceof DensityBasedResourceValue) { 835 outValue.density = 836 ((DensityBasedResourceValue) resVal).getResourceDensity().getDpiValue(); 837 } 838 839 // else it's a string 840 outValue.type = TypedValue.TYPE_STRING; 841 outValue.string = v; 842 return; 843 } 844 } 845 846 // id was not found or not resolved. Throw a NotFoundException. 847 throwException(resources, id); 848 } 849 850 @LayoutlibDelegate getValue(Resources resources, String name, TypedValue outValue, boolean resolveRefs)851 static void getValue(Resources resources, String name, TypedValue outValue, boolean resolveRefs) 852 throws NotFoundException { 853 throw new UnsupportedOperationException(); 854 } 855 856 @LayoutlibDelegate getXml(Resources resources, int id)857 static XmlResourceParser getXml(Resources resources, int id) throws NotFoundException { 858 Pair<String, ResourceValue> value = getResourceValue(resources, id, mPlatformResourceFlag); 859 860 if (value != null) { 861 String v = value.getSecond().getValue(); 862 863 if (v != null) { 864 // check this is a file 865 File f = new File(v); 866 if (f.isFile()) { 867 try { 868 XmlPullParser parser = ParserFactory.create(f); 869 870 return new BridgeXmlBlockParser(parser, resources.mContext, 871 mPlatformResourceFlag[0]); 872 } catch (XmlPullParserException e) { 873 NotFoundException newE = new NotFoundException(); 874 newE.initCause(e); 875 throw newE; 876 } catch (FileNotFoundException e) { 877 NotFoundException newE = new NotFoundException(); 878 newE.initCause(e); 879 throw newE; 880 } 881 } 882 } 883 } 884 885 // id was not found or not resolved. Throw a NotFoundException. 886 throwException(resources, id); 887 888 // this is not used since the method above always throws 889 return null; 890 } 891 892 @LayoutlibDelegate loadXmlResourceParser(Resources resources, int id, String type)893 static XmlResourceParser loadXmlResourceParser(Resources resources, int id, 894 String type) throws NotFoundException { 895 return resources.loadXmlResourceParser_Original(id, type); 896 } 897 898 @LayoutlibDelegate loadXmlResourceParser(Resources resources, String file, int id, int assetCookie, String type)899 static XmlResourceParser loadXmlResourceParser(Resources resources, String file, int id, 900 int assetCookie, String type) throws NotFoundException { 901 // even though we know the XML file to load directly, we still need to resolve the 902 // id so that we can know if it's a platform or project resource. 903 // (mPlatformResouceFlag will get the result and will be used later). 904 getResourceValue(resources, id, mPlatformResourceFlag); 905 906 File f = new File(file); 907 try { 908 XmlPullParser parser = ParserFactory.create(f); 909 910 return new BridgeXmlBlockParser(parser, resources.mContext, mPlatformResourceFlag[0]); 911 } catch (XmlPullParserException e) { 912 NotFoundException newE = new NotFoundException(); 913 newE.initCause(e); 914 throw newE; 915 } catch (FileNotFoundException e) { 916 NotFoundException newE = new NotFoundException(); 917 newE.initCause(e); 918 throw newE; 919 } 920 } 921 922 @LayoutlibDelegate openRawResource(Resources resources, int id)923 static InputStream openRawResource(Resources resources, int id) throws NotFoundException { 924 Pair<String, ResourceValue> value = getResourceValue(resources, id, mPlatformResourceFlag); 925 926 if (value != null) { 927 String path = value.getSecond().getValue(); 928 929 if (path != null) { 930 // check this is a file 931 File f = new File(path); 932 if (f.isFile()) { 933 try { 934 // if it's a nine-patch return a custom input stream so that 935 // other methods (mainly bitmap factory) can detect it's a 9-patch 936 // and actually load it as a 9-patch instead of a normal bitmap 937 if (path.toLowerCase().endsWith(NinePatch.EXTENSION_9PATCH)) { 938 return new NinePatchInputStream(f); 939 } 940 return new FileInputStream(f); 941 } catch (FileNotFoundException e) { 942 NotFoundException newE = new NotFoundException(); 943 newE.initCause(e); 944 throw newE; 945 } 946 } 947 } 948 } 949 950 // id was not found or not resolved. Throw a NotFoundException. 951 throwException(resources, id); 952 953 // this is not used since the method above always throws 954 return null; 955 } 956 957 @LayoutlibDelegate openRawResource(Resources resources, int id, TypedValue value)958 static InputStream openRawResource(Resources resources, int id, TypedValue value) throws 959 NotFoundException { 960 getValue(resources, id, value, true); 961 962 String path = value.string.toString(); 963 964 File f = new File(path); 965 if (f.isFile()) { 966 try { 967 // if it's a nine-patch return a custom input stream so that 968 // other methods (mainly bitmap factory) can detect it's a 9-patch 969 // and actually load it as a 9-patch instead of a normal bitmap 970 if (path.toLowerCase().endsWith(NinePatch.EXTENSION_9PATCH)) { 971 return new NinePatchInputStream(f); 972 } 973 return new FileInputStream(f); 974 } catch (FileNotFoundException e) { 975 NotFoundException exception = new NotFoundException(); 976 exception.initCause(e); 977 throw exception; 978 } 979 } 980 981 throw new NotFoundException(); 982 } 983 984 @LayoutlibDelegate openRawResourceFd(Resources resources, int id)985 static AssetFileDescriptor openRawResourceFd(Resources resources, int id) throws 986 NotFoundException { 987 throw new UnsupportedOperationException(); 988 } 989 990 /** 991 * Builds and throws a {@link Resources.NotFoundException} based on a resource id and a resource 992 * type. 993 * 994 * @param id the id of the resource 995 * 996 * @throws NotFoundException 997 */ throwException(Resources resources, int id)998 private static void throwException(Resources resources, int id) throws NotFoundException { 999 throwException(id, getResourceInfo(resources, id, new boolean[1])); 1000 } 1001 throwException(int id, @Nullable Pair<ResourceType, String> resourceInfo)1002 private static void throwException(int id, @Nullable Pair<ResourceType, String> resourceInfo) { 1003 String message; 1004 if (resourceInfo != null) { 1005 message = String.format( 1006 "Could not find %1$s resource matching value 0x%2$X (resolved name: %3$s) in current configuration.", 1007 resourceInfo.getFirst(), id, resourceInfo.getSecond()); 1008 } else { 1009 message = String.format("Could not resolve resource value: 0x%1$X.", id); 1010 } 1011 1012 throw new NotFoundException(message); 1013 } 1014 getInt(String v)1015 private static int getInt(String v) throws NumberFormatException { 1016 int radix = 10; 1017 if (v.startsWith("0x")) { 1018 v = v.substring(2); 1019 radix = 16; 1020 } else if (v.startsWith("0")) { 1021 radix = 8; 1022 } 1023 return Integer.parseInt(v, radix); 1024 } 1025 } 1026