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 17 package com.android.layoutlib.bridge; 18 19 import com.android.ide.common.rendering.api.Capability; 20 import com.android.ide.common.rendering.api.DrawableParams; 21 import com.android.ide.common.rendering.api.Features; 22 import com.android.ide.common.rendering.api.LayoutLog; 23 import com.android.ide.common.rendering.api.RenderSession; 24 import com.android.ide.common.rendering.api.Result; 25 import com.android.ide.common.rendering.api.Result.Status; 26 import com.android.ide.common.rendering.api.SessionParams; 27 import com.android.layoutlib.bridge.android.RenderParamsFlags; 28 import com.android.layoutlib.bridge.impl.RenderDrawable; 29 import com.android.layoutlib.bridge.impl.RenderSessionImpl; 30 import com.android.layoutlib.bridge.util.DynamicIdMap; 31 import com.android.ninepatch.NinePatchChunk; 32 import com.android.resources.ResourceType; 33 import com.android.tools.layoutlib.create.MethodAdapter; 34 import com.android.tools.layoutlib.create.OverrideMethod; 35 import com.android.util.Pair; 36 37 import android.annotation.NonNull; 38 import android.content.res.BridgeAssetManager; 39 import android.graphics.Bitmap; 40 import android.graphics.FontFamily_Delegate; 41 import android.graphics.Typeface_Delegate; 42 import android.icu.util.ULocale; 43 import android.os.Looper; 44 import android.os.Looper_Accessor; 45 import android.view.View; 46 import android.view.ViewGroup; 47 import android.view.ViewParent; 48 49 import java.io.File; 50 import java.lang.ref.SoftReference; 51 import java.lang.reflect.Field; 52 import java.lang.reflect.Modifier; 53 import java.util.Arrays; 54 import java.util.Comparator; 55 import java.util.EnumMap; 56 import java.util.EnumSet; 57 import java.util.HashMap; 58 import java.util.Map; 59 import java.util.concurrent.locks.ReentrantLock; 60 61 import libcore.io.MemoryMappedFile_Delegate; 62 63 import static com.android.ide.common.rendering.api.Result.Status.ERROR_UNKNOWN; 64 import static com.android.ide.common.rendering.api.Result.Status.SUCCESS; 65 66 /** 67 * Main entry point of the LayoutLib Bridge. 68 * <p/>To use this bridge, simply instantiate an object of type {@link Bridge} and call 69 * {@link #createSession(SessionParams)} 70 */ 71 public final class Bridge extends com.android.ide.common.rendering.api.Bridge { 72 73 private static final String ICU_LOCALE_DIRECTION_RTL = "right-to-left"; 74 75 public static class StaticMethodNotImplementedException extends RuntimeException { 76 private static final long serialVersionUID = 1L; 77 StaticMethodNotImplementedException(String msg)78 public StaticMethodNotImplementedException(String msg) { 79 super(msg); 80 } 81 } 82 83 /** 84 * Lock to ensure only one rendering/inflating happens at a time. 85 * This is due to some singleton in the Android framework. 86 */ 87 private final static ReentrantLock sLock = new ReentrantLock(); 88 89 /** 90 * Maps from id to resource type/name. This is for com.android.internal.R 91 */ 92 private final static Map<Integer, Pair<ResourceType, String>> sRMap = 93 new HashMap<Integer, Pair<ResourceType, String>>(); 94 95 /** 96 * Same as sRMap except for int[] instead of int resources. This is for android.R only. 97 */ 98 private final static Map<IntArray, String> sRArrayMap = new HashMap<IntArray, String>(384); 99 /** 100 * Reverse map compared to sRMap, resource type -> (resource name -> id). 101 * This is for com.android.internal.R. 102 */ 103 private final static Map<ResourceType, Map<String, Integer>> sRevRMap = 104 new EnumMap<ResourceType, Map<String,Integer>>(ResourceType.class); 105 106 // framework resources are defined as 0x01XX#### where XX is the resource type (layout, 107 // drawable, etc...). Using FF as the type allows for 255 resource types before we get a 108 // collision which should be fine. 109 private final static int DYNAMIC_ID_SEED_START = 0x01ff0000; 110 private final static DynamicIdMap sDynamicIds = new DynamicIdMap(DYNAMIC_ID_SEED_START); 111 112 private final static Map<Object, Map<String, SoftReference<Bitmap>>> sProjectBitmapCache = 113 new HashMap<Object, Map<String, SoftReference<Bitmap>>>(); 114 private final static Map<Object, Map<String, SoftReference<NinePatchChunk>>> sProject9PatchCache = 115 new HashMap<Object, Map<String, SoftReference<NinePatchChunk>>>(); 116 117 private final static Map<String, SoftReference<Bitmap>> sFrameworkBitmapCache = 118 new HashMap<String, SoftReference<Bitmap>>(); 119 private final static Map<String, SoftReference<NinePatchChunk>> sFramework9PatchCache = 120 new HashMap<String, SoftReference<NinePatchChunk>>(); 121 122 private static Map<String, Map<String, Integer>> sEnumValueMap; 123 private static Map<String, String> sPlatformProperties; 124 125 /** 126 * int[] wrapper to use as keys in maps. 127 */ 128 private final static class IntArray { 129 private int[] mArray; 130 IntArray()131 private IntArray() { 132 // do nothing 133 } 134 IntArray(int[] a)135 private IntArray(int[] a) { 136 mArray = a; 137 } 138 set(int[] a)139 private void set(int[] a) { 140 mArray = a; 141 } 142 143 @Override hashCode()144 public int hashCode() { 145 return Arrays.hashCode(mArray); 146 } 147 148 @Override equals(Object obj)149 public boolean equals(Object obj) { 150 if (this == obj) return true; 151 if (obj == null) return false; 152 if (getClass() != obj.getClass()) return false; 153 154 IntArray other = (IntArray) obj; 155 return Arrays.equals(mArray, other.mArray); 156 } 157 } 158 159 /** Instance of IntArrayWrapper to be reused in {@link #resolveResourceId(int[])}. */ 160 private final static IntArray sIntArrayWrapper = new IntArray(); 161 162 /** 163 * A default log than prints to stdout/stderr. 164 */ 165 private final static LayoutLog sDefaultLog = new LayoutLog() { 166 @Override 167 public void error(String tag, String message, Object data) { 168 System.err.println(message); 169 } 170 171 @Override 172 public void error(String tag, String message, Throwable throwable, Object data) { 173 System.err.println(message); 174 } 175 176 @Override 177 public void warning(String tag, String message, Object data) { 178 System.out.println(message); 179 } 180 }; 181 182 /** 183 * Current log. 184 */ 185 private static LayoutLog sCurrentLog = sDefaultLog; 186 187 private static final int LAST_SUPPORTED_FEATURE = Features.THEME_PREVIEW_NAVIGATION_BAR; 188 189 @Override getApiLevel()190 public int getApiLevel() { 191 return com.android.ide.common.rendering.api.Bridge.API_CURRENT; 192 } 193 194 @Override 195 @Deprecated getCapabilities()196 public EnumSet<Capability> getCapabilities() { 197 // The Capability class is deprecated and frozen. All Capabilities enumerated there are 198 // supported by this version of LayoutLibrary. So, it's safe to use EnumSet.allOf() 199 return EnumSet.allOf(Capability.class); 200 } 201 202 @Override supports(int feature)203 public boolean supports(int feature) { 204 return feature <= LAST_SUPPORTED_FEATURE; 205 } 206 207 @Override init(Map<String,String> platformProperties, File fontLocation, Map<String, Map<String, Integer>> enumValueMap, LayoutLog log)208 public boolean init(Map<String,String> platformProperties, 209 File fontLocation, 210 Map<String, Map<String, Integer>> enumValueMap, 211 LayoutLog log) { 212 sPlatformProperties = platformProperties; 213 sEnumValueMap = enumValueMap; 214 215 BridgeAssetManager.initSystem(); 216 217 // When DEBUG_LAYOUT is set and is not 0 or false, setup a default listener 218 // on static (native) methods which prints the signature on the console and 219 // throws an exception. 220 // This is useful when testing the rendering in ADT to identify static native 221 // methods that are ignored -- layoutlib_create makes them returns 0/false/null 222 // which is generally OK yet might be a problem, so this is how you'd find out. 223 // 224 // Currently layoutlib_create only overrides static native method. 225 // Static non-natives are not overridden and thus do not get here. 226 final String debug = System.getenv("DEBUG_LAYOUT"); 227 if (debug != null && !debug.equals("0") && !debug.equals("false")) { 228 229 OverrideMethod.setDefaultListener(new MethodAdapter() { 230 @Override 231 public void onInvokeV(String signature, boolean isNative, Object caller) { 232 sDefaultLog.error(null, "Missing Stub: " + signature + 233 (isNative ? " (native)" : ""), null /*data*/); 234 235 if (debug.equalsIgnoreCase("throw")) { 236 // Throwing this exception doesn't seem that useful. It breaks 237 // the layout editor yet doesn't display anything meaningful to the 238 // user. Having the error in the console is just as useful. We'll 239 // throw it only if the environment variable is "throw" or "THROW". 240 throw new StaticMethodNotImplementedException(signature); 241 } 242 } 243 }); 244 } 245 246 // load the fonts. 247 FontFamily_Delegate.setFontLocation(fontLocation.getAbsolutePath()); 248 MemoryMappedFile_Delegate.setDataDir(fontLocation.getAbsoluteFile().getParentFile()); 249 250 // now parse com.android.internal.R (and only this one as android.R is a subset of 251 // the internal version), and put the content in the maps. 252 try { 253 Class<?> r = com.android.internal.R.class; 254 // Parse the styleable class first, since it may contribute to attr values. 255 parseStyleable(); 256 257 for (Class<?> inner : r.getDeclaredClasses()) { 258 if (inner == com.android.internal.R.styleable.class) { 259 // Already handled the styleable case. Not skipping attr, as there may be attrs 260 // that are not referenced from styleables. 261 continue; 262 } 263 String resTypeName = inner.getSimpleName(); 264 ResourceType resType = ResourceType.getEnum(resTypeName); 265 if (resType != null) { 266 Map<String, Integer> fullMap = null; 267 switch (resType) { 268 case ATTR: 269 fullMap = sRevRMap.get(ResourceType.ATTR); 270 break; 271 case STRING: 272 case STYLE: 273 // Slightly less than thousand entries in each. 274 fullMap = new HashMap<String, Integer>(1280); 275 // no break. 276 default: 277 if (fullMap == null) { 278 fullMap = new HashMap<String, Integer>(); 279 } 280 sRevRMap.put(resType, fullMap); 281 } 282 283 for (Field f : inner.getDeclaredFields()) { 284 // only process static final fields. Since the final attribute may have 285 // been altered by layoutlib_create, we only check static 286 if (!isValidRField(f)) { 287 continue; 288 } 289 Class<?> type = f.getType(); 290 if (type.isArray()) { 291 // if the object is an int[] we put it in sRArrayMap using an IntArray 292 // wrapper that properly implements equals and hashcode for the array 293 // objects, as required by the map contract. 294 sRArrayMap.put(new IntArray((int[]) f.get(null)), f.getName()); 295 } else { 296 Integer value = (Integer) f.get(null); 297 sRMap.put(value, Pair.of(resType, f.getName())); 298 fullMap.put(f.getName(), value); 299 } 300 } 301 } 302 } 303 } catch (Exception throwable) { 304 if (log != null) { 305 log.error(LayoutLog.TAG_BROKEN, 306 "Failed to load com.android.internal.R from the layout library jar", 307 throwable, null); 308 } 309 return false; 310 } 311 312 return true; 313 } 314 315 /** 316 * Tests if the field is pubic, static and one of int or int[]. 317 */ isValidRField(Field field)318 private static boolean isValidRField(Field field) { 319 int modifiers = field.getModifiers(); 320 boolean isAcceptable = Modifier.isPublic(modifiers) && Modifier.isStatic(modifiers); 321 Class<?> type = field.getType(); 322 return isAcceptable && type == int.class || 323 (type.isArray() && type.getComponentType() == int.class); 324 325 } 326 parseStyleable()327 private static void parseStyleable() throws Exception { 328 // R.attr doesn't contain all the needed values. There are too many resources in the 329 // framework for all to be in the R class. Only the ones specified manually in 330 // res/values/symbols.xml are put in R class. Since, we need to create a map of all attr 331 // values, we try and find them from the styleables. 332 333 // There were 1500 elements in this map at M timeframe. 334 Map<String, Integer> revRAttrMap = new HashMap<String, Integer>(2048); 335 sRevRMap.put(ResourceType.ATTR, revRAttrMap); 336 // There were 2000 elements in this map at M timeframe. 337 Map<String, Integer> revRStyleableMap = new HashMap<String, Integer>(3072); 338 sRevRMap.put(ResourceType.STYLEABLE, revRStyleableMap); 339 Class<?> c = com.android.internal.R.styleable.class; 340 Field[] fields = c.getDeclaredFields(); 341 // Sort the fields to bring all arrays to the beginning, so that indices into the array are 342 // able to refer back to the arrays (i.e. no forward references). 343 Arrays.sort(fields, new Comparator<Field>() { 344 @Override 345 public int compare(Field o1, Field o2) { 346 if (o1 == o2) { 347 return 0; 348 } 349 Class<?> t1 = o1.getType(); 350 Class<?> t2 = o2.getType(); 351 if (t1.isArray() && !t2.isArray()) { 352 return -1; 353 } else if (t2.isArray() && !t1.isArray()) { 354 return 1; 355 } 356 return o1.getName().compareTo(o2.getName()); 357 } 358 }); 359 Map<String, int[]> styleables = new HashMap<String, int[]>(); 360 for (Field field : fields) { 361 if (!isValidRField(field)) { 362 // Only consider public static fields that are int or int[]. 363 // Don't check the final flag as it may have been modified by layoutlib_create. 364 continue; 365 } 366 String name = field.getName(); 367 if (field.getType().isArray()) { 368 int[] styleableValue = (int[]) field.get(null); 369 sRArrayMap.put(new IntArray(styleableValue), name); 370 styleables.put(name, styleableValue); 371 continue; 372 } 373 // Not an array. 374 String arrayName = name; 375 int[] arrayValue = null; 376 int index; 377 while ((index = arrayName.lastIndexOf('_')) >= 0) { 378 // Find the name of the corresponding styleable. 379 // Search in reverse order so that attrs like LinearLayout_Layout_layout_gravity 380 // are mapped to LinearLayout_Layout and not to LinearLayout. 381 arrayName = arrayName.substring(0, index); 382 arrayValue = styleables.get(arrayName); 383 if (arrayValue != null) { 384 break; 385 } 386 } 387 index = (Integer) field.get(null); 388 if (arrayValue != null) { 389 String attrName = name.substring(arrayName.length() + 1); 390 int attrValue = arrayValue[index]; 391 sRMap.put(attrValue, Pair.of(ResourceType.ATTR, attrName)); 392 revRAttrMap.put(attrName, attrValue); 393 } 394 sRMap.put(index, Pair.of(ResourceType.STYLEABLE, name)); 395 revRStyleableMap.put(name, index); 396 } 397 } 398 399 @Override dispose()400 public boolean dispose() { 401 BridgeAssetManager.clearSystem(); 402 403 // dispose of the default typeface. 404 Typeface_Delegate.resetDefaults(); 405 406 return true; 407 } 408 409 /** 410 * Starts a layout session by inflating and rendering it. The method returns a 411 * {@link RenderSession} on which further actions can be taken. 412 * <p/> 413 * If {@link SessionParams} includes the {@link RenderParamsFlags#FLAG_DO_NOT_RENDER_ON_CREATE}, 414 * this method will only inflate the layout but will NOT render it. 415 * @param params the {@link SessionParams} object with all the information necessary to create 416 * the scene. 417 * @return a new {@link RenderSession} object that contains the result of the layout. 418 * @since 5 419 */ 420 @Override createSession(SessionParams params)421 public RenderSession createSession(SessionParams params) { 422 try { 423 Result lastResult = SUCCESS.createResult(); 424 RenderSessionImpl scene = new RenderSessionImpl(params); 425 try { 426 prepareThread(); 427 lastResult = scene.init(params.getTimeout()); 428 if (lastResult.isSuccess()) { 429 lastResult = scene.inflate(); 430 431 boolean doNotRenderOnCreate = Boolean.TRUE.equals( 432 params.getFlag(RenderParamsFlags.FLAG_DO_NOT_RENDER_ON_CREATE)); 433 if (lastResult.isSuccess() && !doNotRenderOnCreate) { 434 lastResult = scene.render(true /*freshRender*/); 435 } 436 } 437 } finally { 438 scene.release(); 439 cleanupThread(); 440 } 441 442 return new BridgeRenderSession(scene, lastResult); 443 } catch (Throwable t) { 444 // get the real cause of the exception. 445 Throwable t2 = t; 446 while (t2.getCause() != null) { 447 t2 = t.getCause(); 448 } 449 return new BridgeRenderSession(null, 450 ERROR_UNKNOWN.createResult(t2.getMessage(), t)); 451 } 452 } 453 454 @Override renderDrawable(DrawableParams params)455 public Result renderDrawable(DrawableParams params) { 456 try { 457 Result lastResult = SUCCESS.createResult(); 458 RenderDrawable action = new RenderDrawable(params); 459 try { 460 prepareThread(); 461 lastResult = action.init(params.getTimeout()); 462 if (lastResult.isSuccess()) { 463 lastResult = action.render(); 464 } 465 } finally { 466 action.release(); 467 cleanupThread(); 468 } 469 470 return lastResult; 471 } catch (Throwable t) { 472 // get the real cause of the exception. 473 Throwable t2 = t; 474 while (t2.getCause() != null) { 475 t2 = t.getCause(); 476 } 477 return ERROR_UNKNOWN.createResult(t2.getMessage(), t); 478 } 479 } 480 481 @Override clearCaches(Object projectKey)482 public void clearCaches(Object projectKey) { 483 if (projectKey != null) { 484 sProjectBitmapCache.remove(projectKey); 485 sProject9PatchCache.remove(projectKey); 486 } 487 } 488 489 @Override getViewParent(Object viewObject)490 public Result getViewParent(Object viewObject) { 491 if (viewObject instanceof View) { 492 return Status.SUCCESS.createResult(((View)viewObject).getParent()); 493 } 494 495 throw new IllegalArgumentException("viewObject is not a View"); 496 } 497 498 @Override getViewIndex(Object viewObject)499 public Result getViewIndex(Object viewObject) { 500 if (viewObject instanceof View) { 501 View view = (View) viewObject; 502 ViewParent parentView = view.getParent(); 503 504 if (parentView instanceof ViewGroup) { 505 Status.SUCCESS.createResult(((ViewGroup) parentView).indexOfChild(view)); 506 } 507 508 return Status.SUCCESS.createResult(); 509 } 510 511 throw new IllegalArgumentException("viewObject is not a View"); 512 } 513 514 @Override isRtl(String locale)515 public boolean isRtl(String locale) { 516 return isLocaleRtl(locale); 517 } 518 isLocaleRtl(String locale)519 public static boolean isLocaleRtl(String locale) { 520 if (locale == null) { 521 locale = ""; 522 } 523 ULocale uLocale = new ULocale(locale); 524 return uLocale.getCharacterOrientation().equals(ICU_LOCALE_DIRECTION_RTL); 525 } 526 527 /** 528 * Returns the lock for the bridge 529 */ getLock()530 public static ReentrantLock getLock() { 531 return sLock; 532 } 533 534 /** 535 * Prepares the current thread for rendering. 536 * 537 * Note that while this can be called several time, the first call to {@link #cleanupThread()} 538 * will do the clean-up, and make the thread unable to do further scene actions. 539 */ prepareThread()540 public synchronized static void prepareThread() { 541 // we need to make sure the Looper has been initialized for this thread. 542 // this is required for View that creates Handler objects. 543 if (Looper.myLooper() == null) { 544 Looper.prepareMainLooper(); 545 } 546 } 547 548 /** 549 * Cleans up thread-specific data. After this, the thread cannot be used for scene actions. 550 * <p> 551 * Note that it doesn't matter how many times {@link #prepareThread()} was called, a single 552 * call to this will prevent the thread from doing further scene actions 553 */ cleanupThread()554 public synchronized static void cleanupThread() { 555 // clean up the looper 556 Looper_Accessor.cleanupThread(); 557 } 558 getLog()559 public static LayoutLog getLog() { 560 return sCurrentLog; 561 } 562 setLog(LayoutLog log)563 public static void setLog(LayoutLog log) { 564 // check only the thread currently owning the lock can do this. 565 if (!sLock.isHeldByCurrentThread()) { 566 throw new IllegalStateException("scene must be acquired first. see #acquire(long)"); 567 } 568 569 if (log != null) { 570 sCurrentLog = log; 571 } else { 572 sCurrentLog = sDefaultLog; 573 } 574 } 575 576 /** 577 * Returns details of a framework resource from its integer value. 578 * @param value the integer value 579 * @return a Pair containing the resource type and name, or null if the id 580 * does not match any resource. 581 */ resolveResourceId(int value)582 public static Pair<ResourceType, String> resolveResourceId(int value) { 583 Pair<ResourceType, String> pair = sRMap.get(value); 584 if (pair == null) { 585 pair = sDynamicIds.resolveId(value); 586 if (pair == null) { 587 //System.out.println(String.format("Missing id: %1$08X (%1$d)", value)); 588 } 589 } 590 return pair; 591 } 592 593 /** 594 * Returns the name of a framework resource whose value is an int array. 595 */ resolveResourceId(int[] array)596 public static String resolveResourceId(int[] array) { 597 sIntArrayWrapper.set(array); 598 return sRArrayMap.get(sIntArrayWrapper); 599 } 600 601 /** 602 * Returns the integer id of a framework resource, from a given resource type and resource name. 603 * <p/> 604 * If no resource is found, it creates a dynamic id for the resource. 605 * 606 * @param type the type of the resource 607 * @param name the name of the resource. 608 * 609 * @return an {@link Integer} containing the resource id. 610 */ 611 @NonNull getResourceId(ResourceType type, String name)612 public static Integer getResourceId(ResourceType type, String name) { 613 Map<String, Integer> map = sRevRMap.get(type); 614 Integer value = null; 615 if (map != null) { 616 value = map.get(name); 617 } 618 619 return value == null ? sDynamicIds.getId(type, name) : value; 620 621 } 622 623 /** 624 * Returns the list of possible enums for a given attribute name. 625 */ getEnumValues(String attributeName)626 public static Map<String, Integer> getEnumValues(String attributeName) { 627 if (sEnumValueMap != null) { 628 return sEnumValueMap.get(attributeName); 629 } 630 631 return null; 632 } 633 634 /** 635 * Returns the platform build properties. 636 */ getPlatformProperties()637 public static Map<String, String> getPlatformProperties() { 638 return sPlatformProperties; 639 } 640 641 /** 642 * Returns the bitmap for a specific path, from a specific project cache, or from the 643 * framework cache. 644 * @param value the path of the bitmap 645 * @param projectKey the key of the project, or null to query the framework cache. 646 * @return the cached Bitmap or null if not found. 647 */ getCachedBitmap(String value, Object projectKey)648 public static Bitmap getCachedBitmap(String value, Object projectKey) { 649 if (projectKey != null) { 650 Map<String, SoftReference<Bitmap>> map = sProjectBitmapCache.get(projectKey); 651 if (map != null) { 652 SoftReference<Bitmap> ref = map.get(value); 653 if (ref != null) { 654 return ref.get(); 655 } 656 } 657 } else { 658 SoftReference<Bitmap> ref = sFrameworkBitmapCache.get(value); 659 if (ref != null) { 660 return ref.get(); 661 } 662 } 663 664 return null; 665 } 666 667 /** 668 * Sets a bitmap in a project cache or in the framework cache. 669 * @param value the path of the bitmap 670 * @param bmp the Bitmap object 671 * @param projectKey the key of the project, or null to put the bitmap in the framework cache. 672 */ setCachedBitmap(String value, Bitmap bmp, Object projectKey)673 public static void setCachedBitmap(String value, Bitmap bmp, Object projectKey) { 674 if (projectKey != null) { 675 Map<String, SoftReference<Bitmap>> map = sProjectBitmapCache.get(projectKey); 676 677 if (map == null) { 678 map = new HashMap<String, SoftReference<Bitmap>>(); 679 sProjectBitmapCache.put(projectKey, map); 680 } 681 682 map.put(value, new SoftReference<Bitmap>(bmp)); 683 } else { 684 sFrameworkBitmapCache.put(value, new SoftReference<Bitmap>(bmp)); 685 } 686 } 687 688 /** 689 * Returns the 9 patch chunk for a specific path, from a specific project cache, or from the 690 * framework cache. 691 * @param value the path of the 9 patch 692 * @param projectKey the key of the project, or null to query the framework cache. 693 * @return the cached 9 patch or null if not found. 694 */ getCached9Patch(String value, Object projectKey)695 public static NinePatchChunk getCached9Patch(String value, Object projectKey) { 696 if (projectKey != null) { 697 Map<String, SoftReference<NinePatchChunk>> map = sProject9PatchCache.get(projectKey); 698 699 if (map != null) { 700 SoftReference<NinePatchChunk> ref = map.get(value); 701 if (ref != null) { 702 return ref.get(); 703 } 704 } 705 } else { 706 SoftReference<NinePatchChunk> ref = sFramework9PatchCache.get(value); 707 if (ref != null) { 708 return ref.get(); 709 } 710 } 711 712 return null; 713 } 714 715 /** 716 * Sets a 9 patch chunk in a project cache or in the framework cache. 717 * @param value the path of the 9 patch 718 * @param ninePatch the 9 patch object 719 * @param projectKey the key of the project, or null to put the bitmap in the framework cache. 720 */ setCached9Patch(String value, NinePatchChunk ninePatch, Object projectKey)721 public static void setCached9Patch(String value, NinePatchChunk ninePatch, Object projectKey) { 722 if (projectKey != null) { 723 Map<String, SoftReference<NinePatchChunk>> map = sProject9PatchCache.get(projectKey); 724 725 if (map == null) { 726 map = new HashMap<String, SoftReference<NinePatchChunk>>(); 727 sProject9PatchCache.put(projectKey, map); 728 } 729 730 map.put(value, new SoftReference<NinePatchChunk>(ninePatch)); 731 } else { 732 sFramework9PatchCache.put(value, new SoftReference<NinePatchChunk>(ninePatch)); 733 } 734 } 735 } 736