Home | History | Annotate | Download | only in rendering
      1 /*
      2  * Copyright (C) 2010 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.ide.common.rendering;
     18 
     19 import static com.android.ide.common.rendering.api.Result.Status.ERROR_REFLECTION;
     20 
     21 import com.android.ide.common.log.ILogger;
     22 import com.android.ide.common.rendering.api.Bridge;
     23 import com.android.ide.common.rendering.api.Capability;
     24 import com.android.ide.common.rendering.api.DrawableParams;
     25 import com.android.ide.common.rendering.api.ILayoutPullParser;
     26 import com.android.ide.common.rendering.api.LayoutLog;
     27 import com.android.ide.common.rendering.api.RenderSession;
     28 import com.android.ide.common.rendering.api.ResourceValue;
     29 import com.android.ide.common.rendering.api.Result;
     30 import com.android.ide.common.rendering.api.SessionParams;
     31 import com.android.ide.common.rendering.api.ViewInfo;
     32 import com.android.ide.common.rendering.api.Result.Status;
     33 import com.android.ide.common.rendering.api.SessionParams.RenderingMode;
     34 import com.android.ide.common.rendering.legacy.ILegacyPullParser;
     35 import com.android.ide.common.rendering.legacy.LegacyCallback;
     36 import com.android.ide.common.resources.ResourceResolver;
     37 import com.android.ide.common.sdk.LoadStatus;
     38 import com.android.layoutlib.api.ILayoutBridge;
     39 import com.android.layoutlib.api.ILayoutLog;
     40 import com.android.layoutlib.api.ILayoutResult;
     41 import com.android.layoutlib.api.IProjectCallback;
     42 import com.android.layoutlib.api.IResourceValue;
     43 import com.android.layoutlib.api.IXmlPullParser;
     44 import com.android.layoutlib.api.ILayoutResult.ILayoutViewInfo;
     45 import com.android.resources.ResourceType;
     46 
     47 import java.awt.image.BufferedImage;
     48 import java.io.File;
     49 import java.lang.reflect.Constructor;
     50 import java.lang.reflect.Field;
     51 import java.lang.reflect.Method;
     52 import java.net.URI;
     53 import java.net.URL;
     54 import java.net.URLClassLoader;
     55 import java.util.ArrayList;
     56 import java.util.HashMap;
     57 import java.util.List;
     58 import java.util.Map;
     59 import java.util.Map.Entry;
     60 
     61 /**
     62  * Class to use the Layout library.
     63  * <p/>
     64  * Use {@link #load(String, ILogger)} to load the jar file.
     65  * <p/>
     66  * Use the layout library with:
     67  * {@link #init(String, Map)}, {@link #supports(Capability)}, {@link #createSession(SessionParams)},
     68  * {@link #dispose()}, {@link #clearCaches(Object)}.
     69  *
     70  * <p/>
     71  * For client wanting to access both new and old (pre API level 5) layout libraries, it is
     72  * important that the following interfaces be used:<br>
     73  * {@link ILegacyPullParser} instead of {@link ILayoutPullParser}<br>
     74  * {@link LegacyCallback} instead of {@link com.android.ide.common.rendering.api.IProjectCallback}.
     75  * <p/>
     76  * These interfaces will ensure that both new and older Layout libraries can be accessed.
     77  */
     78 @SuppressWarnings("deprecation")
     79 public class LayoutLibrary {
     80 
     81     public final static String CLASS_BRIDGE = "com.android.layoutlib.bridge.Bridge"; //$NON-NLS-1$
     82 
     83     /** Link to the layout bridge */
     84     private final Bridge mBridge;
     85     /** Link to a ILayoutBridge in case loaded an older library */
     86     private final ILayoutBridge mLegacyBridge;
     87     /** Status of the layoutlib.jar loading */
     88     private final LoadStatus mStatus;
     89     /** Message associated with the {@link LoadStatus}. This is mostly used when
     90      * {@link #getStatus()} returns {@link LoadStatus#FAILED}.
     91      */
     92     private final String mLoadMessage;
     93     /** classloader used to load the jar file */
     94     private final ClassLoader mClassLoader;
     95 
     96     // Reflection data for older Layout Libraries.
     97     private Method mViewGetParentMethod;
     98     private Method mViewGetBaselineMethod;
     99     private Method mViewParentIndexOfChildMethod;
    100     private Class<?> mMarginLayoutParamClass;
    101     private Field mLeftMarginField;
    102     private Field mTopMarginField;
    103     private Field mRightMarginField;
    104     private Field mBottomMarginField;
    105 
    106     /**
    107      * Returns the {@link LoadStatus} of the loading of the layoutlib jar file.
    108      */
    109     public LoadStatus getStatus() {
    110         return mStatus;
    111     }
    112 
    113     /** Returns the message associated with the {@link LoadStatus}. This is mostly used when
    114      * {@link #getStatus()} returns {@link LoadStatus#FAILED}.
    115      */
    116     public String getLoadMessage() {
    117         return mLoadMessage;
    118     }
    119 
    120     /**
    121      * Returns the classloader used to load the classes in the layoutlib jar file.
    122      */
    123     public ClassLoader getClassLoader() {
    124         return mClassLoader;
    125     }
    126 
    127     /**
    128      * Loads the layoutlib.jar file located at the given path and returns a {@link LayoutLibrary}
    129      * object representing the result.
    130      * <p/>
    131      * If loading failed {@link #getStatus()} will reflect this, and {@link #getBridge()} will
    132      * return null.
    133      *
    134      * @param layoutLibJarOsPath the path of the jar file
    135      * @param log an optional log file.
    136      * @return a {@link LayoutLibrary} object always.
    137      */
    138     public static LayoutLibrary load(String layoutLibJarOsPath, ILogger log, String toolName) {
    139 
    140         LoadStatus status = LoadStatus.LOADING;
    141         String message = null;
    142         Bridge bridge = null;
    143         ILayoutBridge legacyBridge = null;
    144         ClassLoader classLoader = null;
    145 
    146         try {
    147             // get the URL for the file.
    148             File f = new File(layoutLibJarOsPath);
    149             if (f.isFile() == false) {
    150                 if (log != null) {
    151                     log.error(null, "layoutlib.jar is missing!"); //$NON-NLS-1$
    152                 }
    153             } else {
    154                 URI uri = f.toURI();
    155                 URL url = uri.toURL();
    156 
    157                 // create a class loader. Because this jar reference interfaces
    158                 // that are in the editors plugin, it's important to provide
    159                 // a parent class loader.
    160                 classLoader = new URLClassLoader(
    161                         new URL[] { url },
    162                         LayoutLibrary.class.getClassLoader());
    163 
    164                 // load the class
    165                 Class<?> clazz = classLoader.loadClass(CLASS_BRIDGE);
    166                 if (clazz != null) {
    167                     // instantiate an object of the class.
    168                     Constructor<?> constructor = clazz.getConstructor();
    169                     if (constructor != null) {
    170                         Object bridgeObject = constructor.newInstance();
    171                         if (bridgeObject instanceof Bridge) {
    172                             bridge = (Bridge)bridgeObject;
    173                         } else if (bridgeObject instanceof ILayoutBridge) {
    174                             legacyBridge = (ILayoutBridge) bridgeObject;
    175                         }
    176                     }
    177                 }
    178 
    179                 if (bridge == null && legacyBridge == null) {
    180                     status = LoadStatus.FAILED;
    181                     message = "Failed to load " + CLASS_BRIDGE; //$NON-NLS-1$
    182                     if (log != null) {
    183                         log.error(null,
    184                                 "Failed to load " + //$NON-NLS-1$
    185                                 CLASS_BRIDGE +
    186                                 " from " +          //$NON-NLS-1$
    187                                 layoutLibJarOsPath);
    188                     }
    189                 } else {
    190                     // mark the lib as loaded, unless it's overridden below.
    191                     status = LoadStatus.LOADED;
    192 
    193                     // check the API, only if it's not a legacy bridge
    194                     if (bridge != null) {
    195                         int api = bridge.getApiLevel();
    196                         if (api > Bridge.API_CURRENT) {
    197                             status = LoadStatus.FAILED;
    198                             message = String.format(
    199                                     "This version of the rendering library is more recent than your version of %1$s. Please update %1$s", toolName);
    200                         }
    201                     }
    202                 }
    203             }
    204         } catch (Throwable t) {
    205             status = LoadStatus.FAILED;
    206             Throwable cause = t;
    207             while (cause.getCause() != null) {
    208                 cause = cause.getCause();
    209             }
    210             message = "Failed to load the LayoutLib: " + cause.getMessage();
    211             // log the error.
    212             if (log != null) {
    213                 log.error(t, message);
    214             }
    215         }
    216 
    217         return new LayoutLibrary(bridge, legacyBridge, classLoader, status, message);
    218     }
    219 
    220     // ------ Layout Lib API proxy
    221 
    222     /**
    223      * Returns the API level of the layout library.
    224      */
    225     public int getApiLevel() {
    226         if (mBridge != null) {
    227             return mBridge.getApiLevel();
    228         }
    229 
    230         if (mLegacyBridge != null) {
    231             return getLegacyApiLevel();
    232         }
    233 
    234         return 0;
    235     }
    236 
    237     /**
    238      * Returns the revision of the library inside a given (layoutlib) API level.
    239      * The true version number of the library is {@link #getApiLevel()}.{@link #getRevision()}
    240      */
    241     public int getRevision() {
    242         if (mBridge != null) {
    243             return mBridge.getRevision();
    244         }
    245 
    246         return 0;
    247     }
    248 
    249     /**
    250      * Returns whether the LayoutLibrary supports a given {@link Capability}.
    251      * @return true if it supports it.
    252      *
    253      * @see Bridge#getCapabilities()
    254      *
    255      */
    256     public boolean supports(Capability capability) {
    257         if (mBridge != null) {
    258             return mBridge.getCapabilities().contains(capability);
    259         }
    260 
    261         if (mLegacyBridge != null) {
    262             switch (capability) {
    263                 case UNBOUND_RENDERING:
    264                     // legacy stops at 4. 5 is new API.
    265                     return getLegacyApiLevel() == 4;
    266             }
    267         }
    268 
    269         return false;
    270     }
    271 
    272     /**
    273      * Initializes the Layout Library object. This must be called before any other action is taken
    274      * on the instance.
    275      *
    276      * @param platformProperties The build properties for the platform.
    277      * @param fontLocation the location of the fonts in the SDK target.
    278      * @param enumValueMap map attrName => { map enumFlagName => Integer value }. This is typically
    279      *          read from attrs.xml in the SDK target.
    280      * @param log a {@link LayoutLog} object. Can be null.
    281      * @return true if success.
    282      *
    283      * @see Bridge#init(String, Map)
    284      */
    285     public boolean init(Map<String, String> platformProperties,
    286             File fontLocation,
    287             Map<String, Map<String, Integer>> enumValueMap,
    288             LayoutLog log) {
    289         if (mBridge != null) {
    290             return mBridge.init(platformProperties, fontLocation, enumValueMap, log);
    291         } else if (mLegacyBridge != null) {
    292             return mLegacyBridge.init(fontLocation.getAbsolutePath(), enumValueMap);
    293         }
    294 
    295         return false;
    296     }
    297 
    298     /**
    299      * Prepares the layoutlib to unloaded.
    300      *
    301      * @see Bridge#dispose()
    302      */
    303     public boolean dispose() {
    304         if (mBridge != null) {
    305             return mBridge.dispose();
    306         }
    307 
    308         return true;
    309     }
    310 
    311     /**
    312      * Starts a layout session by inflating and rendering it. The method returns a
    313      * {@link RenderSession} on which further actions can be taken.
    314      * <p/>
    315      * Before taking further actions on the scene, it is recommended to use
    316      * {@link #supports(Capability)} to check what the scene can do.
    317      *
    318      * @return a new {@link ILayoutScene} object that contains the result of the scene creation and
    319      * first rendering or null if {@link #getStatus()} doesn't return {@link LoadStatus#LOADED}.
    320      *
    321      * @see Bridge#createSession(SessionParams)
    322      */
    323     public RenderSession createSession(SessionParams params) {
    324         if (mBridge != null) {
    325             RenderSession session = mBridge.createSession(params);
    326             if (params.getExtendedViewInfoMode() &&
    327                     mBridge.getCapabilities().contains(Capability.EXTENDED_VIEWINFO) == false) {
    328                 // Extended view info was requested but the layoutlib does not support it.
    329                 // Add it manually.
    330                 List<ViewInfo> infoList = session.getRootViews();
    331                 if (infoList != null) {
    332                     for (ViewInfo info : infoList) {
    333                         addExtendedViewInfo(info);
    334                     }
    335                 }
    336             }
    337 
    338             return session;
    339         } else if (mLegacyBridge != null) {
    340             return createLegacySession(params);
    341         }
    342 
    343         return null;
    344     }
    345 
    346     /**
    347      * Renders a Drawable. If the rendering is successful, the result image is accessible through
    348      * {@link Result#getData()}. It is of type {@link BufferedImage}
    349      * @param params the rendering parameters.
    350      * @return the result of the action.
    351      */
    352     public Result renderDrawable(DrawableParams params) {
    353         if (mBridge != null) {
    354             return mBridge.renderDrawable(params);
    355         }
    356 
    357         return Status.NOT_IMPLEMENTED.createResult();
    358     }
    359 
    360     /**
    361      * Clears the resource cache for a specific project.
    362      * <p/>This cache contains bitmaps and nine patches that are loaded from the disk and reused
    363      * until this method is called.
    364      * <p/>The cache is not configuration dependent and should only be cleared when a
    365      * resource changes (at this time only bitmaps and 9 patches go into the cache).
    366      *
    367      * @param projectKey the key for the project.
    368      *
    369      * @see Bridge#clearCaches(Object)
    370      */
    371     public void clearCaches(Object projectKey) {
    372         if (mBridge != null) {
    373             mBridge.clearCaches(projectKey);
    374         } else if (mLegacyBridge != null) {
    375             mLegacyBridge.clearCaches(projectKey);
    376         }
    377     }
    378 
    379     /**
    380      * Utility method returning the parent of a given view object.
    381      *
    382      * @param viewObject the object for which to return the parent.
    383      *
    384      * @return a {@link Result} indicating the status of the action, and if success, the parent
    385      *      object in {@link Result#getData()}
    386      */
    387     public Result getViewParent(Object viewObject) {
    388         if (mBridge != null) {
    389             Result r = mBridge.getViewParent(viewObject);
    390             if (r.isSuccess()) {
    391                 return r;
    392             }
    393         }
    394 
    395         return getViewParentWithReflection(viewObject);
    396     }
    397 
    398     /**
    399      * Utility method returning the index of a given view in its parent.
    400      * @param viewObject the object for which to return the index.
    401      *
    402      * @return a {@link Result} indicating the status of the action, and if success, the index in
    403      *      the parent in {@link Result#getData()}
    404      */
    405     public Result getViewIndex(Object viewObject) {
    406         if (mBridge != null) {
    407             Result r = mBridge.getViewIndex(viewObject);
    408             if (r.isSuccess()) {
    409                 return r;
    410             }
    411         }
    412 
    413         return getViewIndexReflection(viewObject);
    414     }
    415 
    416     // ------ Implementation
    417 
    418     private LayoutLibrary(Bridge bridge, ILayoutBridge legacyBridge, ClassLoader classLoader,
    419             LoadStatus status, String message) {
    420         mBridge = bridge;
    421         mLegacyBridge = legacyBridge;
    422         mClassLoader = classLoader;
    423         mStatus = status;
    424         mLoadMessage = message;
    425     }
    426 
    427     /**
    428      * Returns the API level of the legacy bridge.
    429      * <p/>
    430      * This handles the case where ILayoutBridge does not have a {@link ILayoutBridge#getApiLevel()}
    431      * (at API level 1).
    432      * <p/>
    433      * {@link ILayoutBridge#getApiLevel()} should never called directly.
    434      *
    435      * @return the api level of {@link #mLegacyBridge}.
    436      */
    437     private int getLegacyApiLevel() {
    438         int apiLevel = 1;
    439         try {
    440             apiLevel = mLegacyBridge.getApiLevel();
    441         } catch (AbstractMethodError e) {
    442             // the first version of the api did not have this method
    443             // so this is 1
    444         }
    445 
    446         return apiLevel;
    447     }
    448 
    449     private RenderSession createLegacySession(SessionParams params) {
    450         if (params.getLayoutDescription() instanceof IXmlPullParser == false) {
    451             throw new IllegalArgumentException("Parser must be of type ILegacyPullParser");
    452         }
    453         if (params.getProjectCallback() instanceof
    454                 com.android.layoutlib.api.IProjectCallback == false) {
    455             throw new IllegalArgumentException("Project callback must be of type ILegacyCallback");
    456         }
    457 
    458         if (params.getResources() instanceof ResourceResolver == false) {
    459             throw new IllegalArgumentException("RenderResources object must be of type ResourceResolver");
    460         }
    461 
    462         ResourceResolver resources = (ResourceResolver) params.getResources();
    463 
    464         int apiLevel = getLegacyApiLevel();
    465 
    466         // create a log wrapper since the older api requires a ILayoutLog
    467         final LayoutLog log = params.getLog();
    468         ILayoutLog logWrapper = new ILayoutLog() {
    469 
    470             public void warning(String message) {
    471                 log.warning(null, message, null /*data*/);
    472             }
    473 
    474             public void error(Throwable t) {
    475                 log.error(null, "error!", t, null /*data*/);
    476             }
    477 
    478             public void error(String message) {
    479                 log.error(null, message, null /*data*/);
    480             }
    481         };
    482 
    483 
    484         // convert the map of ResourceValue into IResourceValue. Super ugly but works.
    485 
    486         Map<String, Map<String, IResourceValue>> projectMap = convertMap(
    487                 resources.getProjectResources());
    488         Map<String, Map<String, IResourceValue>> frameworkMap = convertMap(
    489                 resources.getFrameworkResources());
    490 
    491         ILayoutResult result = null;
    492 
    493         if (apiLevel == 4) {
    494             // Final ILayoutBridge API added support for "render full height"
    495             result = mLegacyBridge.computeLayout(
    496                     (IXmlPullParser) params.getLayoutDescription(),
    497                     params.getProjectKey(),
    498                     params.getScreenWidth(), params.getScreenHeight(),
    499                     params.getRenderingMode() == RenderingMode.FULL_EXPAND ? true : false,
    500                     params.getDensity().getDpiValue(), params.getXdpi(), params.getYdpi(),
    501                     resources.getThemeName(), resources.isProjectTheme(),
    502                     projectMap, frameworkMap,
    503                     (IProjectCallback) params.getProjectCallback(),
    504                     logWrapper);
    505         } else if (apiLevel == 3) {
    506             // api 3 add density support.
    507             result = mLegacyBridge.computeLayout(
    508                     (IXmlPullParser) params.getLayoutDescription(), params.getProjectKey(),
    509                     params.getScreenWidth(), params.getScreenHeight(),
    510                     params.getDensity().getDpiValue(), params.getXdpi(), params.getYdpi(),
    511                     resources.getThemeName(), resources.isProjectTheme(),
    512                     projectMap, frameworkMap,
    513                     (IProjectCallback) params.getProjectCallback(), logWrapper);
    514         } else if (apiLevel == 2) {
    515             // api 2 added boolean for separation of project/framework theme
    516             result = mLegacyBridge.computeLayout(
    517                     (IXmlPullParser) params.getLayoutDescription(), params.getProjectKey(),
    518                     params.getScreenWidth(), params.getScreenHeight(),
    519                     resources.getThemeName(), resources.isProjectTheme(),
    520                     projectMap, frameworkMap,
    521                     (IProjectCallback) params.getProjectCallback(), logWrapper);
    522         } else {
    523             // First api with no density/dpi, and project theme boolean mixed
    524             // into the theme name.
    525 
    526             // change the string if it's a custom theme to make sure we can
    527             // differentiate them
    528             String themeName = resources.getThemeName();
    529             if (resources.isProjectTheme()) {
    530                 themeName = "*" + themeName; //$NON-NLS-1$
    531             }
    532 
    533             result = mLegacyBridge.computeLayout(
    534                     (IXmlPullParser) params.getLayoutDescription(), params.getProjectKey(),
    535                     params.getScreenWidth(), params.getScreenHeight(),
    536                     themeName,
    537                     projectMap, frameworkMap,
    538                     (IProjectCallback) params.getProjectCallback(), logWrapper);
    539         }
    540 
    541         // clean up that is not done by the ILayoutBridge itself
    542         legacyCleanUp();
    543 
    544         return convertToScene(result);
    545     }
    546 
    547     @SuppressWarnings("unchecked")
    548     private Map<String, Map<String, IResourceValue>> convertMap(
    549             Map<ResourceType, Map<String, ResourceValue>> map) {
    550         Map<String, Map<String, IResourceValue>> result =
    551             new HashMap<String, Map<String, IResourceValue>>();
    552 
    553         for (Entry<ResourceType, Map<String, ResourceValue>> entry : map.entrySet()) {
    554             // ugly case but works.
    555             result.put(entry.getKey().getName(),
    556                     (Map) entry.getValue());
    557         }
    558 
    559         return result;
    560     }
    561 
    562     /**
    563      * Converts a {@link ILayoutResult} to a {@link RenderSession}.
    564      */
    565     private RenderSession convertToScene(ILayoutResult result) {
    566 
    567         Result sceneResult;
    568         ViewInfo rootViewInfo = null;
    569 
    570         if (result.getSuccess() == ILayoutResult.SUCCESS) {
    571             sceneResult = Status.SUCCESS.createResult();
    572             ILayoutViewInfo oldRootView = result.getRootView();
    573             if (oldRootView != null) {
    574                 rootViewInfo = convertToViewInfo(oldRootView);
    575             }
    576         } else {
    577             sceneResult = Status.ERROR_UNKNOWN.createResult(result.getErrorMessage());
    578         }
    579 
    580         // create a BasicLayoutScene. This will return the given values but return the default
    581         // implementation for all method.
    582         // ADT should gracefully handle the default implementations of LayoutScene
    583         return new StaticRenderSession(sceneResult, rootViewInfo, result.getImage());
    584     }
    585 
    586     /**
    587      * Converts a {@link ILayoutViewInfo} (and its children) to a {@link ViewInfo}.
    588      */
    589     private ViewInfo convertToViewInfo(ILayoutViewInfo view) {
    590         // create the view info.
    591         ViewInfo viewInfo = new ViewInfo(view.getName(), view.getViewKey(),
    592                 view.getLeft(), view.getTop(), view.getRight(), view.getBottom());
    593 
    594         // then convert the children
    595         ILayoutViewInfo[] children = view.getChildren();
    596         if (children != null) {
    597             ArrayList<ViewInfo> convertedChildren = new ArrayList<ViewInfo>(children.length);
    598             for (ILayoutViewInfo child : children) {
    599                 convertedChildren.add(convertToViewInfo(child));
    600             }
    601             viewInfo.setChildren(convertedChildren);
    602         }
    603 
    604         return viewInfo;
    605     }
    606 
    607     /**
    608      * Post rendering clean-up that must be done here because it's not done in any layoutlib using
    609      * {@link ILayoutBridge}.
    610      */
    611     private void legacyCleanUp() {
    612         try {
    613             Class<?> looperClass = mClassLoader.loadClass("android.os.Looper"); //$NON-NLS-1$
    614             Field threadLocalField = looperClass.getField("sThreadLocal"); //$NON-NLS-1$
    615             if (threadLocalField != null) {
    616                 threadLocalField.setAccessible(true);
    617                 // get object. Field is static so no need to pass an object
    618                 ThreadLocal<?> threadLocal = (ThreadLocal<?>) threadLocalField.get(null);
    619                 if (threadLocal != null) {
    620                     threadLocal.remove();
    621                 }
    622             }
    623         } catch (Exception e) {
    624             // do nothing.
    625         }
    626     }
    627 
    628     private Result getViewParentWithReflection(Object viewObject) {
    629         // default implementation using reflection.
    630         try {
    631             if (mViewGetParentMethod == null) {
    632                 Class<?> viewClass = Class.forName("android.view.View");
    633                 mViewGetParentMethod = viewClass.getMethod("getParent");
    634             }
    635 
    636             return Status.SUCCESS.createResult(mViewGetParentMethod.invoke(viewObject));
    637         } catch (Exception e) {
    638             // Catch all for the reflection calls.
    639             return ERROR_REFLECTION.createResult(null, e);
    640         }
    641     }
    642 
    643     /**
    644      * Utility method returning the index of a given view in its parent.
    645      * @param viewObject the object for which to return the index.
    646      *
    647      * @return a {@link Result} indicating the status of the action, and if success, the index in
    648      *      the parent in {@link Result#getData()}
    649      */
    650     private Result getViewIndexReflection(Object viewObject) {
    651         // default implementation using reflection.
    652         try {
    653             Class<?> viewClass = Class.forName("android.view.View");
    654 
    655             if (mViewGetParentMethod == null) {
    656                 mViewGetParentMethod = viewClass.getMethod("getParent");
    657             }
    658 
    659             Object parentObject = mViewGetParentMethod.invoke(viewObject);
    660 
    661             if (mViewParentIndexOfChildMethod == null) {
    662                 Class<?> viewParentClass = Class.forName("android.view.ViewParent");
    663                 mViewParentIndexOfChildMethod = viewParentClass.getMethod("indexOfChild",
    664                         viewClass);
    665             }
    666 
    667             return Status.SUCCESS.createResult(
    668                     mViewParentIndexOfChildMethod.invoke(parentObject, viewObject));
    669         } catch (Exception e) {
    670             // Catch all for the reflection calls.
    671             return ERROR_REFLECTION.createResult(null, e);
    672         }
    673     }
    674 
    675     private void addExtendedViewInfo(ViewInfo info) {
    676         computeExtendedViewInfo(info);
    677 
    678         List<ViewInfo> children = info.getChildren();
    679         for (ViewInfo child : children) {
    680             addExtendedViewInfo(child);
    681         }
    682     }
    683 
    684     private void computeExtendedViewInfo(ViewInfo info) {
    685         Object viewObject = info.getViewObject();
    686         Object params = info.getLayoutParamsObject();
    687 
    688         int baseLine = getViewBaselineReflection(viewObject);
    689         int leftMargin = 0;
    690         int topMargin = 0;
    691         int rightMargin = 0;
    692         int bottomMargin = 0;
    693 
    694         try {
    695             if (mMarginLayoutParamClass == null) {
    696                 mMarginLayoutParamClass = Class.forName(
    697                         "android.view.ViewGroup$MarginLayoutParams");
    698 
    699                 mLeftMarginField = mMarginLayoutParamClass.getField("leftMargin");
    700                 mTopMarginField = mMarginLayoutParamClass.getField("topMargin");
    701                 mRightMarginField = mMarginLayoutParamClass.getField("rightMargin");
    702                 mBottomMarginField = mMarginLayoutParamClass.getField("bottomMargin");
    703             }
    704 
    705             if (mMarginLayoutParamClass.isAssignableFrom(params.getClass())) {
    706 
    707                 leftMargin = (Integer)mLeftMarginField.get(params);
    708                 topMargin = (Integer)mTopMarginField.get(params);
    709                 rightMargin = (Integer)mRightMarginField.get(params);
    710                 bottomMargin = (Integer)mBottomMarginField.get(params);
    711             }
    712 
    713         } catch (Exception e) {
    714             // just use 'unknown' value.
    715             leftMargin = Integer.MIN_VALUE;
    716             topMargin = Integer.MIN_VALUE;
    717             rightMargin = Integer.MIN_VALUE;
    718             bottomMargin = Integer.MIN_VALUE;
    719         }
    720 
    721         info.setExtendedInfo(baseLine, leftMargin, topMargin, rightMargin, bottomMargin);
    722     }
    723 
    724     /**
    725      * Utility method returning the baseline value for a given view object. This basically returns
    726      * View.getBaseline().
    727      *
    728      * @param viewObject the object for which to return the index.
    729      *
    730      * @return the baseline value or -1 if not applicable to the view object or if this layout
    731      *     library does not implement this method.
    732      */
    733     private int getViewBaselineReflection(Object viewObject) {
    734         // default implementation using reflection.
    735         try {
    736             if (mViewGetBaselineMethod == null) {
    737                 Class<?> viewClass = Class.forName("android.view.View");
    738                 mViewGetBaselineMethod = viewClass.getMethod("getBaseline");
    739             }
    740 
    741             Object result = mViewGetBaselineMethod.invoke(viewObject);
    742             if (result instanceof Integer) {
    743                 return ((Integer)result).intValue();
    744             }
    745 
    746         } catch (Exception e) {
    747             // Catch all for the reflection calls.
    748         }
    749 
    750         return Integer.MIN_VALUE;
    751     }
    752 }
    753