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 static com.android.ide.common.rendering.api.Result.Status.ERROR_UNKNOWN;
     20 import static com.android.ide.common.rendering.api.Result.Status.SUCCESS;
     21 
     22 import com.android.ide.common.rendering.api.Capability;
     23 import com.android.ide.common.rendering.api.DrawableParams;
     24 import com.android.ide.common.rendering.api.LayoutLog;
     25 import com.android.ide.common.rendering.api.RenderSession;
     26 import com.android.ide.common.rendering.api.Result;
     27 import com.android.ide.common.rendering.api.Result.Status;
     28 import com.android.ide.common.rendering.api.SessionParams;
     29 import com.android.layoutlib.bridge.impl.FontLoader;
     30 import com.android.layoutlib.bridge.impl.RenderDrawable;
     31 import com.android.layoutlib.bridge.impl.RenderSessionImpl;
     32 import com.android.layoutlib.bridge.util.DynamicIdMap;
     33 import com.android.ninepatch.NinePatchChunk;
     34 import com.android.resources.ResourceType;
     35 import com.android.tools.layoutlib.create.MethodAdapter;
     36 import com.android.tools.layoutlib.create.OverrideMethod;
     37 import com.android.util.Pair;
     38 import com.ibm.icu.util.ULocale;
     39 
     40 import android.content.res.BridgeAssetManager;
     41 import android.graphics.Bitmap;
     42 import android.graphics.Typeface_Accessor;
     43 import android.graphics.Typeface_Delegate;
     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.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 /**
     62  * Main entry point of the LayoutLib Bridge.
     63  * <p/>To use this bridge, simply instantiate an object of type {@link Bridge} and call
     64  * {@link #createScene(SceneParams)}
     65  */
     66 public final class Bridge extends com.android.ide.common.rendering.api.Bridge {
     67 
     68     private static final String ICU_LOCALE_DIRECTION_RTL = "right-to-left";
     69 
     70     public static class StaticMethodNotImplementedException extends RuntimeException {
     71         private static final long serialVersionUID = 1L;
     72 
     73         public StaticMethodNotImplementedException(String msg) {
     74             super(msg);
     75         }
     76     }
     77 
     78     /**
     79      * Lock to ensure only one rendering/inflating happens at a time.
     80      * This is due to some singleton in the Android framework.
     81      */
     82     private final static ReentrantLock sLock = new ReentrantLock();
     83 
     84     /**
     85      * Maps from id to resource type/name. This is for com.android.internal.R
     86      */
     87     private final static Map<Integer, Pair<ResourceType, String>> sRMap =
     88         new HashMap<Integer, Pair<ResourceType, String>>();
     89 
     90     /**
     91      * Same as sRMap except for int[] instead of int resources. This is for android.R only.
     92      */
     93     private final static Map<IntArray, String> sRArrayMap = new HashMap<IntArray, String>();
     94     /**
     95      * Reverse map compared to sRMap, resource type -> (resource name -> id).
     96      * This is for com.android.internal.R.
     97      */
     98     private final static Map<ResourceType, Map<String, Integer>> sRevRMap =
     99         new EnumMap<ResourceType, Map<String,Integer>>(ResourceType.class);
    100 
    101     // framework resources are defined as 0x01XX#### where XX is the resource type (layout,
    102     // drawable, etc...). Using FF as the type allows for 255 resource types before we get a
    103     // collision which should be fine.
    104     private final static int DYNAMIC_ID_SEED_START = 0x01ff0000;
    105     private final static DynamicIdMap sDynamicIds = new DynamicIdMap(DYNAMIC_ID_SEED_START);
    106 
    107     private final static Map<Object, Map<String, SoftReference<Bitmap>>> sProjectBitmapCache =
    108         new HashMap<Object, Map<String, SoftReference<Bitmap>>>();
    109     private final static Map<Object, Map<String, SoftReference<NinePatchChunk>>> sProject9PatchCache =
    110         new HashMap<Object, Map<String, SoftReference<NinePatchChunk>>>();
    111 
    112     private final static Map<String, SoftReference<Bitmap>> sFrameworkBitmapCache =
    113         new HashMap<String, SoftReference<Bitmap>>();
    114     private final static Map<String, SoftReference<NinePatchChunk>> sFramework9PatchCache =
    115         new HashMap<String, SoftReference<NinePatchChunk>>();
    116 
    117     private static Map<String, Map<String, Integer>> sEnumValueMap;
    118     private static Map<String, String> sPlatformProperties;
    119 
    120     /**
    121      * int[] wrapper to use as keys in maps.
    122      */
    123     private final static class IntArray {
    124         private int[] mArray;
    125 
    126         private IntArray() {
    127             // do nothing
    128         }
    129 
    130         private IntArray(int[] a) {
    131             mArray = a;
    132         }
    133 
    134         private void set(int[] a) {
    135             mArray = a;
    136         }
    137 
    138         @Override
    139         public int hashCode() {
    140             return Arrays.hashCode(mArray);
    141         }
    142 
    143         @Override
    144         public boolean equals(Object obj) {
    145             if (this == obj) return true;
    146             if (obj == null) return false;
    147             if (getClass() != obj.getClass()) return false;
    148 
    149             IntArray other = (IntArray) obj;
    150             if (!Arrays.equals(mArray, other.mArray)) return false;
    151             return true;
    152         }
    153     }
    154 
    155     /** Instance of IntArrayWrapper to be reused in {@link #resolveResourceId(int[])}. */
    156     private final static IntArray sIntArrayWrapper = new IntArray();
    157 
    158     /**
    159      * A default log than prints to stdout/stderr.
    160      */
    161     private final static LayoutLog sDefaultLog = new LayoutLog() {
    162         @Override
    163         public void error(String tag, String message, Object data) {
    164             System.err.println(message);
    165         }
    166 
    167         @Override
    168         public void error(String tag, String message, Throwable throwable, Object data) {
    169             System.err.println(message);
    170         }
    171 
    172         @Override
    173         public void warning(String tag, String message, Object data) {
    174             System.out.println(message);
    175         }
    176     };
    177 
    178     /**
    179      * Current log.
    180      */
    181     private static LayoutLog sCurrentLog = sDefaultLog;
    182 
    183     private EnumSet<Capability> mCapabilities;
    184 
    185     @Override
    186     public int getApiLevel() {
    187         return com.android.ide.common.rendering.api.Bridge.API_CURRENT;
    188     }
    189 
    190     @Override
    191     public EnumSet<Capability> getCapabilities() {
    192         return mCapabilities;
    193     }
    194 
    195     @Override
    196     public boolean init(Map<String,String> platformProperties,
    197             File fontLocation,
    198             Map<String, Map<String, Integer>> enumValueMap,
    199             LayoutLog log) {
    200         sPlatformProperties = platformProperties;
    201         sEnumValueMap = enumValueMap;
    202 
    203         // don't use EnumSet.allOf(), because the bridge doesn't come with its specific version
    204         // of layoutlib_api. It is provided by the client which could have a more recent version
    205         // with newer, unsupported capabilities.
    206         mCapabilities = EnumSet.of(
    207                 Capability.UNBOUND_RENDERING,
    208                 Capability.CUSTOM_BACKGROUND_COLOR,
    209                 Capability.RENDER,
    210                 Capability.LAYOUT_ONLY,
    211                 Capability.EMBEDDED_LAYOUT,
    212                 Capability.VIEW_MANIPULATION,
    213                 Capability.PLAY_ANIMATION,
    214                 Capability.ANIMATED_VIEW_MANIPULATION,
    215                 Capability.ADAPTER_BINDING,
    216                 Capability.EXTENDED_VIEWINFO,
    217                 Capability.FIXED_SCALABLE_NINE_PATCH,
    218                 Capability.RTL);
    219 
    220 
    221         BridgeAssetManager.initSystem();
    222 
    223         // When DEBUG_LAYOUT is set and is not 0 or false, setup a default listener
    224         // on static (native) methods which prints the signature on the console and
    225         // throws an exception.
    226         // This is useful when testing the rendering in ADT to identify static native
    227         // methods that are ignored -- layoutlib_create makes them returns 0/false/null
    228         // which is generally OK yet might be a problem, so this is how you'd find out.
    229         //
    230         // Currently layoutlib_create only overrides static native method.
    231         // Static non-natives are not overridden and thus do not get here.
    232         final String debug = System.getenv("DEBUG_LAYOUT");
    233         if (debug != null && !debug.equals("0") && !debug.equals("false")) {
    234 
    235             OverrideMethod.setDefaultListener(new MethodAdapter() {
    236                 @Override
    237                 public void onInvokeV(String signature, boolean isNative, Object caller) {
    238                     sDefaultLog.error(null, "Missing Stub: " + signature +
    239                             (isNative ? " (native)" : ""), null /*data*/);
    240 
    241                     if (debug.equalsIgnoreCase("throw")) {
    242                         // Throwing this exception doesn't seem that useful. It breaks
    243                         // the layout editor yet doesn't display anything meaningful to the
    244                         // user. Having the error in the console is just as useful. We'll
    245                         // throw it only if the environment variable is "throw" or "THROW".
    246                         throw new StaticMethodNotImplementedException(signature);
    247                     }
    248                 }
    249             });
    250         }
    251 
    252         // load the fonts.
    253         FontLoader fontLoader = FontLoader.create(fontLocation.getAbsolutePath());
    254         if (fontLoader != null) {
    255             Typeface_Delegate.init(fontLoader);
    256         } else {
    257             log.error(LayoutLog.TAG_BROKEN,
    258                     "Failed create FontLoader in layout lib.", null);
    259             return false;
    260         }
    261 
    262         // now parse com.android.internal.R (and only this one as android.R is a subset of
    263         // the internal version), and put the content in the maps.
    264         try {
    265             Class<?> r = com.android.internal.R.class;
    266 
    267             for (Class<?> inner : r.getDeclaredClasses()) {
    268                 String resTypeName = inner.getSimpleName();
    269                 ResourceType resType = ResourceType.getEnum(resTypeName);
    270                 if (resType != null) {
    271                     Map<String, Integer> fullMap = new HashMap<String, Integer>();
    272                     sRevRMap.put(resType, fullMap);
    273 
    274                     for (Field f : inner.getDeclaredFields()) {
    275                         // only process static final fields. Since the final attribute may have
    276                         // been altered by layoutlib_create, we only check static
    277                         int modifiers = f.getModifiers();
    278                         if (Modifier.isStatic(modifiers)) {
    279                             Class<?> type = f.getType();
    280                             if (type.isArray() && type.getComponentType() == int.class) {
    281                                 // if the object is an int[] we put it in sRArrayMap using an IntArray
    282                                 // wrapper that properly implements equals and hashcode for the array
    283                                 // objects, as required by the map contract.
    284                                 sRArrayMap.put(new IntArray((int[]) f.get(null)), f.getName());
    285                             } else if (type == int.class) {
    286                                 Integer value = (Integer) f.get(null);
    287                                 sRMap.put(value, Pair.of(resType, f.getName()));
    288                                 fullMap.put(f.getName(), value);
    289                             } else {
    290                                 assert false;
    291                             }
    292                         }
    293                     }
    294                 }
    295             }
    296         } catch (Throwable throwable) {
    297             if (log != null) {
    298                 log.error(LayoutLog.TAG_BROKEN,
    299                         "Failed to load com.android.internal.R from the layout library jar",
    300                         throwable);
    301             }
    302             return false;
    303         }
    304 
    305         return true;
    306     }
    307 
    308     @Override
    309     public boolean dispose() {
    310         BridgeAssetManager.clearSystem();
    311 
    312         // dispose of the default typeface.
    313         Typeface_Accessor.resetDefaults();
    314 
    315         return true;
    316     }
    317 
    318     /**
    319      * Starts a layout session by inflating and rendering it. The method returns a
    320      * {@link RenderSession} on which further actions can be taken.
    321      *
    322      * @param params the {@link SessionParams} object with all the information necessary to create
    323      *           the scene.
    324      * @return a new {@link RenderSession} object that contains the result of the layout.
    325      * @since 5
    326      */
    327     @Override
    328     public RenderSession createSession(SessionParams params) {
    329         try {
    330             Result lastResult = SUCCESS.createResult();
    331             RenderSessionImpl scene = new RenderSessionImpl(params);
    332             try {
    333                 prepareThread();
    334                 lastResult = scene.init(params.getTimeout());
    335                 if (lastResult.isSuccess()) {
    336                     lastResult = scene.inflate();
    337                     if (lastResult.isSuccess()) {
    338                         lastResult = scene.render(true /*freshRender*/);
    339                     }
    340                 }
    341             } finally {
    342                 scene.release();
    343                 cleanupThread();
    344             }
    345 
    346             return new BridgeRenderSession(scene, lastResult);
    347         } catch (Throwable t) {
    348             // get the real cause of the exception.
    349             Throwable t2 = t;
    350             while (t2.getCause() != null) {
    351                 t2 = t.getCause();
    352             }
    353             return new BridgeRenderSession(null,
    354                     ERROR_UNKNOWN.createResult(t2.getMessage(), t));
    355         }
    356     }
    357 
    358     @Override
    359     public Result renderDrawable(DrawableParams params) {
    360         try {
    361             Result lastResult = SUCCESS.createResult();
    362             RenderDrawable action = new RenderDrawable(params);
    363             try {
    364                 prepareThread();
    365                 lastResult = action.init(params.getTimeout());
    366                 if (lastResult.isSuccess()) {
    367                     lastResult = action.render();
    368                 }
    369             } finally {
    370                 action.release();
    371                 cleanupThread();
    372             }
    373 
    374             return lastResult;
    375         } catch (Throwable t) {
    376             // get the real cause of the exception.
    377             Throwable t2 = t;
    378             while (t2.getCause() != null) {
    379                 t2 = t.getCause();
    380             }
    381             return ERROR_UNKNOWN.createResult(t2.getMessage(), t);
    382         }
    383     }
    384 
    385     @Override
    386     public void clearCaches(Object projectKey) {
    387         if (projectKey != null) {
    388             sProjectBitmapCache.remove(projectKey);
    389             sProject9PatchCache.remove(projectKey);
    390         }
    391     }
    392 
    393     @Override
    394     public Result getViewParent(Object viewObject) {
    395         if (viewObject instanceof View) {
    396             return Status.SUCCESS.createResult(((View)viewObject).getParent());
    397         }
    398 
    399         throw new IllegalArgumentException("viewObject is not a View");
    400     }
    401 
    402     @Override
    403     public Result getViewIndex(Object viewObject) {
    404         if (viewObject instanceof View) {
    405             View view = (View) viewObject;
    406             ViewParent parentView = view.getParent();
    407 
    408             if (parentView instanceof ViewGroup) {
    409                 Status.SUCCESS.createResult(((ViewGroup) parentView).indexOfChild(view));
    410             }
    411 
    412             return Status.SUCCESS.createResult();
    413         }
    414 
    415         throw new IllegalArgumentException("viewObject is not a View");
    416     }
    417 
    418     @Override
    419     public boolean isRtl(String locale) {
    420         return isLocaleRtl(locale);
    421     }
    422 
    423     public static boolean isLocaleRtl(String locale) {
    424         if (locale == null) {
    425             locale = "";
    426         }
    427         ULocale uLocale = new ULocale(locale);
    428         return uLocale.getCharacterOrientation().equals(ICU_LOCALE_DIRECTION_RTL) ?
    429                 true : false;
    430     }
    431 
    432     /**
    433      * Returns the lock for the bridge
    434      */
    435     public static ReentrantLock getLock() {
    436         return sLock;
    437     }
    438 
    439     /**
    440      * Prepares the current thread for rendering.
    441      *
    442      * Note that while this can be called several time, the first call to {@link #cleanupThread()}
    443      * will do the clean-up, and make the thread unable to do further scene actions.
    444      */
    445     public static void prepareThread() {
    446         // we need to make sure the Looper has been initialized for this thread.
    447         // this is required for View that creates Handler objects.
    448         if (Looper.myLooper() == null) {
    449             Looper.prepareMainLooper();
    450         }
    451     }
    452 
    453     /**
    454      * Cleans up thread-specific data. After this, the thread cannot be used for scene actions.
    455      * <p>
    456      * Note that it doesn't matter how many times {@link #prepareThread()} was called, a single
    457      * call to this will prevent the thread from doing further scene actions
    458      */
    459     public static void cleanupThread() {
    460         // clean up the looper
    461         Looper_Accessor.cleanupThread();
    462     }
    463 
    464     public static LayoutLog getLog() {
    465         return sCurrentLog;
    466     }
    467 
    468     public static void setLog(LayoutLog log) {
    469         // check only the thread currently owning the lock can do this.
    470         if (sLock.isHeldByCurrentThread() == false) {
    471             throw new IllegalStateException("scene must be acquired first. see #acquire(long)");
    472         }
    473 
    474         if (log != null) {
    475             sCurrentLog = log;
    476         } else {
    477             sCurrentLog = sDefaultLog;
    478         }
    479     }
    480 
    481     /**
    482      * Returns details of a framework resource from its integer value.
    483      * @param value the integer value
    484      * @return a Pair containing the resource type and name, or null if the id
    485      *     does not match any resource.
    486      */
    487     public static Pair<ResourceType, String> resolveResourceId(int value) {
    488         Pair<ResourceType, String> pair = sRMap.get(value);
    489         if (pair == null) {
    490             pair = sDynamicIds.resolveId(value);
    491             if (pair == null) {
    492                 //System.out.println(String.format("Missing id: %1$08X (%1$d)", value));
    493             }
    494         }
    495         return pair;
    496     }
    497 
    498     /**
    499      * Returns the name of a framework resource whose value is an int array.
    500      * @param array
    501      */
    502     public static String resolveResourceId(int[] array) {
    503         sIntArrayWrapper.set(array);
    504         return sRArrayMap.get(sIntArrayWrapper);
    505     }
    506 
    507     /**
    508      * Returns the integer id of a framework resource, from a given resource type and resource name.
    509      * @param type the type of the resource
    510      * @param name the name of the resource.
    511      * @return an {@link Integer} containing the resource id, or null if no resource were found.
    512      */
    513     public static Integer getResourceId(ResourceType type, String name) {
    514         Map<String, Integer> map = sRevRMap.get(type);
    515         Integer value = null;
    516         if (map != null) {
    517             value = map.get(name);
    518         }
    519 
    520         if (value == null) {
    521             value = sDynamicIds.getId(type, name);
    522         }
    523 
    524         return value;
    525     }
    526 
    527     /**
    528      * Returns the list of possible enums for a given attribute name.
    529      */
    530     public static Map<String, Integer> getEnumValues(String attributeName) {
    531         if (sEnumValueMap != null) {
    532             return sEnumValueMap.get(attributeName);
    533         }
    534 
    535         return null;
    536     }
    537 
    538     /**
    539      * Returns the platform build properties.
    540      */
    541     public static Map<String, String> getPlatformProperties() {
    542         return sPlatformProperties;
    543     }
    544 
    545     /**
    546      * Returns the bitmap for a specific path, from a specific project cache, or from the
    547      * framework cache.
    548      * @param value the path of the bitmap
    549      * @param projectKey the key of the project, or null to query the framework cache.
    550      * @return the cached Bitmap or null if not found.
    551      */
    552     public static Bitmap getCachedBitmap(String value, Object projectKey) {
    553         if (projectKey != null) {
    554             Map<String, SoftReference<Bitmap>> map = sProjectBitmapCache.get(projectKey);
    555             if (map != null) {
    556                 SoftReference<Bitmap> ref = map.get(value);
    557                 if (ref != null) {
    558                     return ref.get();
    559                 }
    560             }
    561         } else {
    562             SoftReference<Bitmap> ref = sFrameworkBitmapCache.get(value);
    563             if (ref != null) {
    564                 return ref.get();
    565             }
    566         }
    567 
    568         return null;
    569     }
    570 
    571     /**
    572      * Sets a bitmap in a project cache or in the framework cache.
    573      * @param value the path of the bitmap
    574      * @param bmp the Bitmap object
    575      * @param projectKey the key of the project, or null to put the bitmap in the framework cache.
    576      */
    577     public static void setCachedBitmap(String value, Bitmap bmp, Object projectKey) {
    578         if (projectKey != null) {
    579             Map<String, SoftReference<Bitmap>> map = sProjectBitmapCache.get(projectKey);
    580 
    581             if (map == null) {
    582                 map = new HashMap<String, SoftReference<Bitmap>>();
    583                 sProjectBitmapCache.put(projectKey, map);
    584             }
    585 
    586             map.put(value, new SoftReference<Bitmap>(bmp));
    587         } else {
    588             sFrameworkBitmapCache.put(value, new SoftReference<Bitmap>(bmp));
    589         }
    590     }
    591 
    592     /**
    593      * Returns the 9 patch chunk for a specific path, from a specific project cache, or from the
    594      * framework cache.
    595      * @param value the path of the 9 patch
    596      * @param projectKey the key of the project, or null to query the framework cache.
    597      * @return the cached 9 patch or null if not found.
    598      */
    599     public static NinePatchChunk getCached9Patch(String value, Object projectKey) {
    600         if (projectKey != null) {
    601             Map<String, SoftReference<NinePatchChunk>> map = sProject9PatchCache.get(projectKey);
    602 
    603             if (map != null) {
    604                 SoftReference<NinePatchChunk> ref = map.get(value);
    605                 if (ref != null) {
    606                     return ref.get();
    607                 }
    608             }
    609         } else {
    610             SoftReference<NinePatchChunk> ref = sFramework9PatchCache.get(value);
    611             if (ref != null) {
    612                 return ref.get();
    613             }
    614         }
    615 
    616         return null;
    617     }
    618 
    619     /**
    620      * Sets a 9 patch chunk in a project cache or in the framework cache.
    621      * @param value the path of the 9 patch
    622      * @param ninePatch the 9 patch object
    623      * @param projectKey the key of the project, or null to put the bitmap in the framework cache.
    624      */
    625     public static void setCached9Patch(String value, NinePatchChunk ninePatch, Object projectKey) {
    626         if (projectKey != null) {
    627             Map<String, SoftReference<NinePatchChunk>> map = sProject9PatchCache.get(projectKey);
    628 
    629             if (map == null) {
    630                 map = new HashMap<String, SoftReference<NinePatchChunk>>();
    631                 sProject9PatchCache.put(projectKey, map);
    632             }
    633 
    634             map.put(value, new SoftReference<NinePatchChunk>(ninePatch));
    635         } else {
    636             sFramework9PatchCache.put(value, new SoftReference<NinePatchChunk>(ninePatch));
    637         }
    638     }
    639 }
    640