summaryrefslogtreecommitdiffstats
path: root/tools
diff options
context:
space:
mode:
Diffstat (limited to 'tools')
-rw-r--r--tools/layoutlib/bridge/src/com/android/layoutlib/bridge/Bridge.java254
1 files changed, 180 insertions, 74 deletions
diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/Bridge.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/Bridge.java
index 47a7ec0..d0a1c46 100644
--- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/Bridge.java
+++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/Bridge.java
@@ -55,6 +55,8 @@ import android.view.View.AttachInfo;
import android.view.View.MeasureSpec;
import android.view.WindowManager.LayoutParams;
import android.widget.FrameLayout;
+import android.widget.TabHost;
+import android.widget.TabWidget;
import java.lang.ref.SoftReference;
import java.lang.reflect.Field;
@@ -69,10 +71,10 @@ import java.util.Map;
* {@link #computeLayout(IXmlPullParser, Object, int, int, String, boolean, Map, Map, IProjectCallback, ILayoutLog)}.
*/
public final class Bridge implements ILayoutBridge {
-
+
private static final int DEFAULT_TITLE_BAR_HEIGHT = 25;
private static final int DEFAULT_STATUS_BAR_HEIGHT = 25;
-
+
public static class StaticMethodNotImplementedException extends RuntimeException {
private static final long serialVersionUID = 1L;
@@ -82,19 +84,20 @@ public final class Bridge implements ILayoutBridge {
}
/**
- * Maps from id to resource name/type.
+ * Maps from id to resource name/type. This is for android.R only.
*/
private final static Map<Integer, String[]> sRMap = new HashMap<Integer, String[]>();
/**
- * Same as sRMap except for int[] instead of int resources.
+ * Same as sRMap except for int[] instead of int resources. This is for android.R only.
*/
private final static Map<int[], String> sRArrayMap = new HashMap<int[], String>();
/**
- * Reverse map compared to sRMap, resource type -> (resource name -> id)
+ * Reverse map compared to sRMap, resource type -> (resource name -> id).
+ * This is for android.R only.
*/
private final static Map<String, Map<String, Integer>> sRFullMap =
new HashMap<String, Map<String,Integer>>();
-
+
private final static Map<Object, Map<String, SoftReference<Bitmap>>> sProjectBitmapCache =
new HashMap<Object, Map<String, SoftReference<Bitmap>>>();
private final static Map<Object, Map<String, SoftReference<NinePatch>>> sProject9PatchCache =
@@ -104,7 +107,7 @@ public final class Bridge implements ILayoutBridge {
new HashMap<String, SoftReference<Bitmap>>();
private final static Map<String, SoftReference<NinePatch>> sFramework9PatchCache =
new HashMap<String, SoftReference<NinePatch>>();
-
+
private static Map<String, Map<String, Integer>> sEnumValueMap;
/**
@@ -156,14 +159,14 @@ public final class Bridge implements ILayoutBridge {
return sinit(fontOsLocation, enumValueMap);
}
-
+
private static synchronized boolean sinit(String fontOsLocation,
Map<String, Map<String, Integer>> enumValueMap) {
// When DEBUG_LAYOUT is set and is not 0 or false, setup a default listener
// on static (native) methods which prints the signature on the console and
// throws an exception.
- // This is useful when testing the rendering in ADT to identify static native
+ // This is useful when testing the rendering in ADT to identify static native
// methods that are ignored -- layoutlib_create makes them returns 0/false/null
// which is generally OK yet might be a problem, so this is how you'd find out.
//
@@ -214,7 +217,7 @@ public final class Bridge implements ILayoutBridge {
} else {
return false;
}
-
+
sEnumValueMap = enumValueMap;
// now parse com.android.internal.R (and only this one as android.R is a subset of
@@ -226,13 +229,13 @@ public final class Bridge implements ILayoutBridge {
// int[] does not implement equals/hashCode, and if the parsing used a different class
// loader for the R class, this would NOT work.
Class<?> r = com.android.internal.R.class;
-
+
for (Class<?> inner : r.getDeclaredClasses()) {
String resType = inner.getSimpleName();
Map<String, Integer> fullMap = new HashMap<String, Integer>();
sRFullMap.put(resType, fullMap);
-
+
for (Field f : inner.getDeclaredFields()) {
// only process static final fields. Since the final attribute may have
// been altered by layoutlib_create, we only check static
@@ -243,7 +246,7 @@ public final class Bridge implements ILayoutBridge {
// if the object is an int[] we put it in sRArrayMap
sRArrayMap.put((int[]) f.get(null), f.getName());
} else if (type == int.class) {
- Integer value = (Integer) f.get(null);
+ Integer value = (Integer) f.get(null);
sRMap.put(value, new String[] { f.getName(), resType });
fullMap.put(f.getName(), value);
} else {
@@ -281,7 +284,7 @@ public final class Bridge implements ILayoutBridge {
themeName = themeName.substring(1);
isProjectTheme = true;
}
-
+
return computeLayout(layoutDescription, projectKey,
screenWidth, screenHeight, DisplayMetrics.DEFAULT_DENSITY,
DisplayMetrics.DEFAULT_DENSITY, DisplayMetrics.DEFAULT_DENSITY,
@@ -294,6 +297,7 @@ public final class Bridge implements ILayoutBridge {
* (non-Javadoc)
* @see com.android.layoutlib.api.ILayoutBridge#computeLayout(com.android.layoutlib.api.IXmlPullParser, java.lang.Object, int, int, java.lang.String, boolean, java.util.Map, java.util.Map, com.android.layoutlib.api.IProjectCallback, com.android.layoutlib.api.ILayoutLog)
*/
+ @Deprecated
public ILayoutResult computeLayout(IXmlPullParser layoutDescription, Object projectKey,
int screenWidth, int screenHeight, String themeName, boolean isProjectTheme,
Map<String, Map<String, IResourceValue>> projectResources,
@@ -319,7 +323,7 @@ public final class Bridge implements ILayoutBridge {
if (logger == null) {
logger = sDefaultLogger;
}
-
+
synchronized (sDefaultLogger) {
sLogger = logger;
}
@@ -327,12 +331,12 @@ public final class Bridge implements ILayoutBridge {
// find the current theme and compute the style inheritance map
Map<IStyleResourceValue, IStyleResourceValue> styleParentMap =
new HashMap<IStyleResourceValue, IStyleResourceValue>();
-
+
IStyleResourceValue currentTheme = computeStyleMaps(themeName, isProjectTheme,
projectResources.get(BridgeConstants.RES_STYLE),
frameworkResources.get(BridgeConstants.RES_STYLE), styleParentMap);
-
- BridgeContext context = null;
+
+ BridgeContext context = null;
try {
// setup the display Metrics.
DisplayMetrics metrics = new DisplayMetrics();
@@ -347,29 +351,32 @@ public final class Bridge implements ILayoutBridge {
frameworkResources, styleParentMap, customViewLoader, logger);
BridgeInflater inflater = new BridgeInflater(context, customViewLoader);
context.setBridgeInflater(inflater);
-
+
IResourceValue windowBackground = null;
int screenOffset = 0;
if (currentTheme != null) {
windowBackground = context.findItemInStyle(currentTheme, "windowBackground");
windowBackground = context.resolveResValue(windowBackground);
-
+
screenOffset = getScreenOffset(currentTheme, context);
}
-
+
// we need to make sure the Looper has been initialized for this thread.
// this is required for View that creates Handler objects.
if (Looper.myLooper() == null) {
Looper.prepare();
}
-
+
BridgeXmlBlockParser parser = new BridgeXmlBlockParser(layoutDescription,
context, false /* platformResourceFlag */);
-
+
ViewGroup root = new FrameLayout(context);
-
+
View view = inflater.inflate(parser, root);
-
+
+ // post-inflate process. For now this supports TabHost/TabWidget
+ postInflateProcess(view, customViewLoader);
+
// set the AttachInfo on the root view.
AttachInfo info = new AttachInfo(new WindowSession(), new Window(),
new Handler(), null);
@@ -392,16 +399,19 @@ public final class Bridge implements ILayoutBridge {
// measure the views
view.measure(w_spec, h_spec);
view.layout(0, screenOffset, screenWidth, screenHeight);
-
+
// draw them
BridgeCanvas canvas = new BridgeCanvas(screenWidth, screenHeight - screenOffset,
logger);
-
+
root.draw(canvas);
canvas.dispose();
-
+
return new LayoutResult(visit(((ViewGroup)view).getChildAt(0), context),
canvas.getImage());
+ } catch (PostInflateException e) {
+ return new LayoutResult(ILayoutResult.ERROR, "Error during post inflation process:\n"
+ + e.getMessage());
} catch (Throwable e) {
// get the real cause of the exception.
Throwable t = e;
@@ -419,7 +429,7 @@ public final class Bridge implements ILayoutBridge {
// Make sure to remove static references, otherwise we could not unload the lib
BridgeResources.clearSystem();
BridgeAssetManager.clearSystem();
-
+
// Remove the global logger
synchronized (sDefaultLogger) {
sLogger = sDefaultLogger;
@@ -437,18 +447,18 @@ public final class Bridge implements ILayoutBridge {
sProject9PatchCache.remove(projectKey);
}
}
-
+
/**
* Returns details of a framework resource from its integer value.
* @param value the integer value
* @return an array of 2 strings containing the resource name and type, or null if the id
- * does not match any resource.
+ * does not match any resource.
*/
public static String[] resolveResourceValue(int value) {
return sRMap.get(value);
-
+
}
-
+
/**
* Returns the name of a framework resource whose value is an int array.
* @param array
@@ -456,7 +466,7 @@ public final class Bridge implements ILayoutBridge {
public static String resolveResourceValue(int[] array) {
return sRArrayMap.get(array);
}
-
+
/**
* Returns the integer id of a framework resource, from a given resource type and resource name.
* @param type the type of the resource
@@ -468,15 +478,15 @@ public final class Bridge implements ILayoutBridge {
if (map != null) {
return map.get(name);
}
-
+
return null;
}
-
+
static Map<String, Integer> getEnumValues(String attributeName) {
if (sEnumValueMap != null) {
return sEnumValueMap.get(attributeName);
}
-
+
return null;
}
@@ -507,13 +517,13 @@ public final class Bridge implements ILayoutBridge {
return result;
}
-
+
/**
* Compute style information from the given list of style for the project and framework.
* @param themeName the name of the current theme. In order to differentiate project and
* platform themes sharing the same name, all project themes must be prepended with
* a '*' character.
- * @param isProjectTheme Is this a project theme
+ * @param isProjectTheme Is this a project theme
* @param inProjectStyleMap the project style map
* @param inFrameworkStyleMap the framework style map
* @param outInheritanceMap the map of style inheritance. This is filled by the method
@@ -523,23 +533,23 @@ public final class Bridge implements ILayoutBridge {
String themeName, boolean isProjectTheme, Map<String,
IResourceValue> inProjectStyleMap, Map<String, IResourceValue> inFrameworkStyleMap,
Map<IStyleResourceValue, IStyleResourceValue> outInheritanceMap) {
-
+
if (inProjectStyleMap != null && inFrameworkStyleMap != null) {
// first, get the theme
IResourceValue theme = null;
-
+
// project theme names have been prepended with a *
if (isProjectTheme) {
theme = inProjectStyleMap.get(themeName);
} else {
theme = inFrameworkStyleMap.get(themeName);
}
-
+
if (theme instanceof IStyleResourceValue) {
// compute the inheritance map for both the project and framework styles
computeStyleInheritance(inProjectStyleMap.values(), inProjectStyleMap,
inFrameworkStyleMap, outInheritanceMap);
-
+
// Compute the style inheritance for the framework styles/themes.
// Since, for those, the style parent values do not contain 'android:'
// we want to force looking in the framework style only to avoid using
@@ -547,11 +557,11 @@ public final class Bridge implements ILayoutBridge {
// To do this, we pass null in lieu of the project style map.
computeStyleInheritance(inFrameworkStyleMap.values(), null /*inProjectStyleMap */,
inFrameworkStyleMap, outInheritanceMap);
-
+
return (IStyleResourceValue)theme;
}
}
-
+
return null;
}
@@ -573,7 +583,7 @@ public final class Bridge implements ILayoutBridge {
// first look for a specified parent.
String parentName = style.getParentStyle();
-
+
// no specified parent? try to infer it from the name of the style.
if (parentName == null) {
parentName = getParentName(value.getName());
@@ -581,7 +591,7 @@ public final class Bridge implements ILayoutBridge {
if (parentName != null) {
parentStyle = getStyle(parentName, inProjectStyleMap, inFrameworkStyleMap);
-
+
if (parentStyle != null) {
outInheritanceMap.put(style, parentStyle);
}
@@ -589,7 +599,7 @@ public final class Bridge implements ILayoutBridge {
}
}
}
-
+
/**
* Searches for and returns the {@link IStyleResourceValue} from a given name.
* <p/>The format of the name can be:
@@ -607,27 +617,27 @@ public final class Bridge implements ILayoutBridge {
Map<String, IResourceValue> inProjectStyleMap,
Map<String, IResourceValue> inFrameworkStyleMap) {
boolean frameworkOnly = false;
-
+
String name = parentName;
-
+
// remove the useless @ if it's there
if (name.startsWith(BridgeConstants.PREFIX_RESOURCE_REF)) {
name = name.substring(BridgeConstants.PREFIX_RESOURCE_REF.length());
}
-
+
// check for framework identifier.
if (name.startsWith(BridgeConstants.PREFIX_ANDROID)) {
frameworkOnly = true;
name = name.substring(BridgeConstants.PREFIX_ANDROID.length());
}
-
+
// at this point we could have the format style/<name>. we want only the name
if (name.startsWith(BridgeConstants.REFERENCE_STYLE)) {
name = name.substring(BridgeConstants.REFERENCE_STYLE.length());
}
IResourceValue parent = null;
-
+
// if allowed, search in the project resources.
if (frameworkOnly == false && inProjectStyleMap != null) {
parent = inProjectStyleMap.get(name);
@@ -637,17 +647,17 @@ public final class Bridge implements ILayoutBridge {
if (parent == null) {
parent = inFrameworkStyleMap.get(name);
}
-
+
// make sure the result is the proper class type and return it.
if (parent instanceof IStyleResourceValue) {
return (IStyleResourceValue)parent;
}
-
+
sLogger.error(String.format("Unable to resolve parent style name: ", parentName));
-
+
return null;
}
-
+
/**
* Computes the name of the parent style, or <code>null</code> if the style is a root style.
*/
@@ -656,10 +666,10 @@ public final class Bridge implements ILayoutBridge {
if (index != -1) {
return styleName.substring(0, index);
}
-
+
return null;
}
-
+
/**
* Returns the top screen offset. This depends on whether the current theme defines the user
* of the title and status bars.
@@ -670,7 +680,7 @@ public final class Bridge implements ILayoutBridge {
// get the title bar flag from the current theme.
IResourceValue value = context.findItemInStyle(currentTheme, "windowNoTitle");
-
+
// because it may reference something else, we resolve it.
value = context.resolveResValue(value);
@@ -679,10 +689,10 @@ public final class Bridge implements ILayoutBridge {
XmlUtils.convertValueToBoolean(value.getValue(), false /* defValue */) == false) {
// get value from the theme.
value = context.findItemInStyle(currentTheme, "windowTitleSize");
-
+
// resolve it
value = context.resolveResValue(value);
-
+
// default value
offset = DEFAULT_TITLE_BAR_HEIGHT;
@@ -690,17 +700,17 @@ public final class Bridge implements ILayoutBridge {
if (value != null) {
TypedValue typedValue = ResourceHelper.getValue(value.getValue());
if (typedValue != null) {
- offset = (int)typedValue.getDimension(context.getResources().mMetrics);
+ offset = (int)typedValue.getDimension(context.getResources().mMetrics);
}
}
}
-
+
// get the fullscreen flag from the current theme.
value = context.findItemInStyle(currentTheme, "windowFullscreen");
-
+
// because it may reference something else, we resolve it.
value = context.resolveResValue(value);
-
+
if (value == null || value.getValue() == null ||
XmlUtils.convertValueToBoolean(value.getValue(), false /* defValue */) == false) {
// FIXME: Right now this is hard-coded in the platform, but once there's a constant, we'll need to use it.
@@ -711,6 +721,94 @@ public final class Bridge implements ILayoutBridge {
}
/**
+ * Post process on a view hierachy that was just inflated.
+ * <p/>At the moment this only support TabHost: If {@link TabHost} is detected, look for the
+ * {@link TabWidget}, and the corresponding {@link FrameLayout} and make new tabs automatically
+ * based on the content of the {@link FrameLayout}.
+ * @param view the root view to process.
+ * @param projectCallback callback to the project.
+ */
+ private void postInflateProcess(View view, IProjectCallback projectCallback)
+ throws PostInflateException {
+ if (view instanceof TabHost) {
+ setupTabHost((TabHost)view, projectCallback);
+ } else if (view instanceof ViewGroup) {
+ ViewGroup group = (ViewGroup)view;
+ final int count = group.getChildCount();
+ for (int c = 0 ; c < count ; c++) {
+ View child = group.getChildAt(c);
+ postInflateProcess(child, projectCallback);
+ }
+ }
+ }
+
+ /**
+ * Sets up a {@link TabHost} object.
+ * @param tabHost the TabHost to setup.
+ * @param projectCallback The project callback object to access the project R class.
+ * @throws PostInflateException
+ */
+ private void setupTabHost(TabHost tabHost, IProjectCallback projectCallback)
+ throws PostInflateException {
+ // look for the TabWidget, and the FrameLayout. They have their own specific names
+ View v = tabHost.findViewById(android.R.id.tabs);
+
+ if (v == null) {
+ throw new PostInflateException(
+ "TabHost requires a TabWidget with id \"android:id/tabs\".\n");
+ }
+
+ if ((v instanceof TabWidget) == false) {
+ throw new PostInflateException(String.format(
+ "TabHost requires a TabWidget with id \"android:id/tabs\".\n" +
+ "View found with id 'tabs' is '%s'", v.getClass().getCanonicalName()));
+ }
+
+ v = tabHost.findViewById(android.R.id.tabcontent);
+
+ if (v == null) {
+ // TODO: see if we can fake tabs even without the FrameLayout (same below when the framelayout is empty)
+ throw new PostInflateException(
+ "TabHost requires a FrameLayout with id \"android:id/tabcontent\".");
+ }
+
+ if ((v instanceof FrameLayout) == false) {
+ throw new PostInflateException(String.format(
+ "TabHost requires a FrameLayout with id \"android:id/tabcontent\".\n" +
+ "View found with id 'tabcontent' is '%s'", v.getClass().getCanonicalName()));
+ }
+
+ FrameLayout content = (FrameLayout)v;
+
+ // now process the content of the framelayout and dynamically create tabs for it.
+ final int count = content.getChildCount();
+
+ if (count == 0) {
+ throw new PostInflateException(
+ "The FrameLayout for the TabHost has no content. Rendering failed.\n");
+ }
+
+ // this must be called before addTab() so that the TabHost searches its TabWidget
+ // and FrameLayout.
+ tabHost.setup();
+
+ // for each child of the framelayout, add a new TabSpec
+ for (int i = 0 ; i < count ; i++) {
+ View child = content.getChildAt(i);
+ String tabSpec = String.format("tab_spec%d", i+1);
+ int id = child.getId();
+ String[] resource = projectCallback.resolveResourceValue(id);
+ String name;
+ if (resource != null) {
+ name = resource[0]; // 0 is resource name, 1 is resource type.
+ } else {
+ name = String.format("Tab %d", i+1); // default name if id is unresolved.
+ }
+ tabHost.addTab(tabHost.newTabSpec(tabSpec).setIndicator(name).setContent(id));
+ }
+ }
+
+ /**
* Returns the bitmap for a specific path, from a specific project cache, or from the
* framework cache.
* @param value the path of the bitmap
@@ -750,7 +848,7 @@ public final class Bridge implements ILayoutBridge {
map = new HashMap<String, SoftReference<Bitmap>>();
sProjectBitmapCache.put(projectKey, map);
}
-
+
map.put(value, new SoftReference<Bitmap>(bmp));
} else {
sFrameworkBitmapCache.put(value, new SoftReference<Bitmap>(bmp));
@@ -767,7 +865,7 @@ public final class Bridge implements ILayoutBridge {
static NinePatch getCached9Patch(String value, Object projectKey) {
if (projectKey != null) {
Map<String, SoftReference<NinePatch>> map = sProject9PatchCache.get(projectKey);
-
+
if (map != null) {
SoftReference<NinePatch> ref = map.get(value);
if (ref != null) {
@@ -780,7 +878,7 @@ public final class Bridge implements ILayoutBridge {
return ref.get();
}
}
-
+
return null;
}
@@ -798,13 +896,21 @@ public final class Bridge implements ILayoutBridge {
map = new HashMap<String, SoftReference<NinePatch>>();
sProject9PatchCache.put(projectKey, map);
}
-
+
map.put(value, new SoftReference<NinePatch>(ninePatch));
} else {
sFramework9PatchCache.put(value, new SoftReference<NinePatch>(ninePatch));
}
}
+ private static final class PostInflateException extends Exception {
+ private static final long serialVersionUID = 1L;
+
+ public PostInflateException(String message) {
+ super(message);
+ }
+ }
+
/**
* Implementation of {@link IWindowSession} so that mSession is not null in
* the {@link SurfaceView}.
@@ -839,7 +945,7 @@ public final class Bridge implements ILayoutBridge {
// pass for now.
return false;
}
-
+
@SuppressWarnings("unused")
public MotionEvent getPendingPointerMove(IWindow arg0) throws RemoteException {
// pass for now.
@@ -863,7 +969,7 @@ public final class Bridge implements ILayoutBridge {
public void getDisplayFrame(IWindow window, Rect outDisplayFrame) {
// pass for now.
}
-
+
@SuppressWarnings("unused")
public void remove(IWindow arg0) throws RemoteException {
// pass for now.
@@ -883,13 +989,13 @@ public final class Bridge implements ILayoutBridge {
Rect visibleInsets) {
// pass for now.
}
-
+
public IBinder asBinder() {
// pass for now.
return null;
}
}
-
+
/**
* Implementation of {@link IWindow} to pass to the {@link AttachInfo}.
*/