• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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      */
getStatus()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      */
getLoadMessage()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      */
getClassLoader()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      */
load(String layoutLibJarOsPath, ILogger log, String toolName)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      */
getApiLevel()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      */
getRevision()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      */
supports(Capability capability)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      */
init(Map<String, String> platformProperties, File fontLocation, Map<String, Map<String, Integer>> enumValueMap, LayoutLog log)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      */
dispose()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      */
createSession(SessionParams params)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      */
renderDrawable(DrawableParams params)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      */
clearCaches(Object projectKey)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      */
getViewParent(Object viewObject)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      */
getViewIndex(Object viewObject)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 
LayoutLibrary(Bridge bridge, ILayoutBridge legacyBridge, ClassLoader classLoader, LoadStatus status, String message)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      */
getLegacyApiLevel()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 
createLegacySession(SessionParams params)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")
convertMap( Map<ResourceType, Map<String, ResourceValue>> map)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      */
convertToScene(ILayoutResult result)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      */
convertToViewInfo(ILayoutViewInfo view)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      */
legacyCleanUp()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 
getViewParentWithReflection(Object viewObject)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      */
getViewIndexReflection(Object viewObject)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 
addExtendedViewInfo(ViewInfo info)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 
computeExtendedViewInfo(ViewInfo info)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      */
getViewBaselineReflection(Object viewObject)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