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.Result.Status;
     31 import com.android.ide.common.rendering.api.SessionParams;
     32 import com.android.ide.common.rendering.api.SessionParams.RenderingMode;
     33 import com.android.ide.common.rendering.api.ViewInfo;
     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.ILayoutResult.ILayoutViewInfo;
     42 import com.android.layoutlib.api.IProjectCallback;
     43 import com.android.layoutlib.api.IResourceValue;
     44 import com.android.layoutlib.api.IXmlPullParser;
     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             @Override
    471             public void warning(String message) {
    472                 log.warning(null, message, null /*data*/);
    473             }
    474 
    475             @Override
    476             public void error(Throwable t) {
    477                 log.error(null, "error!", t, null /*data*/);
    478             }
    479 
    480             @Override
    481             public void error(String message) {
    482                 log.error(null, message, null /*data*/);
    483             }
    484         };
    485 
    486 
    487         // convert the map of ResourceValue into IResourceValue. Super ugly but works.
    488 
    489         Map<String, Map<String, IResourceValue>> projectMap = convertMap(
    490                 resources.getProjectResources());
    491         Map<String, Map<String, IResourceValue>> frameworkMap = convertMap(
    492                 resources.getFrameworkResources());
    493 
    494         ILayoutResult result = null;
    495 
    496         if (apiLevel == 4) {
    497             // Final ILayoutBridge API added support for "render full height"
    498             result = mLegacyBridge.computeLayout(
    499                     (IXmlPullParser) params.getLayoutDescription(),
    500                     params.getProjectKey(),
    501                     params.getScreenWidth(), params.getScreenHeight(),
    502                     params.getRenderingMode() == RenderingMode.FULL_EXPAND ? true : false,
    503                     params.getDensity().getDpiValue(), params.getXdpi(), params.getYdpi(),
    504                     resources.getThemeName(), resources.isProjectTheme(),
    505                     projectMap, frameworkMap,
    506                     (IProjectCallback) params.getProjectCallback(),
    507                     logWrapper);
    508         } else if (apiLevel == 3) {
    509             // api 3 add density support.
    510             result = mLegacyBridge.computeLayout(
    511                     (IXmlPullParser) params.getLayoutDescription(), params.getProjectKey(),
    512                     params.getScreenWidth(), params.getScreenHeight(),
    513                     params.getDensity().getDpiValue(), params.getXdpi(), params.getYdpi(),
    514                     resources.getThemeName(), resources.isProjectTheme(),
    515                     projectMap, frameworkMap,
    516                     (IProjectCallback) params.getProjectCallback(), logWrapper);
    517         } else if (apiLevel == 2) {
    518             // api 2 added boolean for separation of project/framework theme
    519             result = mLegacyBridge.computeLayout(
    520                     (IXmlPullParser) params.getLayoutDescription(), params.getProjectKey(),
    521                     params.getScreenWidth(), params.getScreenHeight(),
    522                     resources.getThemeName(), resources.isProjectTheme(),
    523                     projectMap, frameworkMap,
    524                     (IProjectCallback) params.getProjectCallback(), logWrapper);
    525         } else {
    526             // First api with no density/dpi, and project theme boolean mixed
    527             // into the theme name.
    528 
    529             // change the string if it's a custom theme to make sure we can
    530             // differentiate them
    531             String themeName = resources.getThemeName();
    532             if (resources.isProjectTheme()) {
    533                 themeName = "*" + themeName; //$NON-NLS-1$
    534             }
    535 
    536             result = mLegacyBridge.computeLayout(
    537                     (IXmlPullParser) params.getLayoutDescription(), params.getProjectKey(),
    538                     params.getScreenWidth(), params.getScreenHeight(),
    539                     themeName,
    540                     projectMap, frameworkMap,
    541                     (IProjectCallback) params.getProjectCallback(), logWrapper);
    542         }
    543 
    544         // clean up that is not done by the ILayoutBridge itself
    545         legacyCleanUp();
    546 
    547         return convertToScene(result);
    548     }
    549 
    550     @SuppressWarnings("unchecked")
    551     private Map<String, Map<String, IResourceValue>> convertMap(
    552             Map<ResourceType, Map<String, ResourceValue>> map) {
    553         Map<String, Map<String, IResourceValue>> result =
    554             new HashMap<String, Map<String, IResourceValue>>();
    555 
    556         for (Entry<ResourceType, Map<String, ResourceValue>> entry : map.entrySet()) {
    557             // ugly case but works.
    558             result.put(entry.getKey().getName(),
    559                     (Map) entry.getValue());
    560         }
    561 
    562         return result;
    563     }
    564 
    565     /**
    566      * Converts a {@link ILayoutResult} to a {@link RenderSession}.
    567      */
    568     private RenderSession convertToScene(ILayoutResult result) {
    569 
    570         Result sceneResult;
    571         ViewInfo rootViewInfo = null;
    572 
    573         if (result.getSuccess() == ILayoutResult.SUCCESS) {
    574             sceneResult = Status.SUCCESS.createResult();
    575             ILayoutViewInfo oldRootView = result.getRootView();
    576             if (oldRootView != null) {
    577                 rootViewInfo = convertToViewInfo(oldRootView);
    578             }
    579         } else {
    580             sceneResult = Status.ERROR_UNKNOWN.createResult(result.getErrorMessage());
    581         }
    582 
    583         // create a BasicLayoutScene. This will return the given values but return the default
    584         // implementation for all method.
    585         // ADT should gracefully handle the default implementations of LayoutScene
    586         return new StaticRenderSession(sceneResult, rootViewInfo, result.getImage());
    587     }
    588 
    589     /**
    590      * Converts a {@link ILayoutViewInfo} (and its children) to a {@link ViewInfo}.
    591      */
    592     private ViewInfo convertToViewInfo(ILayoutViewInfo view) {
    593         // create the view info.
    594         ViewInfo viewInfo = new ViewInfo(view.getName(), view.getViewKey(),
    595                 view.getLeft(), view.getTop(), view.getRight(), view.getBottom());
    596 
    597         // then convert the children
    598         ILayoutViewInfo[] children = view.getChildren();
    599         if (children != null) {
    600             ArrayList<ViewInfo> convertedChildren = new ArrayList<ViewInfo>(children.length);
    601             for (ILayoutViewInfo child : children) {
    602                 convertedChildren.add(convertToViewInfo(child));
    603             }
    604             viewInfo.setChildren(convertedChildren);
    605         }
    606 
    607         return viewInfo;
    608     }
    609 
    610     /**
    611      * Post rendering clean-up that must be done here because it's not done in any layoutlib using
    612      * {@link ILayoutBridge}.
    613      */
    614     private void legacyCleanUp() {
    615         try {
    616             Class<?> looperClass = mClassLoader.loadClass("android.os.Looper"); //$NON-NLS-1$
    617             Field threadLocalField = looperClass.getField("sThreadLocal"); //$NON-NLS-1$
    618             if (threadLocalField != null) {
    619                 threadLocalField.setAccessible(true);
    620                 // get object. Field is static so no need to pass an object
    621                 ThreadLocal<?> threadLocal = (ThreadLocal<?>) threadLocalField.get(null);
    622                 if (threadLocal != null) {
    623                     threadLocal.remove();
    624                 }
    625             }
    626         } catch (Exception e) {
    627             // do nothing.
    628         }
    629     }
    630 
    631     private Result getViewParentWithReflection(Object viewObject) {
    632         // default implementation using reflection.
    633         try {
    634             if (mViewGetParentMethod == null) {
    635                 Class<?> viewClass = Class.forName("android.view.View");
    636                 mViewGetParentMethod = viewClass.getMethod("getParent");
    637             }
    638 
    639             return Status.SUCCESS.createResult(mViewGetParentMethod.invoke(viewObject));
    640         } catch (Exception e) {
    641             // Catch all for the reflection calls.
    642             return ERROR_REFLECTION.createResult(null, e);
    643         }
    644     }
    645 
    646     /**
    647      * Utility method returning the index of a given view in its parent.
    648      * @param viewObject the object for which to return the index.
    649      *
    650      * @return a {@link Result} indicating the status of the action, and if success, the index in
    651      *      the parent in {@link Result#getData()}
    652      */
    653     private Result getViewIndexReflection(Object viewObject) {
    654         // default implementation using reflection.
    655         try {
    656             Class<?> viewClass = Class.forName("android.view.View");
    657 
    658             if (mViewGetParentMethod == null) {
    659                 mViewGetParentMethod = viewClass.getMethod("getParent");
    660             }
    661 
    662             Object parentObject = mViewGetParentMethod.invoke(viewObject);
    663 
    664             if (mViewParentIndexOfChildMethod == null) {
    665                 Class<?> viewParentClass = Class.forName("android.view.ViewParent");
    666                 mViewParentIndexOfChildMethod = viewParentClass.getMethod("indexOfChild",
    667                         viewClass);
    668             }
    669 
    670             return Status.SUCCESS.createResult(
    671                     mViewParentIndexOfChildMethod.invoke(parentObject, viewObject));
    672         } catch (Exception e) {
    673             // Catch all for the reflection calls.
    674             return ERROR_REFLECTION.createResult(null, e);
    675         }
    676     }
    677 
    678     private void addExtendedViewInfo(ViewInfo info) {
    679         computeExtendedViewInfo(info);
    680 
    681         List<ViewInfo> children = info.getChildren();
    682         for (ViewInfo child : children) {
    683             addExtendedViewInfo(child);
    684         }
    685     }
    686 
    687     private void computeExtendedViewInfo(ViewInfo info) {
    688         Object viewObject = info.getViewObject();
    689         Object params = info.getLayoutParamsObject();
    690 
    691         int baseLine = getViewBaselineReflection(viewObject);
    692         int leftMargin = 0;
    693         int topMargin = 0;
    694         int rightMargin = 0;
    695         int bottomMargin = 0;
    696 
    697         try {
    698             if (mMarginLayoutParamClass == null) {
    699                 mMarginLayoutParamClass = Class.forName(
    700                         "android.view.ViewGroup$MarginLayoutParams");
    701 
    702                 mLeftMarginField = mMarginLayoutParamClass.getField("leftMargin");
    703                 mTopMarginField = mMarginLayoutParamClass.getField("topMargin");
    704                 mRightMarginField = mMarginLayoutParamClass.getField("rightMargin");
    705                 mBottomMarginField = mMarginLayoutParamClass.getField("bottomMargin");
    706             }
    707 
    708             if (mMarginLayoutParamClass.isAssignableFrom(params.getClass())) {
    709 
    710                 leftMargin = (Integer)mLeftMarginField.get(params);
    711                 topMargin = (Integer)mTopMarginField.get(params);
    712                 rightMargin = (Integer)mRightMarginField.get(params);
    713                 bottomMargin = (Integer)mBottomMarginField.get(params);
    714             }
    715 
    716         } catch (Exception e) {
    717             // just use 'unknown' value.
    718             leftMargin = Integer.MIN_VALUE;
    719             topMargin = Integer.MIN_VALUE;
    720             rightMargin = Integer.MIN_VALUE;
    721             bottomMargin = Integer.MIN_VALUE;
    722         }
    723 
    724         info.setExtendedInfo(baseLine, leftMargin, topMargin, rightMargin, bottomMargin);
    725     }
    726 
    727     /**
    728      * Utility method returning the baseline value for a given view object. This basically returns
    729      * View.getBaseline().
    730      *
    731      * @param viewObject the object for which to return the index.
    732      *
    733      * @return the baseline value or -1 if not applicable to the view object or if this layout
    734      *     library does not implement this method.
    735      */
    736     private int getViewBaselineReflection(Object viewObject) {
    737         // default implementation using reflection.
    738         try {
    739             if (mViewGetBaselineMethod == null) {
    740                 Class<?> viewClass = Class.forName("android.view.View");
    741                 mViewGetBaselineMethod = viewClass.getMethod("getBaseline");
    742             }
    743 
    744             Object result = mViewGetBaselineMethod.invoke(viewObject);
    745             if (result instanceof Integer) {
    746                 return ((Integer)result).intValue();
    747             }
    748 
    749         } catch (Exception e) {
    750             // Catch all for the reflection calls.
    751         }
    752 
    753         return Integer.MIN_VALUE;
    754     }
    755 }
    756