• 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.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      */
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             @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")
convertMap( Map<ResourceType, Map<String, ResourceValue>> map)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      */
convertToScene(ILayoutResult result)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      */
convertToViewInfo(ILayoutViewInfo view)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      */
legacyCleanUp()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 
getViewParentWithReflection(Object viewObject)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      */
getViewIndexReflection(Object viewObject)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 
addExtendedViewInfo(ViewInfo info)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 
computeExtendedViewInfo(ViewInfo info)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      */
getViewBaselineReflection(Object viewObject)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