diff options
Diffstat (limited to 'tools/layoutlib')
20 files changed, 1052 insertions, 165 deletions
diff --git a/tools/layoutlib/bridge/src/android/graphics/Canvas_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/Canvas_Delegate.java index 49c1e4b..5623526 100644 --- a/tools/layoutlib/bridge/src/android/graphics/Canvas_Delegate.java +++ b/tools/layoutlib/bridge/src/android/graphics/Canvas_Delegate.java @@ -443,7 +443,7 @@ public final class Canvas_Delegate { AffineTransform matrixTx = matrixDelegate.getAffineTransform(); // combine them so that the given matrix is applied after. - currentTx.preConcatenate(matrixTx); + currentTx.concatenate(matrixTx); // give it to the graphics2D as a new matrix replacing all previous transform snapshot.setTransform(currentTx); @@ -735,7 +735,7 @@ public final class Canvas_Delegate { /*package*/ static void native_drawCircle(int nativeCanvas, float cx, float cy, float radius, int paint) { native_drawOval(nativeCanvas, - new RectF(cx - radius, cy - radius, radius*2, radius*2), + new RectF(cx - radius, cy - radius, radius, radius), paint); } diff --git a/tools/layoutlib/bridge/src/android/os/HandlerThread_Delegate.java b/tools/layoutlib/bridge/src/android/os/HandlerThread_Delegate.java new file mode 100644 index 0000000..afbe97c --- /dev/null +++ b/tools/layoutlib/bridge/src/android/os/HandlerThread_Delegate.java @@ -0,0 +1,80 @@ +/* + * Copyright (C) 2011 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 android.os; + +import com.android.layoutlib.bridge.android.BridgeContext; +import com.android.layoutlib.bridge.impl.RenderAction; +import com.android.tools.layoutlib.annotations.LayoutlibDelegate; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * Delegate overriding selected methods of android.os.HandlerThread + * + * Through the layoutlib_create tool, selected methods of Handler have been replaced + * by calls to methods of the same name in this delegate class. + * + * + */ +public class HandlerThread_Delegate { + + private static Map<BridgeContext, List<HandlerThread>> sThreads = + new HashMap<BridgeContext, List<HandlerThread>>(); + + public static void cleanUp(BridgeContext context) { + List<HandlerThread> list = sThreads.get(context); + if (list != null) { + for (HandlerThread thread : list) { + thread.quit(); + } + + list.clear(); + sThreads.remove(context); + } + } + + // -------- Delegate methods + + @LayoutlibDelegate + /*package*/ static void run(HandlerThread theThread) { + // record the thread so that it can be quit() on clean up. + BridgeContext context = RenderAction.getCurrentContext(); + List<HandlerThread> list = sThreads.get(context); + if (list == null) { + list = new ArrayList<HandlerThread>(); + sThreads.put(context, list); + } + + list.add(theThread); + + // ---- START DEFAULT IMPLEMENTATION. + + theThread.mTid = Process.myTid(); + Looper.prepare(); + synchronized (theThread) { + theThread.mLooper = Looper.myLooper(); + theThread.notifyAll(); + } + Process.setThreadPriority(theThread.mPriority); + theThread.onLooperPrepared(); + Looper.loop(); + theThread.mTid = -1; + } +} diff --git a/tools/layoutlib/bridge/src/android/view/LayoutInflater_Delegate.java b/tools/layoutlib/bridge/src/android/view/LayoutInflater_Delegate.java index d5266a5..ea7242c 100644 --- a/tools/layoutlib/bridge/src/android/view/LayoutInflater_Delegate.java +++ b/tools/layoutlib/bridge/src/android/view/LayoutInflater_Delegate.java @@ -43,6 +43,8 @@ import java.io.IOException; */ public class LayoutInflater_Delegate { + public static boolean sIsInInclude = false; + @LayoutlibDelegate /*package*/ static void parseInclude(LayoutInflater thisInflater, XmlPullParser parser, View parent, AttributeSet attrs) @@ -109,10 +111,22 @@ public class LayoutInflater_Delegate { // false means we need to rely on the included layout params. ViewGroup.LayoutParams params = null; try { + // ---- START CHANGES + sIsInInclude = true; + // ---- END CHANGES + params = group.generateLayoutParams(attrs); } catch (RuntimeException e) { + // ---- START CHANGES + sIsInInclude = false; + // ---- END CHANGES + params = group.generateLayoutParams(childAttrs); } finally { + // ---- START CHANGES + sIsInInclude = false; + // ---- END CHANGES + if (params != null) { view.setLayoutParams(params); } 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 b1a7824..c74eb33 100644 --- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/Bridge.java +++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/Bridge.java @@ -25,6 +25,7 @@ import com.android.ide.common.rendering.api.LayoutLog; import com.android.ide.common.rendering.api.RenderSession; import com.android.ide.common.rendering.api.Result; import com.android.ide.common.rendering.api.SessionParams; +import com.android.ide.common.rendering.api.Result.Status; import com.android.layoutlib.bridge.android.BridgeAssetManager; import com.android.layoutlib.bridge.impl.FontLoader; import com.android.layoutlib.bridge.impl.RenderDrawable; @@ -39,6 +40,9 @@ import android.graphics.Bitmap; import android.graphics.Typeface; import android.graphics.Typeface_Delegate; import android.os.Looper; +import android.view.View; +import android.view.ViewGroup; +import android.view.ViewParent; import java.io.File; import java.lang.ref.SoftReference; @@ -192,10 +196,11 @@ public final class Bridge extends com.android.ide.common.rendering.api.Bridge { Capability.UNBOUND_RENDERING, Capability.CUSTOM_BACKGROUND_COLOR, Capability.RENDER, - //Capability.LAYOUT_ONLY, // disable to run on ADT 10.0 which doesn't include this. + Capability.LAYOUT_ONLY, Capability.EMBEDDED_LAYOUT, - Capability.VIEW_MANIPULATION); - + Capability.VIEW_MANIPULATION, + Capability.ADAPTER_BINDING, + Capability.EXTENDED_VIEWINFO); BridgeAssetManager.initSystem(); @@ -367,6 +372,31 @@ public final class Bridge extends com.android.ide.common.rendering.api.Bridge { } } + @Override + public Result getViewParent(Object viewObject) { + if (viewObject instanceof View) { + return Status.SUCCESS.createResult(((View)viewObject).getParent()); + } + + throw new IllegalArgumentException("viewObject is not a View"); + } + + @Override + public Result getViewIndex(Object viewObject) { + if (viewObject instanceof View) { + View view = (View) viewObject; + ViewParent parentView = view.getParent(); + + if (parentView instanceof ViewGroup) { + Status.SUCCESS.createResult(((ViewGroup) parentView).indexOfChild(view)); + } + + return Status.SUCCESS.createResult(); + } + + throw new IllegalArgumentException("viewObject is not a View"); + } + /** * Returns the lock for the bridge */ diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/BridgeRenderSession.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/BridgeRenderSession.java index bf22c4d..e38b910 100644 --- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/BridgeRenderSession.java +++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/BridgeRenderSession.java @@ -22,12 +22,10 @@ import com.android.ide.common.rendering.api.RenderParams; import com.android.ide.common.rendering.api.RenderSession; import com.android.ide.common.rendering.api.Result; import com.android.ide.common.rendering.api.ViewInfo; -import com.android.ide.common.rendering.api.Result.Status; import com.android.layoutlib.bridge.impl.RenderSessionImpl; import android.view.View; import android.view.ViewGroup; -import android.view.ViewParent; import java.awt.image.BufferedImage; import java.util.List; @@ -83,31 +81,6 @@ public class BridgeRenderSession extends RenderSession { } @Override - public Result getViewParent(Object viewObject) { - if (viewObject instanceof View) { - return Status.SUCCESS.createResult(((View)viewObject).getParent()); - } - - throw new IllegalArgumentException("viewObject is not a View"); - } - - @Override - public Result getViewIndex(Object viewObject) { - if (viewObject instanceof View) { - View view = (View) viewObject; - ViewParent parentView = view.getParent(); - - if (parentView instanceof ViewGroup) { - Status.SUCCESS.createResult(((ViewGroup) parentView).indexOfChild(view)); - } - - return Status.SUCCESS.createResult(); - } - - throw new IllegalArgumentException("viewObject is not a View"); - } - - @Override public Result render(long timeout) { try { Bridge.prepareThread(); diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/MockView.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/MockView.java index 1ca3182..3d50b2a 100644 --- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/MockView.java +++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/MockView.java @@ -19,21 +19,23 @@ package com.android.layoutlib.bridge; import android.content.Context; import android.graphics.Canvas; import android.util.AttributeSet; +import android.view.Gravity; import android.widget.TextView; /** * Base class for mocked views. - * + * * TODO: implement onDraw and draw a rectangle in a random color with the name of the class * (or better the id of the view). */ public class MockView extends TextView { - + public MockView(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); - + setText(this.getClass().getSimpleName()); setTextColor(0xFF000000); + setGravity(Gravity.CENTER); } @Override diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeContext.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeContext.java index c4eee17..ea3d5d2 100644 --- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeContext.java +++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeContext.java @@ -16,9 +16,11 @@ package com.android.layoutlib.bridge.android; +import com.android.ide.common.rendering.api.ILayoutPullParser; import com.android.ide.common.rendering.api.IProjectCallback; import com.android.ide.common.rendering.api.LayoutLog; import com.android.ide.common.rendering.api.RenderResources; +import com.android.ide.common.rendering.api.ResourceReference; import com.android.ide.common.rendering.api.ResourceValue; import com.android.ide.common.rendering.api.StyleResourceValue; import com.android.layoutlib.bridge.Bridge; @@ -27,6 +29,10 @@ import com.android.layoutlib.bridge.impl.Stack; import com.android.resources.ResourceType; import com.android.util.Pair; +import org.kxml2.io.KXmlParser; +import org.xmlpull.v1.XmlPullParser; +import org.xmlpull.v1.XmlPullParserException; + import android.app.Activity; import android.content.BroadcastReceiver; import android.content.ComponentName; @@ -57,6 +63,7 @@ import android.util.DisplayMetrics; import android.util.TypedValue; import android.view.LayoutInflater; import android.view.View; +import android.view.ViewGroup; import java.io.File; import java.io.FileInputStream; @@ -265,6 +272,107 @@ public final class BridgeContext extends Activity { } + public ResourceReference resolveId(int id) { + // first get the String related to this id in the framework + Pair<ResourceType, String> resourceInfo = Bridge.resolveResourceId(id); + + if (resourceInfo != null) { + return new ResourceReference(resourceInfo.getSecond(), true); + } + + // didn't find a match in the framework? look in the project. + if (mProjectCallback != null) { + resourceInfo = mProjectCallback.resolveResourceId(id); + + if (resourceInfo != null) { + return new ResourceReference(resourceInfo.getSecond(), false); + } + } + + return null; + } + + public Pair<View, Boolean> inflateView(ResourceReference resource, ViewGroup parent, + boolean attachToRoot, boolean skipCallbackParser) { + String layoutName = resource.getName(); + boolean isPlatformLayout = resource.isFramework(); + + if (isPlatformLayout == false && skipCallbackParser == false) { + // check if the project callback can provide us with a custom parser. + ILayoutPullParser parser = mProjectCallback.getParser(layoutName); + if (parser != null) { + BridgeXmlBlockParser blockParser = new BridgeXmlBlockParser(parser, + this, resource.isFramework()); + try { + pushParser(blockParser); + return Pair.of( + mBridgeInflater.inflate(blockParser, parent, attachToRoot), + true); + } finally { + popParser(); + } + } + } + + ResourceValue resValue; + if (resource instanceof ResourceValue) { + resValue = (ResourceValue) resource; + } else { + if (isPlatformLayout) { + resValue = mRenderResources.getFrameworkResource(ResourceType.LAYOUT, + resource.getName()); + } else { + resValue = mRenderResources.getProjectResource(ResourceType.LAYOUT, + resource.getName()); + } + } + + if (resValue != null) { + + File xml = new File(resValue.getValue()); + if (xml.isFile()) { + // we need to create a pull parser around the layout XML file, and then + // give that to our XmlBlockParser + try { + KXmlParser parser = new KXmlParser(); + parser.setFeature(XmlPullParser.FEATURE_PROCESS_NAMESPACES, true); + parser.setInput(new FileInputStream(xml), "UTF-8"); //$NON-NLS-1$); + + // set the resource ref to have correct view cookies + mBridgeInflater.setResourceReference(resource); + + BridgeXmlBlockParser blockParser = new BridgeXmlBlockParser(parser, + this, resource.isFramework()); + try { + pushParser(blockParser); + return Pair.of( + mBridgeInflater.inflate(blockParser, parent, attachToRoot), + false); + } finally { + popParser(); + } + } catch (XmlPullParserException e) { + Bridge.getLog().error(LayoutLog.TAG_BROKEN, + "Failed to configure parser for " + xml, e, null /*data*/); + // we'll return null below. + } catch (FileNotFoundException e) { + // this shouldn't happen since we check above. + } finally { + mBridgeInflater.setResourceReference(null); + } + } else { + Bridge.getLog().error(LayoutLog.TAG_BROKEN, + String.format("File %s is missing!", xml), null); + } + } else { + Bridge.getLog().error(LayoutLog.TAG_BROKEN, + String.format("Layout %s%s does not exist.", isPlatformLayout ? "android:" : "", + layoutName), null); + } + + return Pair.of(null, false); + } + // ------------- Activity Methods @Override @@ -400,14 +508,13 @@ public final class BridgeContext extends Activity { BridgeTypedArray ta = ((BridgeResources) mSystemResources).newTypeArray(attrs.length, isPlatformFile); - // resolve the defStyleAttr value into a IStyleResourceValue - StyleResourceValue defStyleValues = null; - // look for a custom style. String customStyle = null; if (set != null) { customStyle = set.getAttributeValue(null /* namespace*/, "style"); } + + StyleResourceValue customStyleValues = null; if (customStyle != null) { ResourceValue item = mRenderResources.findResValue(customStyle, false /*forceFrameworkOnly*/); @@ -416,75 +523,76 @@ public final class BridgeContext extends Activity { item = mRenderResources.resolveResValue(item); if (item instanceof StyleResourceValue) { - defStyleValues = (StyleResourceValue)item; + customStyleValues = (StyleResourceValue)item; } } - if (defStyleValues == null) { - if (defStyleAttr != 0) { - // get the name from the int. - String defStyleName = searchAttr(defStyleAttr); + // resolve the defStyleAttr value into a IStyleResourceValue + StyleResourceValue defStyleValues = null; - if (defaultPropMap != null) { - defaultPropMap.put("style", defStyleName); - } + if (defStyleAttr != 0) { + // get the name from the int. + String defStyleName = searchAttr(defStyleAttr); - // look for the style in the current theme, and its parent: - ResourceValue item = mRenderResources.findItemInTheme(defStyleName); + if (defaultPropMap != null) { + defaultPropMap.put("style", defStyleName); + } - if (item != null) { - // item is a reference to a style entry. Search for it. - item = mRenderResources.findResValue(item.getValue(), - false /*forceFrameworkOnly*/); + // look for the style in the current theme, and its parent: + ResourceValue item = mRenderResources.findItemInTheme(defStyleName); - if (item instanceof StyleResourceValue) { - defStyleValues = (StyleResourceValue)item; - } - } else { - Bridge.getLog().error(null, - String.format( - "Failed to find style '%s' in current theme", defStyleName), - null /*data*/); - } - } else if (defStyleRes != 0) { - Pair<ResourceType, String> value = Bridge.resolveResourceId(defStyleRes); - if (value == null) { - value = mProjectCallback.resolveResourceId(defStyleRes); + if (item != null) { + // item is a reference to a style entry. Search for it. + item = mRenderResources.findResValue(item.getValue(), + false /*forceFrameworkOnly*/); + + if (item instanceof StyleResourceValue) { + defStyleValues = (StyleResourceValue)item; } + } else { + Bridge.getLog().error(null, + String.format( + "Failed to find style '%s' in current theme", defStyleName), + null /*data*/); + } + } else if (defStyleRes != 0) { + Pair<ResourceType, String> value = Bridge.resolveResourceId(defStyleRes); + if (value == null) { + value = mProjectCallback.resolveResourceId(defStyleRes); + } - if (value != null) { - if (value.getFirst() == ResourceType.STYLE) { - // look for the style in the current theme, and its parent: - ResourceValue item = mRenderResources.findItemInTheme(value.getSecond()); - if (item != null) { - if (item instanceof StyleResourceValue) { - if (defaultPropMap != null) { - defaultPropMap.put("style", item.getName()); - } - - defStyleValues = (StyleResourceValue)item; + if (value != null) { + if (value.getFirst() == ResourceType.STYLE) { + // look for the style in the current theme, and its parent: + ResourceValue item = mRenderResources.findItemInTheme(value.getSecond()); + if (item != null) { + if (item instanceof StyleResourceValue) { + if (defaultPropMap != null) { + defaultPropMap.put("style", item.getName()); } - } else { - Bridge.getLog().error(null, - String.format( - "Style with id 0x%x (resolved to '%s') does not exist.", - defStyleRes, value.getSecond()), - null /*data*/); + + defStyleValues = (StyleResourceValue)item; } } else { Bridge.getLog().error(null, String.format( - "Resouce id 0x%x is not of type STYLE (instead %s)", - defStyleRes, value.getFirst().toString()), + "Style with id 0x%x (resolved to '%s') does not exist.", + defStyleRes, value.getSecond()), null /*data*/); } } else { Bridge.getLog().error(null, String.format( - "Failed to find style with id 0x%x in current theme", - defStyleRes), + "Resouce id 0x%x is not of type STYLE (instead %s)", + defStyleRes, value.getFirst().toString()), null /*data*/); } + } else { + Bridge.getLog().error(null, + String.format( + "Failed to find style with id 0x%x in current theme", + defStyleRes), + null /*data*/); } } @@ -509,8 +617,13 @@ public final class BridgeContext extends Activity { if (value == null) { ResourceValue resValue = null; - // look for the value in the defStyle first (and its parent if needed) - if (defStyleValues != null) { + // look for the value in the custom style first (and its parent if needed) + if (customStyleValues != null) { + resValue = mRenderResources.findItemInStyle(customStyleValues, name); + } + + // then look for the value in the default Style (and its parent if needed) + if (resValue == null && defStyleValues != null) { resValue = mRenderResources.findItemInStyle(defStyleValues, name); } diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeInflater.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeInflater.java index cb8d8dd..edfe83e 100644 --- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeInflater.java +++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeInflater.java @@ -19,6 +19,7 @@ package com.android.layoutlib.bridge.android; import com.android.ide.common.rendering.api.IProjectCallback; import com.android.ide.common.rendering.api.LayoutLog; import com.android.ide.common.rendering.api.MergeCookie; +import com.android.ide.common.rendering.api.ResourceReference; import com.android.ide.common.rendering.api.ResourceValue; import com.android.layoutlib.bridge.Bridge; import com.android.resources.ResourceType; @@ -35,7 +36,7 @@ import android.view.View; import android.view.ViewGroup; import java.io.File; -import java.io.FileReader; +import java.io.FileInputStream; /** * Custom implementation of {@link LayoutInflater} to handle custom views. @@ -44,6 +45,7 @@ public final class BridgeInflater extends LayoutInflater { private final IProjectCallback mProjectCallback; private boolean mIsInMerge = false; + private ResourceReference mResourceReference; /** * List of class prefixes which are tried first by default. @@ -175,7 +177,7 @@ public final class BridgeInflater extends LayoutInflater { try { KXmlParser parser = new KXmlParser(); parser.setFeature(XmlPullParser.FEATURE_PROCESS_NAMESPACES, true); - parser.setInput(new FileReader(f)); + parser.setInput(new FileInputStream(f), "UTF-8"); //$NON-NLS-1$ BridgeXmlBlockParser bridgeParser = new BridgeXmlBlockParser( parser, bridgeContext, false); @@ -223,23 +225,33 @@ public final class BridgeInflater extends LayoutInflater { // get the view key Object viewKey = parser.getViewCookie(); - // if there's no view key and the depth is 1 (ie this is the first tag), or 2 - // (this is first item in included merge layout) - // look for a previous parser in the context, and check if this one has a viewkey. - int testDepth = mIsInMerge ? 2 : 1; - if (viewKey == null && parser.getDepth() == testDepth) { + if (viewKey == null) { + int currentDepth = parser.getDepth(); + + // test whether we are in an included file or in a adapter binding view. BridgeXmlBlockParser previousParser = bc.getPreviousParser(); if (previousParser != null) { - viewKey = previousParser.getViewCookie(); + // looks like we inside an embedded layout. + // only apply the cookie of the calling node (<include>) if we are at the + // top level of the embedded layout. If there is a merge tag, then + // skip it and look for the 2nd level + int testDepth = mIsInMerge ? 2 : 1; + if (currentDepth == testDepth) { + viewKey = previousParser.getViewCookie(); + // if we are in a merge, wrap the cookie in a MergeCookie. + if (viewKey != null && mIsInMerge) { + viewKey = new MergeCookie(viewKey); + } + } + } else if (mResourceReference != null && currentDepth == 1) { + // else if there's a resource reference, this means we are in an adapter + // binding case. Set the resource ref as the view cookie only for the top + // level view. + viewKey = mResourceReference; } } if (viewKey != null) { - if (testDepth == 2) { - // include-merge case - viewKey = new MergeCookie(viewKey); - } - bc.addViewKey(view, viewKey); } } @@ -250,6 +262,10 @@ public final class BridgeInflater extends LayoutInflater { mIsInMerge = isInMerge; } + public void setResourceReference(ResourceReference reference) { + mResourceReference = reference; + } + @Override public LayoutInflater cloneInContext(Context newContext) { return new BridgeInflater(this, newContext); diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeResources.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeResources.java index 5e5aeb1..345f053 100644 --- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeResources.java +++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeResources.java @@ -46,7 +46,6 @@ import android.view.ViewGroup.LayoutParams; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; -import java.io.FileReader; import java.io.InputStream; /** @@ -232,9 +231,8 @@ public final class BridgeResources extends Resources { try { // check if the current parser can provide us with a custom parser. - BridgeXmlBlockParser currentParser = mContext.getCurrentParser(); - if (currentParser != null) { - parser = currentParser.getParser(value.getName()); + if (mPlatformResourceFlag[0] == false) { + parser = mProjectCallback.getParser(value.getName()); } // create a new one manually if needed. @@ -245,7 +243,7 @@ public final class BridgeResources extends Resources { // give that to our XmlBlockParser parser = new KXmlParser(); parser.setFeature(XmlPullParser.FEATURE_PROCESS_NAMESPACES, true); - parser.setInput(new FileReader(xml)); + parser.setInput(new FileInputStream(xml), "UTF-8"); //$NON-NLS-1$); } } @@ -283,7 +281,7 @@ public final class BridgeResources extends Resources { // give that to our XmlBlockParser parser = new KXmlParser(); parser.setFeature(XmlPullParser.FEATURE_PROCESS_NAMESPACES, true); - parser.setInput(new FileReader(xml)); + parser.setInput(new FileInputStream(xml), "UTF-8"); //$NON-NLS-1$); return new BridgeXmlBlockParser(parser, mContext, mPlatformResourceFlag[0]); } @@ -502,7 +500,7 @@ public final class BridgeResources extends Resources { try { KXmlParser parser = new KXmlParser(); parser.setFeature(XmlPullParser.FEATURE_PROCESS_NAMESPACES, true); - parser.setInput(new FileReader(f)); + parser.setInput(new FileInputStream(f), "UTF-8"); //$NON-NLS-1$); return new BridgeXmlBlockParser(parser, mContext, mPlatformResourceFlag[0]); } catch (XmlPullParserException e) { @@ -537,7 +535,7 @@ public final class BridgeResources extends Resources { try { KXmlParser parser = new KXmlParser(); parser.setFeature(XmlPullParser.FEATURE_PROCESS_NAMESPACES, true); - parser.setInput(new FileReader(f)); + parser.setInput(new FileInputStream(f), "UTF-8"); //$NON-NLS-1$); return new BridgeXmlBlockParser(parser, mContext, mPlatformResourceFlag[0]); } catch (XmlPullParserException e) { diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeTypedArray.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeTypedArray.java index b9f769f..d4600a1 100644 --- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeTypedArray.java +++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeTypedArray.java @@ -36,10 +36,11 @@ import android.content.res.TypedArray; import android.graphics.drawable.Drawable; import android.util.DisplayMetrics; import android.util.TypedValue; +import android.view.LayoutInflater_Delegate; import android.view.ViewGroup.LayoutParams; import java.io.File; -import java.io.FileReader; +import java.io.FileInputStream; import java.util.Arrays; import java.util.Map; @@ -315,7 +316,7 @@ public final class BridgeTypedArray extends TypedArray { try { KXmlParser parser = new KXmlParser(); parser.setFeature(XmlPullParser.FEATURE_PROCESS_NAMESPACES, true); - parser.setInput(new FileReader(f)); + parser.setInput(new FileInputStream(f), "UTF-8"); //$NON-NLS-1$); BridgeXmlBlockParser blockParser = new BridgeXmlBlockParser( parser, mContext, resValue.isFramework()); @@ -471,40 +472,23 @@ public final class BridgeTypedArray extends TypedArray { */ @Override public int getDimensionPixelSize(int index, int defValue) { - if (mResourceData[index] == null) { - return defValue; - } + try { + return getDimension(index); + } catch (RuntimeException e) { + if (mResourceData[index] != null) { + String s = mResourceData[index].getValue(); - String s = mResourceData[index].getValue(); + if (s != null) { + // looks like we were unable to resolve the dimension value + Bridge.getLog().warning(LayoutLog.TAG_RESOURCES_FORMAT, + String.format( + "\"%1$s\" in attribute \"%2$s\" is not a valid format.", + s, mNames[index]), null /*data*/); + } + } - if (s == null) { - return defValue; - } else if (s.equals(BridgeConstants.MATCH_PARENT) || - s.equals(BridgeConstants.FILL_PARENT)) { - return LayoutParams.MATCH_PARENT; - } else if (s.equals(BridgeConstants.WRAP_CONTENT)) { - return LayoutParams.WRAP_CONTENT; - } else if (RenderResources.REFERENCE_NULL.equals(s)) { return defValue; } - - if (ResourceHelper.stringToFloat(s, mValue)) { - float f = mValue.getDimension(mBridgeResources.mMetrics); - - final int res = (int)(f+0.5f); - if (res != 0) return res; - if (f == 0) return 0; - if (f > 0) return 1; - return defValue; // this is basically unreachable. - } - - // looks like we were unable to resolve the dimension value - Bridge.getLog().warning(LayoutLog.TAG_RESOURCES_FORMAT, - String.format( - "\"%1$s\" in attribute \"%2$s\" is not a valid format.", - s, mNames[index]), null /*data*/); - - return defValue; } /** @@ -521,7 +505,20 @@ public final class BridgeTypedArray extends TypedArray { */ @Override public int getLayoutDimension(int index, String name) { - return getDimensionPixelSize(index, 0); + try { + // this will throw an exception + return getDimension(index); + } catch (RuntimeException e) { + + if (LayoutInflater_Delegate.sIsInInclude) { + throw new RuntimeException(); + } + + Bridge.getLog().warning(LayoutLog.TAG_RESOURCES_FORMAT, + "You must supply a " + name + " attribute.", null); + + return 0; + } } @Override @@ -529,6 +526,36 @@ public final class BridgeTypedArray extends TypedArray { return getDimensionPixelSize(index, defValue); } + private int getDimension(int index) { + if (mResourceData[index] == null) { + throw new RuntimeException(); + } + + String s = mResourceData[index].getValue(); + + if (s == null) { + throw new RuntimeException(); + } else if (s.equals(BridgeConstants.MATCH_PARENT) || + s.equals(BridgeConstants.FILL_PARENT)) { + return LayoutParams.MATCH_PARENT; + } else if (s.equals(BridgeConstants.WRAP_CONTENT)) { + return LayoutParams.WRAP_CONTENT; + } else if (RenderResources.REFERENCE_NULL.equals(s)) { + throw new RuntimeException(); + } + + if (ResourceHelper.stringToFloat(s, mValue)) { + float f = mValue.getDimension(mBridgeResources.mMetrics); + + final int res = (int)(f+0.5f); + if (res != 0) return res; + if (f == 0) return 0; + if (f > 0) return 1; + } + + throw new RuntimeException(); + } + /** * Retrieve a fractional unit attribute at <var>index</var>. * diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeXmlBlockParser.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeXmlBlockParser.java index 2f54ae6..70dbaa4 100644 --- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeXmlBlockParser.java +++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeXmlBlockParser.java @@ -69,14 +69,6 @@ public class BridgeXmlBlockParser implements XmlResourceParser { return mPlatformFile; } - public ILayoutPullParser getParser(String layoutName) { - if (mParser instanceof ILayoutPullParser) { - return ((ILayoutPullParser)mParser).getParser(layoutName); - } - - return null; - } - public Object getViewCookie() { if (mParser instanceof ILayoutPullParser) { return ((ILayoutPullParser)mParser).getViewCookie(); diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/bars/CustomBar.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/bars/CustomBar.java index 0c4b0d3..060e6ee 100644 --- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/bars/CustomBar.java +++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/bars/CustomBar.java @@ -73,7 +73,7 @@ abstract class CustomBar extends LinearLayout { parser.setFeature(XmlPullParser.FEATURE_PROCESS_NAMESPACES, true); parser.setInput( getClass().getResourceAsStream(layoutPath), - "UTF8"); + "UTF8"); //$NON-NLS-1$ BridgeXmlBlockParser bridgeParser = new BridgeXmlBlockParser( parser, (BridgeContext) context, false /*platformFile*/); diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/RenderAction.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/RenderAction.java index 8e80c21..6194f5d 100644 --- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/RenderAction.java +++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/RenderAction.java @@ -29,6 +29,7 @@ import com.android.layoutlib.bridge.Bridge; import com.android.layoutlib.bridge.android.BridgeContext; import com.android.resources.ResourceType; +import android.os.HandlerThread_Delegate; import android.util.DisplayMetrics; import java.util.concurrent.TimeUnit; @@ -228,6 +229,10 @@ public abstract class RenderAction<T extends RenderParams> extends FrameworkReso private void tearDown() { // Make sure to remove static references, otherwise we could not unload the lib mContext.disposeResources(); + + // quit HandlerThread created during this session. + HandlerThread_Delegate.cleanUp(sCurrentContext); + sCurrentContext = null; Bridge.setLog(null); diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/RenderSessionImpl.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/RenderSessionImpl.java index 348c579..f29a9d0 100644 --- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/RenderSessionImpl.java +++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/RenderSessionImpl.java @@ -22,12 +22,14 @@ import static com.android.ide.common.rendering.api.Result.Status.ERROR_UNKNOWN; import static com.android.ide.common.rendering.api.Result.Status.ERROR_VIEWGROUP_NO_CHILDREN; import static com.android.ide.common.rendering.api.Result.Status.SUCCESS; +import com.android.ide.common.rendering.api.AdapterBinding; import com.android.ide.common.rendering.api.IAnimationListener; import com.android.ide.common.rendering.api.ILayoutPullParser; import com.android.ide.common.rendering.api.IProjectCallback; import com.android.ide.common.rendering.api.RenderParams; import com.android.ide.common.rendering.api.RenderResources; import com.android.ide.common.rendering.api.RenderSession; +import com.android.ide.common.rendering.api.ResourceReference; import com.android.ide.common.rendering.api.ResourceValue; import com.android.ide.common.rendering.api.Result; import com.android.ide.common.rendering.api.SessionParams; @@ -44,6 +46,8 @@ import com.android.layoutlib.bridge.android.BridgeWindowSession; import com.android.layoutlib.bridge.android.BridgeXmlBlockParser; import com.android.layoutlib.bridge.bars.PhoneSystemBar; import com.android.layoutlib.bridge.bars.TitleBar; +import com.android.layoutlib.bridge.impl.binding.FakeAdapter; +import com.android.layoutlib.bridge.impl.binding.FakeExpandableAdapter; import com.android.resources.ResourceType; import com.android.resources.ScreenSize; import com.android.util.Pair; @@ -62,8 +66,14 @@ import android.view.ViewGroup; import android.view.View.AttachInfo; import android.view.View.MeasureSpec; import android.view.ViewGroup.LayoutParams; +import android.view.ViewGroup.MarginLayoutParams; +import android.widget.AbsListView; +import android.widget.AbsSpinner; +import android.widget.AdapterView; +import android.widget.ExpandableListView; import android.widget.FrameLayout; import android.widget.LinearLayout; +import android.widget.ListView; import android.widget.TabHost; import android.widget.TabWidget; import android.widget.TabHost.TabSpec; @@ -268,6 +278,9 @@ public class RenderSessionImpl extends RenderAction<SessionParams> { View view = mInflater.inflate(mBlockParser, mContentRoot); + // done with the parser, pop it. + context.popParser(); + // set the AttachInfo on the root view. AttachInfo info = new AttachInfo(new BridgeWindowSession(), new BridgeWindow(), new Handler(), null); @@ -453,7 +466,7 @@ public class RenderSessionImpl extends RenderAction<SessionParams> { mViewRoot.draw(mCanvas); } - mViewInfoList = startVisitingViews(mViewRoot, 0); + mViewInfoList = startVisitingViews(mViewRoot, 0, params.getExtendedViewInfoMode()); // success! return SUCCESS.createResult(); @@ -875,6 +888,75 @@ public class RenderSessionImpl extends RenderAction<SessionParams> { throws PostInflateException { if (view instanceof TabHost) { setupTabHost((TabHost)view, projectCallback); + } else if (view instanceof AdapterView<?>) { + // get the view ID. + int id = view.getId(); + + BridgeContext context = getContext(); + + // get a ResourceReference from the integer ID. + ResourceReference listRef = context.resolveId(id); + + if (listRef != null) { + SessionParams params = getParams(); + AdapterBinding binding = params.getAdapterBindings().get(listRef); + + // if there was no adapter binding, trying to get it from the call back. + if (binding == null) { + binding = params.getProjectCallback().getAdapterBinding(listRef, + context.getViewKey(view), view); + } + + if (binding != null) { + + if (view instanceof AbsListView) { + if ((binding.getFooterCount() > 0 || binding.getHeaderCount() > 0) && + view instanceof ListView) { + ListView list = (ListView) view; + + boolean skipCallbackParser = false; + + int count = binding.getHeaderCount(); + for (int i = 0 ; i < count ; i++) { + Pair<View, Boolean> pair = context.inflateView( + binding.getHeaderAt(i), + list, false /*attachToRoot*/, skipCallbackParser); + if (pair.getFirst() != null) { + list.addHeaderView(pair.getFirst()); + } + + skipCallbackParser |= pair.getSecond(); + } + + count = binding.getFooterCount(); + for (int i = 0 ; i < count ; i++) { + Pair<View, Boolean> pair = context.inflateView( + binding.getFooterAt(i), + list, false /*attachToRoot*/, skipCallbackParser); + if (pair.getFirst() != null) { + list.addFooterView(pair.getFirst()); + } + + skipCallbackParser |= pair.getSecond(); + } + } + + if (view instanceof ExpandableListView) { + ((ExpandableListView) view).setAdapter( + new FakeExpandableAdapter( + listRef, binding, params.getProjectCallback())); + } else { + ((AbsListView) view).setAdapter( + new FakeAdapter( + listRef, binding, params.getProjectCallback())); + } + } else if (view instanceof AbsSpinner) { + ((AbsSpinner) view).setAdapter( + new FakeAdapter( + listRef, binding, params.getProjectCallback())); + } + } + } } else if (view instanceof ViewGroup) { ViewGroup group = (ViewGroup)view; final int count = group.getChildCount(); @@ -959,7 +1041,7 @@ public class RenderSessionImpl extends RenderAction<SessionParams> { } } - private List<ViewInfo> startVisitingViews(View view, int offset) { + private List<ViewInfo> startVisitingViews(View view, int offset, boolean setExtendedInfo) { if (view == null) { return null; } @@ -968,7 +1050,7 @@ public class RenderSessionImpl extends RenderAction<SessionParams> { offset += view.getTop(); if (view == mContentRoot) { - return visitAllChildren(mContentRoot, offset); + return visitAllChildren(mContentRoot, offset, setExtendedInfo); } // otherwise, look for mContentRoot in the children @@ -976,7 +1058,8 @@ public class RenderSessionImpl extends RenderAction<SessionParams> { ViewGroup group = ((ViewGroup) view); for (int i = 0; i < group.getChildCount(); i++) { - List<ViewInfo> list = startVisitingViews(group.getChildAt(i), offset); + List<ViewInfo> list = startVisitingViews(group.getChildAt(i), offset, + setExtendedInfo); if (list != null) { return list; } @@ -991,8 +1074,9 @@ public class RenderSessionImpl extends RenderAction<SessionParams> { * bounds of all the views. * @param view the root View * @param offset an offset for the view bounds. + * @param setExtendedInfo whether to set the extended view info in the {@link ViewInfo} object. */ - private ViewInfo visit(View view, int offset) { + private ViewInfo visit(View view, int offset, boolean setExtendedInfo) { if (view == null) { return null; } @@ -1002,9 +1086,22 @@ public class RenderSessionImpl extends RenderAction<SessionParams> { view.getLeft(), view.getTop() + offset, view.getRight(), view.getBottom() + offset, view, view.getLayoutParams()); + if (setExtendedInfo) { + MarginLayoutParams marginParams = null; + LayoutParams params = view.getLayoutParams(); + if (params instanceof MarginLayoutParams) { + marginParams = (MarginLayoutParams) params; + } + result.setExtendedInfo(view.getBaseline(), + marginParams != null ? marginParams.leftMargin : 0, + marginParams != null ? marginParams.topMargin : 0, + marginParams != null ? marginParams.rightMargin : 0, + marginParams != null ? marginParams.bottomMargin : 0); + } + if (view instanceof ViewGroup) { ViewGroup group = ((ViewGroup) view); - result.setChildren(visitAllChildren(group, 0 /*offset*/)); + result.setChildren(visitAllChildren(group, 0 /*offset*/, setExtendedInfo)); } return result; @@ -1015,15 +1112,17 @@ public class RenderSessionImpl extends RenderAction<SessionParams> { * containing the bounds of all the views. * @param view the root View * @param offset an offset for the view bounds. + * @param setExtendedInfo whether to set the extended view info in the {@link ViewInfo} object. */ - private List<ViewInfo> visitAllChildren(ViewGroup viewGroup, int offset) { + private List<ViewInfo> visitAllChildren(ViewGroup viewGroup, int offset, + boolean setExtendedInfo) { if (viewGroup == null) { return null; } List<ViewInfo> children = new ArrayList<ViewInfo>(); for (int i = 0; i < viewGroup.getChildCount(); i++) { - children.add(visit(viewGroup.getChildAt(i), offset)); + children.add(visit(viewGroup.getChildAt(i), offset, setExtendedInfo)); } return children; } diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/ResourceHelper.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/ResourceHelper.java index 649160e..e5efa4e 100644 --- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/ResourceHelper.java +++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/ResourceHelper.java @@ -44,7 +44,6 @@ import android.util.TypedValue; import java.io.File; import java.io.FileInputStream; -import java.io.FileReader; import java.io.IOException; import java.io.InputStream; import java.net.MalformedURLException; @@ -115,7 +114,7 @@ public final class ResourceHelper { public static ColorStateList getColorStateList(ResourceValue resValue, BridgeContext context) { String value = resValue.getValue(); - if (value != null) { + if (value != null && RenderResources.REFERENCE_NULL.equals(value) == false) { // first check if the value is a file (xml most likely) File f = new File(value); if (f.isFile()) { @@ -124,7 +123,7 @@ public final class ResourceHelper { // providing an XmlPullParser KXmlParser parser = new KXmlParser(); parser.setFeature(XmlPullParser.FEATURE_PROCESS_NAMESPACES, true); - parser.setInput(new FileReader(f)); + parser.setInput(new FileInputStream(f), "UTF-8"); //$NON-NLS-1$); BridgeXmlBlockParser blockParser = new BridgeXmlBlockParser( parser, context, resValue.isFramework()); @@ -206,7 +205,7 @@ public final class ResourceHelper { // let the framework inflate the Drawable from the XML file. KXmlParser parser = new KXmlParser(); parser.setFeature(XmlPullParser.FEATURE_PROCESS_NAMESPACES, true); - parser.setInput(new FileReader(f)); + parser.setInput(new FileInputStream(f), "UTF-8"); //$NON-NLS-1$); BridgeXmlBlockParser blockParser = new BridgeXmlBlockParser( parser, context, value.isFramework()); diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/binding/BaseAdapter.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/binding/BaseAdapter.java new file mode 100644 index 0000000..e0414fe --- /dev/null +++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/binding/BaseAdapter.java @@ -0,0 +1,247 @@ +/* + * Copyright (C) 2011 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.layoutlib.bridge.impl.binding; + +import com.android.ide.common.rendering.api.AdapterBinding; +import com.android.ide.common.rendering.api.DataBindingItem; +import com.android.ide.common.rendering.api.IProjectCallback; +import com.android.ide.common.rendering.api.LayoutLog; +import com.android.ide.common.rendering.api.ResourceReference; +import com.android.ide.common.rendering.api.IProjectCallback.ViewAttribute; +import com.android.layoutlib.bridge.Bridge; +import com.android.layoutlib.bridge.android.BridgeContext; +import com.android.layoutlib.bridge.impl.RenderAction; +import com.android.util.Pair; + +import android.database.DataSetObserver; +import android.view.View; +import android.view.ViewGroup; +import android.widget.AdapterView; +import android.widget.Checkable; +import android.widget.ImageView; +import android.widget.TextView; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +/** + * Base adapter to do fake data binding in {@link AdapterView} objects. + */ +public class BaseAdapter { + + /** + * This is the items provided by the adapter. They are dynamically generated. + */ + protected final static class AdapterItem { + private final DataBindingItem mItem; + private final int mType; + private final int mFullPosition; + private final int mPositionPerType; + private List<AdapterItem> mChildren; + + protected AdapterItem(DataBindingItem item, int type, int fullPosition, + int positionPerType) { + mItem = item; + mType = type; + mFullPosition = fullPosition; + mPositionPerType = positionPerType; + } + + void addChild(AdapterItem child) { + if (mChildren == null) { + mChildren = new ArrayList<AdapterItem>(); + } + + mChildren.add(child); + } + + List<AdapterItem> getChildren() { + if (mChildren != null) { + return mChildren; + } + + return Collections.emptyList(); + } + + int getType() { + return mType; + } + + int getFullPosition() { + return mFullPosition; + } + + int getPositionPerType() { + return mPositionPerType; + } + + DataBindingItem getDataBindingItem() { + return mItem; + } + } + + private final AdapterBinding mBinding; + private final IProjectCallback mCallback; + private final ResourceReference mAdapterRef; + private boolean mSkipCallbackParser = false; + + protected final List<AdapterItem> mItems = new ArrayList<AdapterItem>(); + + protected BaseAdapter(ResourceReference adapterRef, AdapterBinding binding, + IProjectCallback callback) { + mAdapterRef = adapterRef; + mBinding = binding; + mCallback = callback; + } + + // ------- Some Adapter method used by all children classes. + + public boolean areAllItemsEnabled() { + return true; + } + + public boolean hasStableIds() { + return true; + } + + public boolean isEmpty() { + return mItems.size() == 0; + } + + public void registerDataSetObserver(DataSetObserver observer) { + // pass + } + + public void unregisterDataSetObserver(DataSetObserver observer) { + // pass + } + + // ------- + + + protected AdapterBinding getBinding() { + return mBinding; + } + + protected View getView(AdapterItem item, AdapterItem parentItem, View convertView, + ViewGroup parent) { + // we don't care about recycling here because we never scroll. + DataBindingItem dataBindingItem = item.getDataBindingItem(); + + BridgeContext context = RenderAction.getCurrentContext(); + + Pair<View, Boolean> pair = context.inflateView(dataBindingItem.getViewReference(), + parent, false /*attachToRoot*/, mSkipCallbackParser); + + View view = pair.getFirst(); + mSkipCallbackParser |= pair.getSecond(); + + if (view != null) { + fillView(context, view, item, parentItem); + } else { + // create a text view to display an error. + TextView tv = new TextView(context); + tv.setText("Unable to find layout: " + dataBindingItem.getViewReference().getName()); + view = tv; + } + + return view; + } + + private void fillView(BridgeContext context, View view, AdapterItem item, + AdapterItem parentItem) { + if (view instanceof ViewGroup) { + ViewGroup group = (ViewGroup) view; + final int count = group.getChildCount(); + for (int i = 0 ; i < count ; i++) { + fillView(context, group.getChildAt(i), item, parentItem); + } + } else { + int id = view.getId(); + if (id != 0) { + ResourceReference resolvedRef = context.resolveId(id); + if (resolvedRef != null) { + int fullPosition = item.getFullPosition(); + int positionPerType = item.getPositionPerType(); + int fullParentPosition = parentItem != null ? parentItem.getFullPosition() : 0; + int parentPositionPerType = parentItem != null ? + parentItem.getPositionPerType() : 0; + + if (view instanceof TextView) { + TextView tv = (TextView) view; + Object value = mCallback.getAdapterItemValue( + mAdapterRef, context.getViewKey(view), + item.getDataBindingItem().getViewReference(), + fullPosition, positionPerType, + fullParentPosition, parentPositionPerType, + resolvedRef, ViewAttribute.TEXT, tv.getText().toString()); + if (value != null) { + if (value.getClass() != ViewAttribute.TEXT.getAttributeClass()) { + Bridge.getLog().error(LayoutLog.TAG_BROKEN, String.format( + "Wrong Adapter Item value class for TEXT. Expected String, got %s", + value.getClass().getName()), null); + } else { + tv.setText((String) value); + } + } + } + + if (view instanceof Checkable) { + Checkable cb = (Checkable) view; + + Object value = mCallback.getAdapterItemValue( + mAdapterRef, context.getViewKey(view), + item.getDataBindingItem().getViewReference(), + fullPosition, positionPerType, + fullParentPosition, parentPositionPerType, + resolvedRef, ViewAttribute.IS_CHECKED, cb.isChecked()); + if (value != null) { + if (value.getClass() != ViewAttribute.IS_CHECKED.getAttributeClass()) { + Bridge.getLog().error(LayoutLog.TAG_BROKEN, String.format( + "Wrong Adapter Item value class for TEXT. Expected Boolean, got %s", + value.getClass().getName()), null); + } else { + cb.setChecked((Boolean) value); + } + } + } + + if (view instanceof ImageView) { + ImageView iv = (ImageView) view; + + Object value = mCallback.getAdapterItemValue( + mAdapterRef, context.getViewKey(view), + item.getDataBindingItem().getViewReference(), + fullPosition, positionPerType, + fullParentPosition, parentPositionPerType, + resolvedRef, ViewAttribute.SRC, iv.getDrawable()); + if (value != null) { + if (value.getClass() != ViewAttribute.SRC.getAttributeClass()) { + Bridge.getLog().error(LayoutLog.TAG_BROKEN, String.format( + "Wrong Adapter Item value class for TEXT. Expected Boolean, got %s", + value.getClass().getName()), null); + } else { + // FIXME + } + } + } + } + } + } + } +} diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/binding/FakeAdapter.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/binding/FakeAdapter.java new file mode 100644 index 0000000..c9bb424 --- /dev/null +++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/binding/FakeAdapter.java @@ -0,0 +1,113 @@ +/* + * Copyright (C) 2011 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.layoutlib.bridge.impl.binding; + +import com.android.ide.common.rendering.api.AdapterBinding; +import com.android.ide.common.rendering.api.DataBindingItem; +import com.android.ide.common.rendering.api.IProjectCallback; +import com.android.ide.common.rendering.api.ResourceReference; + +import android.view.View; +import android.view.ViewGroup; +import android.widget.AdapterView; +import android.widget.ListAdapter; +import android.widget.SpinnerAdapter; + +import java.util.ArrayList; +import java.util.List; + +/** + * Fake adapter to do fake data binding in {@link AdapterView} objects for {@link ListAdapter} + * and {@link SpinnerAdapter}. + * + */ +public class FakeAdapter extends BaseAdapter implements ListAdapter, SpinnerAdapter { + + // don't use a set because the order is important. + private final List<ResourceReference> mTypes = new ArrayList<ResourceReference>(); + + public FakeAdapter(ResourceReference adapterRef, AdapterBinding binding, + IProjectCallback callback) { + super(adapterRef, binding, callback); + + final int repeatCount = getBinding().getRepeatCount(); + final int itemCount = getBinding().getItemCount(); + + // Need an array to count for each type. + // This is likely too big, but is the max it can be. + int[] typeCount = new int[itemCount]; + + // We put several repeating sets. + for (int r = 0 ; r < repeatCount ; r++) { + // loop on the type of list items, and add however many for each type. + for (DataBindingItem dataBindingItem : getBinding()) { + ResourceReference viewRef = dataBindingItem.getViewReference(); + int typeIndex = mTypes.indexOf(viewRef); + if (typeIndex == -1) { + typeIndex = mTypes.size(); + mTypes.add(viewRef); + } + + int count = dataBindingItem.getCount(); + + int index = typeCount[typeIndex]; + typeCount[typeIndex] += count; + + for (int k = 0 ; k < count ; k++) { + mItems.add(new AdapterItem(dataBindingItem, typeIndex, mItems.size(), index++)); + } + } + } + } + + public boolean isEnabled(int position) { + return true; + } + + public int getCount() { + return mItems.size(); + } + + public Object getItem(int position) { + return mItems.get(position); + } + + public long getItemId(int position) { + return position; + } + + public int getItemViewType(int position) { + return mItems.get(position).getType(); + } + + public View getView(int position, View convertView, ViewGroup parent) { + // we don't care about recycling here because we never scroll. + AdapterItem item = mItems.get(position); + return getView(item, null /*parentGroup*/, convertView, parent); + } + + public int getViewTypeCount() { + return mTypes.size(); + } + + // ---- SpinnerAdapter + + public View getDropDownView(int position, View convertView, ViewGroup parent) { + // pass + return null; + } +} diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/binding/FakeExpandableAdapter.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/binding/FakeExpandableAdapter.java new file mode 100644 index 0000000..2c492e3 --- /dev/null +++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/binding/FakeExpandableAdapter.java @@ -0,0 +1,179 @@ +/* + * Copyright (C) 2011 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.layoutlib.bridge.impl.binding; + +import com.android.ide.common.rendering.api.AdapterBinding; +import com.android.ide.common.rendering.api.DataBindingItem; +import com.android.ide.common.rendering.api.IProjectCallback; +import com.android.ide.common.rendering.api.ResourceReference; + +import android.view.View; +import android.view.ViewGroup; +import android.widget.ExpandableListAdapter; +import android.widget.HeterogeneousExpandableList; + +import java.util.ArrayList; +import java.util.List; + +public class FakeExpandableAdapter extends BaseAdapter implements ExpandableListAdapter, + HeterogeneousExpandableList { + + // don't use a set because the order is important. + private final List<ResourceReference> mGroupTypes = new ArrayList<ResourceReference>(); + private final List<ResourceReference> mChildrenTypes = new ArrayList<ResourceReference>(); + + public FakeExpandableAdapter(ResourceReference adapterRef, AdapterBinding binding, + IProjectCallback callback) { + super(adapterRef, binding, callback); + + createItems(binding, binding.getItemCount(), binding.getRepeatCount(), mGroupTypes, 1); + } + + private void createItems(Iterable<DataBindingItem> iterable, final int itemCount, + final int repeatCount, List<ResourceReference> types, int depth) { + // Need an array to count for each type. + // This is likely too big, but is the max it can be. + int[] typeCount = new int[itemCount]; + + // we put several repeating sets. + for (int r = 0 ; r < repeatCount ; r++) { + // loop on the type of list items, and add however many for each type. + for (DataBindingItem dataBindingItem : iterable) { + ResourceReference viewRef = dataBindingItem.getViewReference(); + int typeIndex = types.indexOf(viewRef); + if (typeIndex == -1) { + typeIndex = types.size(); + types.add(viewRef); + } + + List<DataBindingItem> children = dataBindingItem.getChildren(); + int count = dataBindingItem.getCount(); + + // if there are children, we use the count as a repeat count for the children. + if (children.size() > 0) { + count = 1; + } + + int index = typeCount[typeIndex]; + typeCount[typeIndex] += count; + + for (int k = 0 ; k < count ; k++) { + AdapterItem item = new AdapterItem(dataBindingItem, typeIndex, mItems.size(), + index++); + mItems.add(item); + + if (children.size() > 0) { + createItems(dataBindingItem, depth + 1); + } + } + } + } + } + + private void createItems(DataBindingItem item, int depth) { + if (depth == 2) { + createItems(item, item.getChildren().size(), item.getCount(), mChildrenTypes, depth); + } + } + + private AdapterItem getChildItem(int groupPosition, int childPosition) { + AdapterItem item = mItems.get(groupPosition); + + List<AdapterItem> children = item.getChildren(); + return children.get(childPosition); + } + + // ---- ExpandableListAdapter + + public int getGroupCount() { + return mItems.size(); + } + + public int getChildrenCount(int groupPosition) { + AdapterItem item = mItems.get(groupPosition); + return item.getChildren().size(); + } + + public Object getGroup(int groupPosition) { + return mItems.get(groupPosition); + } + + public Object getChild(int groupPosition, int childPosition) { + return getChildItem(groupPosition, childPosition); + } + + public View getGroupView(int groupPosition, boolean isExpanded, View convertView, + ViewGroup parent) { + // we don't care about recycling here because we never scroll. + AdapterItem item = mItems.get(groupPosition); + return getView(item, null /*parentItem*/, convertView, parent); + } + + public View getChildView(int groupPosition, int childPosition, boolean isLastChild, + View convertView, ViewGroup parent) { + // we don't care about recycling here because we never scroll. + AdapterItem parentItem = mItems.get(groupPosition); + AdapterItem item = getChildItem(groupPosition, childPosition); + return getView(item, parentItem, convertView, parent); + } + + public long getGroupId(int groupPosition) { + return groupPosition; + } + + public long getChildId(int groupPosition, int childPosition) { + return childPosition; + } + + public long getCombinedGroupId(long groupId) { + return groupId << 16 | 0x0000FFFF; + } + + public long getCombinedChildId(long groupId, long childId) { + return groupId << 16 | childId; + } + + public boolean isChildSelectable(int groupPosition, int childPosition) { + return true; + } + + public void onGroupCollapsed(int groupPosition) { + // pass + } + + public void onGroupExpanded(int groupPosition) { + // pass + } + + // ---- HeterogeneousExpandableList + + public int getChildType(int groupPosition, int childPosition) { + return getChildItem(groupPosition, childPosition).getType(); + } + + public int getChildTypeCount() { + return mChildrenTypes.size(); + } + + public int getGroupType(int groupPosition) { + return mItems.get(groupPosition).getType(); + } + + public int getGroupTypeCount() { + return mGroupTypes.size(); + } +} diff --git a/tools/layoutlib/bridge/tests/src/com/android/layoutlib/bridge/android/BridgeXmlBlockParserTest.java b/tools/layoutlib/bridge/tests/src/com/android/layoutlib/bridge/android/BridgeXmlBlockParserTest.java index 3252fb4..70d5446 100644 --- a/tools/layoutlib/bridge/tests/src/com/android/layoutlib/bridge/android/BridgeXmlBlockParserTest.java +++ b/tools/layoutlib/bridge/tests/src/com/android/layoutlib/bridge/android/BridgeXmlBlockParserTest.java @@ -44,7 +44,7 @@ public class BridgeXmlBlockParserTest extends TestCase { InputStream input = this.getClass().getClassLoader().getResourceAsStream( "com/android/layoutlib/testdata/layout1.xml"); - parser.setInput(input, null /*encoding*/); + parser.setInput(input, "UTF-8"); //$NON-NLS-1$ assertEquals(XmlPullParser.START_DOCUMENT, parser.next()); diff --git a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/CreateInfo.java b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/CreateInfo.java index 85e6c3f..95506c6 100644 --- a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/CreateInfo.java +++ b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/CreateInfo.java @@ -100,11 +100,11 @@ public final class CreateInfo implements ICreateInfo { "android.content.res.Resources$Theme#resolveAttribute", "android.graphics.BitmapFactory#finishDecode", "android.os.Handler#sendMessageAtTime", + "android.os.HandlerThread#run", "android.os.Build#getString", "android.view.LayoutInflater#parseInclude", "android.view.View#isInEditMode", "com.android.internal.util.XmlUtils#convertValueToInt", - // TODO: comment out once DelegateClass is working }; /** |