| /* |
| * Copyright (C) 2010 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| package com.android.ide.common.rendering; |
| |
| import static com.android.ide.common.rendering.api.Result.Status.ERROR_REFLECTION; |
| |
| import com.android.ide.common.rendering.api.Bridge; |
| import com.android.ide.common.rendering.api.Capability; |
| import com.android.ide.common.rendering.api.DrawableParams; |
| import com.android.ide.common.rendering.api.ILayoutPullParser; |
| import com.android.ide.common.rendering.api.LayoutLog; |
| import com.android.ide.common.rendering.api.RenderSession; |
| import com.android.ide.common.rendering.api.ResourceValue; |
| import com.android.ide.common.rendering.api.Result; |
| import com.android.ide.common.rendering.api.Result.Status; |
| import com.android.ide.common.rendering.api.SessionParams; |
| import com.android.ide.common.rendering.api.SessionParams.RenderingMode; |
| import com.android.ide.common.rendering.api.ViewInfo; |
| import com.android.ide.common.rendering.legacy.ILegacyPullParser; |
| import com.android.ide.common.rendering.legacy.LegacyCallback; |
| import com.android.ide.common.resources.ResourceResolver; |
| import com.android.ide.common.sdk.LoadStatus; |
| import com.android.layoutlib.api.ILayoutBridge; |
| import com.android.layoutlib.api.ILayoutLog; |
| import com.android.layoutlib.api.ILayoutResult; |
| import com.android.layoutlib.api.ILayoutResult.ILayoutViewInfo; |
| import com.android.layoutlib.api.IProjectCallback; |
| import com.android.layoutlib.api.IResourceValue; |
| import com.android.layoutlib.api.IXmlPullParser; |
| import com.android.resources.ResourceType; |
| import com.android.utils.ILogger; |
| |
| import java.awt.image.BufferedImage; |
| import java.io.File; |
| import java.lang.reflect.Constructor; |
| import java.lang.reflect.Field; |
| import java.lang.reflect.Method; |
| import java.net.URI; |
| import java.net.URL; |
| import java.net.URLClassLoader; |
| import java.util.ArrayList; |
| import java.util.HashMap; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Map.Entry; |
| |
| /** |
| * Class to use the Layout library. |
| * <p/> |
| * Use {@link #load(String, ILogger)} to load the jar file. |
| * <p/> |
| * Use the layout library with: |
| * {@link #init(String, Map)}, {@link #supports(Capability)}, {@link #createSession(SessionParams)}, |
| * {@link #dispose()}, {@link #clearCaches(Object)}. |
| * |
| * <p/> |
| * For client wanting to access both new and old (pre API level 5) layout libraries, it is |
| * important that the following interfaces be used:<br> |
| * {@link ILegacyPullParser} instead of {@link ILayoutPullParser}<br> |
| * {@link LegacyCallback} instead of {@link com.android.ide.common.rendering.api.IProjectCallback}. |
| * <p/> |
| * These interfaces will ensure that both new and older Layout libraries can be accessed. |
| */ |
| @SuppressWarnings("deprecation") |
| public class LayoutLibrary { |
| |
| public final static String CLASS_BRIDGE = "com.android.layoutlib.bridge.Bridge"; //$NON-NLS-1$ |
| |
| /** Link to the layout bridge */ |
| private final Bridge mBridge; |
| /** Link to a ILayoutBridge in case loaded an older library */ |
| private final ILayoutBridge mLegacyBridge; |
| /** Status of the layoutlib.jar loading */ |
| private final LoadStatus mStatus; |
| /** Message associated with the {@link LoadStatus}. This is mostly used when |
| * {@link #getStatus()} returns {@link LoadStatus#FAILED}. |
| */ |
| private final String mLoadMessage; |
| /** classloader used to load the jar file */ |
| private final ClassLoader mClassLoader; |
| |
| // Reflection data for older Layout Libraries. |
| private Method mViewGetParentMethod; |
| private Method mViewGetBaselineMethod; |
| private Method mViewParentIndexOfChildMethod; |
| private Class<?> mMarginLayoutParamClass; |
| private Field mLeftMarginField; |
| private Field mTopMarginField; |
| private Field mRightMarginField; |
| private Field mBottomMarginField; |
| |
| /** |
| * Returns the {@link LoadStatus} of the loading of the layoutlib jar file. |
| */ |
| public LoadStatus getStatus() { |
| return mStatus; |
| } |
| |
| /** Returns the message associated with the {@link LoadStatus}. This is mostly used when |
| * {@link #getStatus()} returns {@link LoadStatus#FAILED}. |
| */ |
| public String getLoadMessage() { |
| return mLoadMessage; |
| } |
| |
| /** |
| * Returns the classloader used to load the classes in the layoutlib jar file. |
| */ |
| public ClassLoader getClassLoader() { |
| return mClassLoader; |
| } |
| |
| /** |
| * Loads the layoutlib.jar file located at the given path and returns a {@link LayoutLibrary} |
| * object representing the result. |
| * <p/> |
| * If loading failed {@link #getStatus()} will reflect this, and {@link #getBridge()} will |
| * return null. |
| * |
| * @param layoutLibJarOsPath the path of the jar file |
| * @param log an optional log file. |
| * @return a {@link LayoutLibrary} object always. |
| */ |
| public static LayoutLibrary load(String layoutLibJarOsPath, ILogger log, String toolName) { |
| |
| LoadStatus status = LoadStatus.LOADING; |
| String message = null; |
| Bridge bridge = null; |
| ILayoutBridge legacyBridge = null; |
| ClassLoader classLoader = null; |
| |
| try { |
| // get the URL for the file. |
| File f = new File(layoutLibJarOsPath); |
| if (f.isFile() == false) { |
| if (log != null) { |
| log.error(null, "layoutlib.jar is missing!"); //$NON-NLS-1$ |
| } |
| } else { |
| URI uri = f.toURI(); |
| URL url = uri.toURL(); |
| |
| // create a class loader. Because this jar reference interfaces |
| // that are in the editors plugin, it's important to provide |
| // a parent class loader. |
| classLoader = new URLClassLoader( |
| new URL[] { url }, |
| LayoutLibrary.class.getClassLoader()); |
| |
| // load the class |
| Class<?> clazz = classLoader.loadClass(CLASS_BRIDGE); |
| if (clazz != null) { |
| // instantiate an object of the class. |
| Constructor<?> constructor = clazz.getConstructor(); |
| if (constructor != null) { |
| Object bridgeObject = constructor.newInstance(); |
| if (bridgeObject instanceof Bridge) { |
| bridge = (Bridge)bridgeObject; |
| } else if (bridgeObject instanceof ILayoutBridge) { |
| legacyBridge = (ILayoutBridge) bridgeObject; |
| } |
| } |
| } |
| |
| if (bridge == null && legacyBridge == null) { |
| status = LoadStatus.FAILED; |
| message = "Failed to load " + CLASS_BRIDGE; //$NON-NLS-1$ |
| if (log != null) { |
| log.error(null, |
| "Failed to load " + //$NON-NLS-1$ |
| CLASS_BRIDGE + |
| " from " + //$NON-NLS-1$ |
| layoutLibJarOsPath); |
| } |
| } else { |
| // mark the lib as loaded, unless it's overridden below. |
| status = LoadStatus.LOADED; |
| |
| // check the API, only if it's not a legacy bridge |
| if (bridge != null) { |
| int api = bridge.getApiLevel(); |
| if (api > Bridge.API_CURRENT) { |
| status = LoadStatus.FAILED; |
| message = String.format( |
| "This version of the rendering library is more recent than your version of %1$s. Please update %1$s", toolName); |
| } |
| } |
| } |
| } |
| } catch (Throwable t) { |
| status = LoadStatus.FAILED; |
| Throwable cause = t; |
| while (cause.getCause() != null) { |
| cause = cause.getCause(); |
| } |
| message = "Failed to load the LayoutLib: " + cause.getMessage(); |
| // log the error. |
| if (log != null) { |
| log.error(t, message); |
| } |
| } |
| |
| return new LayoutLibrary(bridge, legacyBridge, classLoader, status, message); |
| } |
| |
| // ------ Layout Lib API proxy |
| |
| /** |
| * Returns the API level of the layout library. |
| */ |
| public int getApiLevel() { |
| if (mBridge != null) { |
| return mBridge.getApiLevel(); |
| } |
| |
| if (mLegacyBridge != null) { |
| return getLegacyApiLevel(); |
| } |
| |
| return 0; |
| } |
| |
| /** |
| * Returns the revision of the library inside a given (layoutlib) API level. |
| * The true version number of the library is {@link #getApiLevel()}.{@link #getRevision()} |
| */ |
| public int getRevision() { |
| if (mBridge != null) { |
| return mBridge.getRevision(); |
| } |
| |
| return 0; |
| } |
| |
| /** |
| * Returns whether the LayoutLibrary supports a given {@link Capability}. |
| * @return true if it supports it. |
| * |
| * @see Bridge#getCapabilities() |
| * |
| */ |
| public boolean supports(Capability capability) { |
| if (mBridge != null) { |
| return mBridge.getCapabilities().contains(capability); |
| } |
| |
| if (mLegacyBridge != null) { |
| switch (capability) { |
| case UNBOUND_RENDERING: |
| // legacy stops at 4. 5 is new API. |
| return getLegacyApiLevel() == 4; |
| } |
| } |
| |
| return false; |
| } |
| |
| /** |
| * Initializes the Layout Library object. This must be called before any other action is taken |
| * on the instance. |
| * |
| * @param platformProperties The build properties for the platform. |
| * @param fontLocation the location of the fonts in the SDK target. |
| * @param enumValueMap map attrName => { map enumFlagName => Integer value }. This is typically |
| * read from attrs.xml in the SDK target. |
| * @param log a {@link LayoutLog} object. Can be null. |
| * @return true if success. |
| * |
| * @see Bridge#init(String, Map) |
| */ |
| public boolean init(Map<String, String> platformProperties, |
| File fontLocation, |
| Map<String, Map<String, Integer>> enumValueMap, |
| LayoutLog log) { |
| if (mBridge != null) { |
| return mBridge.init(platformProperties, fontLocation, enumValueMap, log); |
| } else if (mLegacyBridge != null) { |
| return mLegacyBridge.init(fontLocation.getAbsolutePath(), enumValueMap); |
| } |
| |
| return false; |
| } |
| |
| /** |
| * Prepares the layoutlib to unloaded. |
| * |
| * @see Bridge#dispose() |
| */ |
| public boolean dispose() { |
| if (mBridge != null) { |
| return mBridge.dispose(); |
| } |
| |
| return true; |
| } |
| |
| /** |
| * Starts a layout session by inflating and rendering it. The method returns a |
| * {@link RenderSession} on which further actions can be taken. |
| * <p/> |
| * Before taking further actions on the scene, it is recommended to use |
| * {@link #supports(Capability)} to check what the scene can do. |
| * |
| * @return a new {@link ILayoutScene} object that contains the result of the scene creation and |
| * first rendering or null if {@link #getStatus()} doesn't return {@link LoadStatus#LOADED}. |
| * |
| * @see Bridge#createSession(SessionParams) |
| */ |
| public RenderSession createSession(SessionParams params) { |
| if (mBridge != null) { |
| RenderSession session = mBridge.createSession(params); |
| if (params.getExtendedViewInfoMode() && |
| mBridge.getCapabilities().contains(Capability.EXTENDED_VIEWINFO) == false) { |
| // Extended view info was requested but the layoutlib does not support it. |
| // Add it manually. |
| List<ViewInfo> infoList = session.getRootViews(); |
| if (infoList != null) { |
| for (ViewInfo info : infoList) { |
| addExtendedViewInfo(info); |
| } |
| } |
| } |
| |
| return session; |
| } else if (mLegacyBridge != null) { |
| return createLegacySession(params); |
| } |
| |
| return null; |
| } |
| |
| /** |
| * Renders a Drawable. If the rendering is successful, the result image is accessible through |
| * {@link Result#getData()}. It is of type {@link BufferedImage} |
| * @param params the rendering parameters. |
| * @return the result of the action. |
| */ |
| public Result renderDrawable(DrawableParams params) { |
| if (mBridge != null) { |
| return mBridge.renderDrawable(params); |
| } |
| |
| return Status.NOT_IMPLEMENTED.createResult(); |
| } |
| |
| /** |
| * Clears the resource cache for a specific project. |
| * <p/>This cache contains bitmaps and nine patches that are loaded from the disk and reused |
| * until this method is called. |
| * <p/>The cache is not configuration dependent and should only be cleared when a |
| * resource changes (at this time only bitmaps and 9 patches go into the cache). |
| * |
| * @param projectKey the key for the project. |
| * |
| * @see Bridge#clearCaches(Object) |
| */ |
| public void clearCaches(Object projectKey) { |
| if (mBridge != null) { |
| mBridge.clearCaches(projectKey); |
| } else if (mLegacyBridge != null) { |
| mLegacyBridge.clearCaches(projectKey); |
| } |
| } |
| |
| /** |
| * Utility method returning the parent of a given view object. |
| * |
| * @param viewObject the object for which to return the parent. |
| * |
| * @return a {@link Result} indicating the status of the action, and if success, the parent |
| * object in {@link Result#getData()} |
| */ |
| public Result getViewParent(Object viewObject) { |
| if (mBridge != null) { |
| Result r = mBridge.getViewParent(viewObject); |
| if (r.isSuccess()) { |
| return r; |
| } |
| } |
| |
| return getViewParentWithReflection(viewObject); |
| } |
| |
| /** |
| * Utility method returning the index of a given view in its parent. |
| * @param viewObject the object for which to return the index. |
| * |
| * @return a {@link Result} indicating the status of the action, and if success, the index in |
| * the parent in {@link Result#getData()} |
| */ |
| public Result getViewIndex(Object viewObject) { |
| if (mBridge != null) { |
| Result r = mBridge.getViewIndex(viewObject); |
| if (r.isSuccess()) { |
| return r; |
| } |
| } |
| |
| return getViewIndexReflection(viewObject); |
| } |
| |
| // ------ Implementation |
| |
| private LayoutLibrary(Bridge bridge, ILayoutBridge legacyBridge, ClassLoader classLoader, |
| LoadStatus status, String message) { |
| mBridge = bridge; |
| mLegacyBridge = legacyBridge; |
| mClassLoader = classLoader; |
| mStatus = status; |
| mLoadMessage = message; |
| } |
| |
| /** |
| * Returns the API level of the legacy bridge. |
| * <p/> |
| * This handles the case where ILayoutBridge does not have a {@link ILayoutBridge#getApiLevel()} |
| * (at API level 1). |
| * <p/> |
| * {@link ILayoutBridge#getApiLevel()} should never called directly. |
| * |
| * @return the api level of {@link #mLegacyBridge}. |
| */ |
| private int getLegacyApiLevel() { |
| int apiLevel = 1; |
| try { |
| apiLevel = mLegacyBridge.getApiLevel(); |
| } catch (AbstractMethodError e) { |
| // the first version of the api did not have this method |
| // so this is 1 |
| } |
| |
| return apiLevel; |
| } |
| |
| private RenderSession createLegacySession(SessionParams params) { |
| if (params.getLayoutDescription() instanceof IXmlPullParser == false) { |
| throw new IllegalArgumentException("Parser must be of type ILegacyPullParser"); |
| } |
| if (params.getProjectCallback() instanceof |
| com.android.layoutlib.api.IProjectCallback == false) { |
| throw new IllegalArgumentException("Project callback must be of type ILegacyCallback"); |
| } |
| |
| if (params.getResources() instanceof ResourceResolver == false) { |
| throw new IllegalArgumentException("RenderResources object must be of type ResourceResolver"); |
| } |
| |
| ResourceResolver resources = (ResourceResolver) params.getResources(); |
| |
| int apiLevel = getLegacyApiLevel(); |
| |
| // create a log wrapper since the older api requires a ILayoutLog |
| final LayoutLog log = params.getLog(); |
| ILayoutLog logWrapper = new ILayoutLog() { |
| |
| @Override |
| public void warning(String message) { |
| log.warning(null, message, null /*data*/); |
| } |
| |
| @Override |
| public void error(Throwable t) { |
| log.error(null, "error!", t, null /*data*/); |
| } |
| |
| @Override |
| public void error(String message) { |
| log.error(null, message, null /*data*/); |
| } |
| }; |
| |
| |
| // convert the map of ResourceValue into IResourceValue. Super ugly but works. |
| |
| Map<String, Map<String, IResourceValue>> projectMap = convertMap( |
| resources.getProjectResources()); |
| Map<String, Map<String, IResourceValue>> frameworkMap = convertMap( |
| resources.getFrameworkResources()); |
| |
| ILayoutResult result = null; |
| |
| if (apiLevel == 4) { |
| // Final ILayoutBridge API added support for "render full height" |
| result = mLegacyBridge.computeLayout( |
| (IXmlPullParser) params.getLayoutDescription(), |
| params.getProjectKey(), |
| params.getScreenWidth(), params.getScreenHeight(), |
| params.getRenderingMode() == RenderingMode.FULL_EXPAND ? true : false, |
| params.getDensity().getDpiValue(), params.getXdpi(), params.getYdpi(), |
| resources.getThemeName(), resources.isProjectTheme(), |
| projectMap, frameworkMap, |
| (IProjectCallback) params.getProjectCallback(), |
| logWrapper); |
| } else if (apiLevel == 3) { |
| // api 3 add density support. |
| result = mLegacyBridge.computeLayout( |
| (IXmlPullParser) params.getLayoutDescription(), params.getProjectKey(), |
| params.getScreenWidth(), params.getScreenHeight(), |
| params.getDensity().getDpiValue(), params.getXdpi(), params.getYdpi(), |
| resources.getThemeName(), resources.isProjectTheme(), |
| projectMap, frameworkMap, |
| (IProjectCallback) params.getProjectCallback(), logWrapper); |
| } else if (apiLevel == 2) { |
| // api 2 added boolean for separation of project/framework theme |
| result = mLegacyBridge.computeLayout( |
| (IXmlPullParser) params.getLayoutDescription(), params.getProjectKey(), |
| params.getScreenWidth(), params.getScreenHeight(), |
| resources.getThemeName(), resources.isProjectTheme(), |
| projectMap, frameworkMap, |
| (IProjectCallback) params.getProjectCallback(), logWrapper); |
| } else { |
| // First api with no density/dpi, and project theme boolean mixed |
| // into the theme name. |
| |
| // change the string if it's a custom theme to make sure we can |
| // differentiate them |
| String themeName = resources.getThemeName(); |
| if (resources.isProjectTheme()) { |
| themeName = "*" + themeName; //$NON-NLS-1$ |
| } |
| |
| result = mLegacyBridge.computeLayout( |
| (IXmlPullParser) params.getLayoutDescription(), params.getProjectKey(), |
| params.getScreenWidth(), params.getScreenHeight(), |
| themeName, |
| projectMap, frameworkMap, |
| (IProjectCallback) params.getProjectCallback(), logWrapper); |
| } |
| |
| // clean up that is not done by the ILayoutBridge itself |
| legacyCleanUp(); |
| |
| return convertToScene(result); |
| } |
| |
| @SuppressWarnings("unchecked") |
| private Map<String, Map<String, IResourceValue>> convertMap( |
| Map<ResourceType, Map<String, ResourceValue>> map) { |
| Map<String, Map<String, IResourceValue>> result = |
| new HashMap<String, Map<String, IResourceValue>>(); |
| |
| for (Entry<ResourceType, Map<String, ResourceValue>> entry : map.entrySet()) { |
| // ugly case but works. |
| result.put(entry.getKey().getName(), |
| (Map) entry.getValue()); |
| } |
| |
| return result; |
| } |
| |
| /** |
| * Converts a {@link ILayoutResult} to a {@link RenderSession}. |
| */ |
| private RenderSession convertToScene(ILayoutResult result) { |
| |
| Result sceneResult; |
| ViewInfo rootViewInfo = null; |
| |
| if (result.getSuccess() == ILayoutResult.SUCCESS) { |
| sceneResult = Status.SUCCESS.createResult(); |
| ILayoutViewInfo oldRootView = result.getRootView(); |
| if (oldRootView != null) { |
| rootViewInfo = convertToViewInfo(oldRootView); |
| } |
| } else { |
| sceneResult = Status.ERROR_UNKNOWN.createResult(result.getErrorMessage()); |
| } |
| |
| // create a BasicLayoutScene. This will return the given values but return the default |
| // implementation for all method. |
| // ADT should gracefully handle the default implementations of LayoutScene |
| return new StaticRenderSession(sceneResult, rootViewInfo, result.getImage()); |
| } |
| |
| /** |
| * Converts a {@link ILayoutViewInfo} (and its children) to a {@link ViewInfo}. |
| */ |
| private ViewInfo convertToViewInfo(ILayoutViewInfo view) { |
| // create the view info. |
| ViewInfo viewInfo = new ViewInfo(view.getName(), view.getViewKey(), |
| view.getLeft(), view.getTop(), view.getRight(), view.getBottom()); |
| |
| // then convert the children |
| ILayoutViewInfo[] children = view.getChildren(); |
| if (children != null) { |
| ArrayList<ViewInfo> convertedChildren = new ArrayList<ViewInfo>(children.length); |
| for (ILayoutViewInfo child : children) { |
| convertedChildren.add(convertToViewInfo(child)); |
| } |
| viewInfo.setChildren(convertedChildren); |
| } |
| |
| return viewInfo; |
| } |
| |
| /** |
| * Post rendering clean-up that must be done here because it's not done in any layoutlib using |
| * {@link ILayoutBridge}. |
| */ |
| private void legacyCleanUp() { |
| try { |
| Class<?> looperClass = mClassLoader.loadClass("android.os.Looper"); //$NON-NLS-1$ |
| Field threadLocalField = looperClass.getField("sThreadLocal"); //$NON-NLS-1$ |
| if (threadLocalField != null) { |
| threadLocalField.setAccessible(true); |
| // get object. Field is static so no need to pass an object |
| ThreadLocal<?> threadLocal = (ThreadLocal<?>) threadLocalField.get(null); |
| if (threadLocal != null) { |
| threadLocal.remove(); |
| } |
| } |
| } catch (Exception e) { |
| // do nothing. |
| } |
| } |
| |
| private Result getViewParentWithReflection(Object viewObject) { |
| // default implementation using reflection. |
| try { |
| if (mViewGetParentMethod == null) { |
| Class<?> viewClass = Class.forName("android.view.View"); |
| mViewGetParentMethod = viewClass.getMethod("getParent"); |
| } |
| |
| return Status.SUCCESS.createResult(mViewGetParentMethod.invoke(viewObject)); |
| } catch (Exception e) { |
| // Catch all for the reflection calls. |
| return ERROR_REFLECTION.createResult(null, e); |
| } |
| } |
| |
| /** |
| * Utility method returning the index of a given view in its parent. |
| * @param viewObject the object for which to return the index. |
| * |
| * @return a {@link Result} indicating the status of the action, and if success, the index in |
| * the parent in {@link Result#getData()} |
| */ |
| private Result getViewIndexReflection(Object viewObject) { |
| // default implementation using reflection. |
| try { |
| Class<?> viewClass = Class.forName("android.view.View"); |
| |
| if (mViewGetParentMethod == null) { |
| mViewGetParentMethod = viewClass.getMethod("getParent"); |
| } |
| |
| Object parentObject = mViewGetParentMethod.invoke(viewObject); |
| |
| if (mViewParentIndexOfChildMethod == null) { |
| Class<?> viewParentClass = Class.forName("android.view.ViewParent"); |
| mViewParentIndexOfChildMethod = viewParentClass.getMethod("indexOfChild", |
| viewClass); |
| } |
| |
| return Status.SUCCESS.createResult( |
| mViewParentIndexOfChildMethod.invoke(parentObject, viewObject)); |
| } catch (Exception e) { |
| // Catch all for the reflection calls. |
| return ERROR_REFLECTION.createResult(null, e); |
| } |
| } |
| |
| private void addExtendedViewInfo(ViewInfo info) { |
| computeExtendedViewInfo(info); |
| |
| List<ViewInfo> children = info.getChildren(); |
| for (ViewInfo child : children) { |
| addExtendedViewInfo(child); |
| } |
| } |
| |
| private void computeExtendedViewInfo(ViewInfo info) { |
| Object viewObject = info.getViewObject(); |
| Object params = info.getLayoutParamsObject(); |
| |
| int baseLine = getViewBaselineReflection(viewObject); |
| int leftMargin = 0; |
| int topMargin = 0; |
| int rightMargin = 0; |
| int bottomMargin = 0; |
| |
| try { |
| if (mMarginLayoutParamClass == null) { |
| mMarginLayoutParamClass = Class.forName( |
| "android.view.ViewGroup$MarginLayoutParams"); |
| |
| mLeftMarginField = mMarginLayoutParamClass.getField("leftMargin"); |
| mTopMarginField = mMarginLayoutParamClass.getField("topMargin"); |
| mRightMarginField = mMarginLayoutParamClass.getField("rightMargin"); |
| mBottomMarginField = mMarginLayoutParamClass.getField("bottomMargin"); |
| } |
| |
| if (mMarginLayoutParamClass.isAssignableFrom(params.getClass())) { |
| |
| leftMargin = (Integer)mLeftMarginField.get(params); |
| topMargin = (Integer)mTopMarginField.get(params); |
| rightMargin = (Integer)mRightMarginField.get(params); |
| bottomMargin = (Integer)mBottomMarginField.get(params); |
| } |
| |
| } catch (Exception e) { |
| // just use 'unknown' value. |
| leftMargin = Integer.MIN_VALUE; |
| topMargin = Integer.MIN_VALUE; |
| rightMargin = Integer.MIN_VALUE; |
| bottomMargin = Integer.MIN_VALUE; |
| } |
| |
| info.setExtendedInfo(baseLine, leftMargin, topMargin, rightMargin, bottomMargin); |
| } |
| |
| /** |
| * Utility method returning the baseline value for a given view object. This basically returns |
| * View.getBaseline(). |
| * |
| * @param viewObject the object for which to return the index. |
| * |
| * @return the baseline value or -1 if not applicable to the view object or if this layout |
| * library does not implement this method. |
| */ |
| private int getViewBaselineReflection(Object viewObject) { |
| // default implementation using reflection. |
| try { |
| if (mViewGetBaselineMethod == null) { |
| Class<?> viewClass = Class.forName("android.view.View"); |
| mViewGetBaselineMethod = viewClass.getMethod("getBaseline"); |
| } |
| |
| Object result = mViewGetBaselineMethod.invoke(viewObject); |
| if (result instanceof Integer) { |
| return ((Integer)result).intValue(); |
| } |
| |
| } catch (Exception e) { |
| // Catch all for the reflection calls. |
| } |
| |
| return Integer.MIN_VALUE; |
| } |
| } |