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