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