diff options
Diffstat (limited to 'tools/layoutlib/bridge/src/android')
4 files changed, 1563 insertions, 33 deletions
diff --git a/tools/layoutlib/bridge/src/android/content/res/BridgeResources.java b/tools/layoutlib/bridge/src/android/content/res/BridgeResources.java new file mode 100644 index 0000000..e039898 --- /dev/null +++ b/tools/layoutlib/bridge/src/android/content/res/BridgeResources.java @@ -0,0 +1,647 @@ +/* + * Copyright (C) 2008 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.content.res; + +import com.android.ide.common.rendering.api.IProjectCallback; +import com.android.ide.common.rendering.api.LayoutLog; +import com.android.ide.common.rendering.api.ResourceValue; +import com.android.layoutlib.bridge.Bridge; +import com.android.layoutlib.bridge.BridgeConstants; +import com.android.layoutlib.bridge.android.BridgeContext; +import com.android.layoutlib.bridge.android.BridgeXmlBlockParser; +import com.android.layoutlib.bridge.impl.ParserFactory; +import com.android.layoutlib.bridge.impl.ResourceHelper; +import com.android.ninepatch.NinePatch; +import com.android.resources.ResourceType; +import com.android.util.Pair; + +import org.xmlpull.v1.XmlPullParser; +import org.xmlpull.v1.XmlPullParserException; + +import android.graphics.drawable.Drawable; +import android.util.AttributeSet; +import android.util.DisplayMetrics; +import android.util.TypedValue; +import android.view.ViewGroup.LayoutParams; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.InputStream; + +/** + * + */ +public final class BridgeResources extends Resources { + + private BridgeContext mContext; + private IProjectCallback mProjectCallback; + private boolean[] mPlatformResourceFlag = new boolean[1]; + + /** + * Simpler wrapper around FileInputStream. This is used when the input stream represent + * not a normal bitmap but a nine patch. + * This is useful when the InputStream is created in a method but used in another that needs + * to know whether this is 9-patch or not, such as BitmapFactory. + */ + public class NinePatchInputStream extends FileInputStream { + private boolean mFakeMarkSupport = true; + public NinePatchInputStream(File file) throws FileNotFoundException { + super(file); + } + + @Override + public boolean markSupported() { + if (mFakeMarkSupport) { + // this is needed so that BitmapFactory doesn't wrap this in a BufferedInputStream. + return true; + } + + return super.markSupported(); + } + + public void disableFakeMarkSupport() { + // disable fake mark support so that in case codec actually try to use them + // we don't lie to them. + mFakeMarkSupport = false; + } + } + + /** + * This initializes the static field {@link Resources#mSystem} which is used + * by methods who get global resources using {@link Resources#getSystem()}. + * <p/> + * They will end up using our bridge resources. + * <p/> + * {@link Bridge} calls this method after setting up a new bridge. + */ + /*package*/ public static Resources initSystem(BridgeContext context, + AssetManager assets, + DisplayMetrics metrics, + Configuration config, + IProjectCallback projectCallback) { + return Resources.mSystem = new BridgeResources(context, + assets, + metrics, + config, + projectCallback); + } + + /** + * Disposes the static {@link Resources#mSystem} to make sure we don't leave objects + * around that would prevent us from unloading the library. + */ + /*package*/ public static void disposeSystem() { + if (Resources.mSystem instanceof BridgeResources) { + ((BridgeResources)(Resources.mSystem)).mContext = null; + ((BridgeResources)(Resources.mSystem)).mProjectCallback = null; + } + Resources.mSystem = null; + } + + private BridgeResources(BridgeContext context, AssetManager assets, DisplayMetrics metrics, + Configuration config, IProjectCallback projectCallback) { + super(assets, metrics, config); + mContext = context; + mProjectCallback = projectCallback; + } + + public BridgeTypedArray newTypeArray(int numEntries, boolean platformFile, + boolean platformStyleable, String styleableName) { + return new BridgeTypedArray(this, mContext, numEntries, platformFile, + platformStyleable, styleableName); + } + + private Pair<String, ResourceValue> getResourceValue(int id, boolean[] platformResFlag_out) { + // first get the String related to this id in the framework + Pair<ResourceType, String> resourceInfo = Bridge.resolveResourceId(id); + + if (resourceInfo != null) { + platformResFlag_out[0] = true; + String attributeName = resourceInfo.getSecond(); + + return Pair.of(attributeName, mContext.getRenderResources().getFrameworkResource( + resourceInfo.getFirst(), attributeName)); + } + + // didn't find a match in the framework? look in the project. + if (mProjectCallback != null) { + resourceInfo = mProjectCallback.resolveResourceId(id); + + if (resourceInfo != null) { + platformResFlag_out[0] = false; + String attributeName = resourceInfo.getSecond(); + + return Pair.of(attributeName, mContext.getRenderResources().getProjectResource( + resourceInfo.getFirst(), attributeName)); + } + } + + return null; + } + + @Override + public Drawable getDrawable(int id) throws NotFoundException { + Pair<String, ResourceValue> value = getResourceValue(id, mPlatformResourceFlag); + + if (value != null) { + return ResourceHelper.getDrawable(value.getSecond(), mContext); + } + + // id was not found or not resolved. Throw a NotFoundException. + throwException(id); + + // this is not used since the method above always throws + return null; + } + + @Override + public int getColor(int id) throws NotFoundException { + Pair<String, ResourceValue> value = getResourceValue(id, mPlatformResourceFlag); + + if (value != null) { + try { + return ResourceHelper.getColor(value.getSecond().getValue()); + } catch (NumberFormatException e) { + Bridge.getLog().error(LayoutLog.TAG_RESOURCES_FORMAT, e.getMessage(), e, + null /*data*/); + return 0; + } + } + + // id was not found or not resolved. Throw a NotFoundException. + throwException(id); + + // this is not used since the method above always throws + return 0; + } + + @Override + public ColorStateList getColorStateList(int id) throws NotFoundException { + Pair<String, ResourceValue> resValue = getResourceValue(id, mPlatformResourceFlag); + + if (resValue != null) { + ColorStateList stateList = ResourceHelper.getColorStateList(resValue.getSecond(), + mContext); + if (stateList != null) { + return stateList; + } + } + + // id was not found or not resolved. Throw a NotFoundException. + throwException(id); + + // this is not used since the method above always throws + return null; + } + + @Override + public CharSequence getText(int id) throws NotFoundException { + Pair<String, ResourceValue> value = getResourceValue(id, mPlatformResourceFlag); + + if (value != null) { + return value.getSecond().getValue(); + } + + // id was not found or not resolved. Throw a NotFoundException. + throwException(id); + + // this is not used since the method above always throws + return null; + } + + @Override + public XmlResourceParser getLayout(int id) throws NotFoundException { + Pair<String, ResourceValue> v = getResourceValue(id, mPlatformResourceFlag); + + if (v != null) { + ResourceValue value = v.getSecond(); + XmlPullParser parser = null; + + try { + // check if the current parser can provide us with a custom parser. + if (mPlatformResourceFlag[0] == false) { + parser = mProjectCallback.getParser(value); + } + + // create a new one manually if needed. + if (parser == null) { + File xml = new File(value.getValue()); + if (xml.isFile()) { + // we need to create a pull parser around the layout XML file, and then + // give that to our XmlBlockParser + parser = ParserFactory.create(xml); + } + } + + if (parser != null) { + return new BridgeXmlBlockParser(parser, mContext, mPlatformResourceFlag[0]); + } + } catch (XmlPullParserException e) { + Bridge.getLog().error(LayoutLog.TAG_BROKEN, + "Failed to configure parser for " + value.getValue(), e, null /*data*/); + // we'll return null below. + } catch (FileNotFoundException e) { + // this shouldn't happen since we check above. + } + + } + + // id was not found or not resolved. Throw a NotFoundException. + throwException(id); + + // this is not used since the method above always throws + return null; + } + + @Override + public XmlResourceParser getAnimation(int id) throws NotFoundException { + Pair<String, ResourceValue> v = getResourceValue(id, mPlatformResourceFlag); + + if (v != null) { + ResourceValue value = v.getSecond(); + XmlPullParser parser = null; + + try { + File xml = new File(value.getValue()); + if (xml.isFile()) { + // we need to create a pull parser around the layout XML file, and then + // give that to our XmlBlockParser + parser = ParserFactory.create(xml); + + return new BridgeXmlBlockParser(parser, mContext, mPlatformResourceFlag[0]); + } + } catch (XmlPullParserException e) { + Bridge.getLog().error(LayoutLog.TAG_BROKEN, + "Failed to configure parser for " + value.getValue(), e, null /*data*/); + // we'll return null below. + } catch (FileNotFoundException e) { + // this shouldn't happen since we check above. + } + + } + + // id was not found or not resolved. Throw a NotFoundException. + throwException(id); + + // this is not used since the method above always throws + return null; + } + + @Override + public TypedArray obtainAttributes(AttributeSet set, int[] attrs) { + return mContext.obtainStyledAttributes(set, attrs); + } + + @Override + public TypedArray obtainTypedArray(int id) throws NotFoundException { + throw new UnsupportedOperationException(); + } + + + @Override + public float getDimension(int id) throws NotFoundException { + Pair<String, ResourceValue> value = getResourceValue(id, mPlatformResourceFlag); + + if (value != null) { + String v = value.getSecond().getValue(); + + if (v != null) { + if (v.equals(BridgeConstants.MATCH_PARENT) || + v.equals(BridgeConstants.FILL_PARENT)) { + return LayoutParams.MATCH_PARENT; + } else if (v.equals(BridgeConstants.WRAP_CONTENT)) { + return LayoutParams.WRAP_CONTENT; + } + + if (ResourceHelper.parseFloatAttribute( + value.getFirst(), v, mTmpValue, true /*requireUnit*/) && + mTmpValue.type == TypedValue.TYPE_DIMENSION) { + return mTmpValue.getDimension(getDisplayMetrics()); + } + } + } + + // id was not found or not resolved. Throw a NotFoundException. + throwException(id); + + // this is not used since the method above always throws + return 0; + } + + @Override + public int getDimensionPixelOffset(int id) throws NotFoundException { + Pair<String, ResourceValue> value = getResourceValue(id, mPlatformResourceFlag); + + if (value != null) { + String v = value.getSecond().getValue(); + + if (v != null) { + if (ResourceHelper.parseFloatAttribute( + value.getFirst(), v, mTmpValue, true /*requireUnit*/) && + mTmpValue.type == TypedValue.TYPE_DIMENSION) { + return TypedValue.complexToDimensionPixelOffset(mTmpValue.data, + getDisplayMetrics()); + } + } + } + + // id was not found or not resolved. Throw a NotFoundException. + throwException(id); + + // this is not used since the method above always throws + return 0; + } + + @Override + public int getDimensionPixelSize(int id) throws NotFoundException { + Pair<String, ResourceValue> value = getResourceValue(id, mPlatformResourceFlag); + + if (value != null) { + String v = value.getSecond().getValue(); + + if (v != null) { + if (ResourceHelper.parseFloatAttribute( + value.getFirst(), v, mTmpValue, true /*requireUnit*/) && + mTmpValue.type == TypedValue.TYPE_DIMENSION) { + return TypedValue.complexToDimensionPixelSize(mTmpValue.data, + getDisplayMetrics()); + } + } + } + + // id was not found or not resolved. Throw a NotFoundException. + throwException(id); + + // this is not used since the method above always throws + return 0; + } + + @Override + public int getInteger(int id) throws NotFoundException { + Pair<String, ResourceValue> value = getResourceValue(id, mPlatformResourceFlag); + + if (value != null && value.getSecond().getValue() != null) { + String v = value.getSecond().getValue(); + int radix = 10; + if (v.startsWith("0x")) { + v = v.substring(2); + radix = 16; + } + try { + return Integer.parseInt(v, radix); + } catch (NumberFormatException e) { + // return exception below + } + } + + // id was not found or not resolved. Throw a NotFoundException. + throwException(id); + + // this is not used since the method above always throws + return 0; + } + + @Override + public String getResourceEntryName(int resid) throws NotFoundException { + throw new UnsupportedOperationException(); + } + + @Override + public String getResourceName(int resid) throws NotFoundException { + throw new UnsupportedOperationException(); + } + + @Override + public String getResourceTypeName(int resid) throws NotFoundException { + throw new UnsupportedOperationException(); + } + + @Override + public String getString(int id, Object... formatArgs) throws NotFoundException { + String s = getString(id); + if (s != null) { + return String.format(s, formatArgs); + + } + + // id was not found or not resolved. Throw a NotFoundException. + throwException(id); + + // this is not used since the method above always throws + return null; + } + + @Override + public String getString(int id) throws NotFoundException { + Pair<String, ResourceValue> value = getResourceValue(id, mPlatformResourceFlag); + + if (value != null && value.getSecond().getValue() != null) { + return value.getSecond().getValue(); + } + + // id was not found or not resolved. Throw a NotFoundException. + throwException(id); + + // this is not used since the method above always throws + return null; + } + + @Override + public void getValue(int id, TypedValue outValue, boolean resolveRefs) + throws NotFoundException { + Pair<String, ResourceValue> value = getResourceValue(id, mPlatformResourceFlag); + + if (value != null) { + String v = value.getSecond().getValue(); + + if (v != null) { + if (ResourceHelper.parseFloatAttribute(value.getFirst(), v, outValue, + false /*requireUnit*/)) { + return; + } + + // else it's a string + outValue.type = TypedValue.TYPE_STRING; + outValue.string = v; + return; + } + } + + // id was not found or not resolved. Throw a NotFoundException. + throwException(id); + } + + @Override + public void getValue(String name, TypedValue outValue, boolean resolveRefs) + throws NotFoundException { + throw new UnsupportedOperationException(); + } + + @Override + public XmlResourceParser getXml(int id) throws NotFoundException { + Pair<String, ResourceValue> value = getResourceValue(id, mPlatformResourceFlag); + + if (value != null) { + String v = value.getSecond().getValue(); + + if (v != null) { + // check this is a file + File f = new File(v); + if (f.isFile()) { + try { + XmlPullParser parser = ParserFactory.create(f); + + return new BridgeXmlBlockParser(parser, mContext, mPlatformResourceFlag[0]); + } catch (XmlPullParserException e) { + NotFoundException newE = new NotFoundException(); + newE.initCause(e); + throw newE; + } catch (FileNotFoundException e) { + NotFoundException newE = new NotFoundException(); + newE.initCause(e); + throw newE; + } + } + } + } + + // id was not found or not resolved. Throw a NotFoundException. + throwException(id); + + // this is not used since the method above always throws + return null; + } + + @Override + public XmlResourceParser loadXmlResourceParser(String file, int id, + int assetCookie, String type) throws NotFoundException { + // even though we know the XML file to load directly, we still need to resolve the + // id so that we can know if it's a platform or project resource. + // (mPlatformResouceFlag will get the result and will be used later). + getResourceValue(id, mPlatformResourceFlag); + + File f = new File(file); + try { + XmlPullParser parser = ParserFactory.create(f); + + return new BridgeXmlBlockParser(parser, mContext, mPlatformResourceFlag[0]); + } catch (XmlPullParserException e) { + NotFoundException newE = new NotFoundException(); + newE.initCause(e); + throw newE; + } catch (FileNotFoundException e) { + NotFoundException newE = new NotFoundException(); + newE.initCause(e); + throw newE; + } + } + + + @Override + public InputStream openRawResource(int id) throws NotFoundException { + Pair<String, ResourceValue> value = getResourceValue(id, mPlatformResourceFlag); + + if (value != null) { + String path = value.getSecond().getValue(); + + if (path != null) { + // check this is a file + File f = new File(path); + if (f.isFile()) { + try { + // if it's a nine-patch return a custom input stream so that + // other methods (mainly bitmap factory) can detect it's a 9-patch + // and actually load it as a 9-patch instead of a normal bitmap + if (path.toLowerCase().endsWith(NinePatch.EXTENSION_9PATCH)) { + return new NinePatchInputStream(f); + } + return new FileInputStream(f); + } catch (FileNotFoundException e) { + NotFoundException newE = new NotFoundException(); + newE.initCause(e); + throw newE; + } + } + } + } + + // id was not found or not resolved. Throw a NotFoundException. + throwException(id); + + // this is not used since the method above always throws + return null; + } + + @Override + public InputStream openRawResource(int id, TypedValue value) throws NotFoundException { + getValue(id, value, true); + + String path = value.string.toString(); + + File f = new File(path); + if (f.isFile()) { + try { + // if it's a nine-patch return a custom input stream so that + // other methods (mainly bitmap factory) can detect it's a 9-patch + // and actually load it as a 9-patch instead of a normal bitmap + if (path.toLowerCase().endsWith(NinePatch.EXTENSION_9PATCH)) { + return new NinePatchInputStream(f); + } + return new FileInputStream(f); + } catch (FileNotFoundException e) { + NotFoundException exception = new NotFoundException(); + exception.initCause(e); + throw exception; + } + } + + throw new NotFoundException(); + } + + @Override + public AssetFileDescriptor openRawResourceFd(int id) throws NotFoundException { + throw new UnsupportedOperationException(); + } + + /** + * Builds and throws a {@link Resources.NotFoundException} based on a resource id and a resource type. + * @param id the id of the resource + * @throws NotFoundException + */ + private void throwException(int id) throws NotFoundException { + // first get the String related to this id in the framework + Pair<ResourceType, String> resourceInfo = Bridge.resolveResourceId(id); + + // if the name is unknown in the framework, get it from the custom view loader. + if (resourceInfo == null && mProjectCallback != null) { + resourceInfo = mProjectCallback.resolveResourceId(id); + } + + String message = null; + if (resourceInfo != null) { + message = String.format( + "Could not find %1$s resource matching value 0x%2$X (resolved name: %3$s) in current configuration.", + resourceInfo.getFirst(), id, resourceInfo.getSecond()); + } else { + message = String.format( + "Could not resolve resource value: 0x%1$X.", id); + } + + throw new NotFoundException(message); + } +} diff --git a/tools/layoutlib/bridge/src/android/content/res/BridgeTypedArray.java b/tools/layoutlib/bridge/src/android/content/res/BridgeTypedArray.java new file mode 100644 index 0000000..9deed32 --- /dev/null +++ b/tools/layoutlib/bridge/src/android/content/res/BridgeTypedArray.java @@ -0,0 +1,909 @@ +/* + * Copyright (C) 2008 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.content.res; + +import com.android.ide.common.rendering.api.DeclareStyleableResourceValue; +import com.android.ide.common.rendering.api.LayoutLog; +import com.android.ide.common.rendering.api.RenderResources; +import com.android.ide.common.rendering.api.ResourceValue; +import com.android.ide.common.rendering.api.StyleResourceValue; +import com.android.internal.util.XmlUtils; +import com.android.layoutlib.bridge.Bridge; +import com.android.layoutlib.bridge.BridgeConstants; +import com.android.layoutlib.bridge.android.BridgeContext; +import com.android.layoutlib.bridge.android.BridgeXmlBlockParser; +import com.android.layoutlib.bridge.impl.ParserFactory; +import com.android.layoutlib.bridge.impl.ResourceHelper; +import com.android.resources.ResourceType; + +import org.xmlpull.v1.XmlPullParser; +import org.xmlpull.v1.XmlPullParserException; + +import android.content.res.ColorStateList; +import android.content.res.Resources; +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.util.Arrays; +import java.util.Map; + +/** + * Custom implementation of TypedArray to handle non compiled resources. + */ +public final class BridgeTypedArray extends TypedArray { + + private final BridgeResources mBridgeResources; + private final BridgeContext mContext; + private final boolean mPlatformFile; + private final boolean mPlatformStyleable; + private final String mStyleableName; + + private ResourceValue[] mResourceData; + private String[] mNames; + + public BridgeTypedArray(BridgeResources resources, BridgeContext context, int len, + boolean platformFile, boolean platformStyleable, String styleableName) { + super(null, null, null, 0); + mBridgeResources = resources; + mContext = context; + mPlatformFile = platformFile; + mPlatformStyleable = platformStyleable; + mStyleableName = styleableName; + mResourceData = new ResourceValue[len]; + mNames = new String[len]; + } + + /** A bridge-specific method that sets a value in the type array */ + public void bridgeSetValue(int index, String name, ResourceValue value) { + mResourceData[index] = value; + mNames[index] = name; + } + + /** + * Seals the array after all calls to {@link #bridgeSetValue(int, String, ResourceValue)} have + * been done. + * <p/>This allows to compute the list of non default values, permitting + * {@link #getIndexCount()} to return the proper value. + */ + public void sealArray() { + // fills TypedArray.mIndices which is used to implement getIndexCount/getIndexAt + // first count the array size + int count = 0; + for (ResourceValue data : mResourceData) { + if (data != null) { + count++; + } + } + + // allocate the table with an extra to store the size + mIndices = new int[count+1]; + mIndices[0] = count; + + // fill the array with the indices. + int index = 1; + for (int i = 0 ; i < mResourceData.length ; i++) { + if (mResourceData[i] != null) { + mIndices[index++] = i; + } + } + } + + /** + * Return the number of values in this array. + */ + @Override + public int length() { + return mResourceData.length; + } + + /** + * Return the Resources object this array was loaded from. + */ + @Override + public Resources getResources() { + return mBridgeResources; + } + + /** + * Retrieve the styled string value for the attribute at <var>index</var>. + * + * @param index Index of attribute to retrieve. + * + * @return CharSequence holding string data. May be styled. Returns + * null if the attribute is not defined. + */ + @Override + public CharSequence getText(int index) { + if (index < 0 || index >= mResourceData.length) { + return null; + } + + if (mResourceData[index] != null) { + // FIXME: handle styled strings! + return mResourceData[index].getValue(); + } + + return null; + } + + /** + * Retrieve the string value for the attribute at <var>index</var>. + * + * @param index Index of attribute to retrieve. + * + * @return String holding string data. Any styling information is + * removed. Returns null if the attribute is not defined. + */ + @Override + public String getString(int index) { + if (index < 0 || index >= mResourceData.length) { + return null; + } + + if (mResourceData[index] != null) { + return mResourceData[index].getValue(); + } + + return null; + } + + /** + * Retrieve the boolean value for the attribute at <var>index</var>. + * + * @param index Index of attribute to retrieve. + * @param defValue Value to return if the attribute is not defined. + * + * @return Attribute boolean value, or defValue if not defined. + */ + @Override + public boolean getBoolean(int index, boolean defValue) { + if (index < 0 || index >= mResourceData.length) { + return defValue; + } + + if (mResourceData[index] == null) { + return defValue; + } + + String s = mResourceData[index].getValue(); + if (s != null) { + return XmlUtils.convertValueToBoolean(s, defValue); + } + + return defValue; + } + + /** + * Retrieve the integer value for the attribute at <var>index</var>. + * + * @param index Index of attribute to retrieve. + * @param defValue Value to return if the attribute is not defined. + * + * @return Attribute int value, or defValue if not defined. + */ + @Override + public int getInt(int index, int defValue) { + if (index < 0 || index >= mResourceData.length) { + return defValue; + } + + if (mResourceData[index] == null) { + return defValue; + } + + String s = mResourceData[index].getValue(); + + if (RenderResources.REFERENCE_NULL.equals(s)) { + return defValue; + } + + try { + return (s == null) ? defValue : XmlUtils.convertValueToInt(s, defValue); + } catch (NumberFormatException e) { + // pass + } + + // Field is not null and is not an integer. + // Check for possible constants and try to find them. + // Get the map of attribute-constant -> IntegerValue + Map<String, Integer> map = null; + if (mPlatformStyleable) { + map = Bridge.getEnumValues(mNames[index]); + } else if (mStyleableName != null) { + // get the styleable matching the resolved name + RenderResources res = mContext.getRenderResources(); + ResourceValue styleable = res.getProjectResource(ResourceType.DECLARE_STYLEABLE, + mStyleableName); + if (styleable instanceof DeclareStyleableResourceValue) { + map = ((DeclareStyleableResourceValue) styleable).getAttributeValues(mNames[index]); + } + } + + if (map != null) { + // accumulator to store the value of the 1+ constants. + int result = 0; + + // split the value in case this is a mix of several flags. + String[] keywords = s.split("\\|"); + for (String keyword : keywords) { + Integer i = map.get(keyword.trim()); + if (i != null) { + result |= i.intValue(); + } else { + Bridge.getLog().warning(LayoutLog.TAG_RESOURCES_FORMAT, + String.format( + "\"%s\" in attribute \"%2$s\" is not a valid value", + keyword, mNames[index]), null /*data*/); + } + } + return result; + } + + return defValue; + } + + /** + * Retrieve the float value for the attribute at <var>index</var>. + * + * @param index Index of attribute to retrieve. + * + * @return Attribute float value, or defValue if not defined.. + */ + @Override + public float getFloat(int index, float defValue) { + if (index < 0 || index >= mResourceData.length) { + return defValue; + } + + if (mResourceData[index] == null) { + return defValue; + } + + String s = mResourceData[index].getValue(); + + if (s != null) { + try { + return Float.parseFloat(s); + } catch (NumberFormatException e) { + Bridge.getLog().warning(LayoutLog.TAG_RESOURCES_FORMAT, + String.format( + "\"%s\" in attribute \"%2$s\" cannot be converted to float.", + s, mNames[index]), null /*data*/); + + // we'll return the default value below. + } + } + return defValue; + } + + /** + * Retrieve the color value for the attribute at <var>index</var>. If + * the attribute references a color resource holding a complex + * {@link android.content.res.ColorStateList}, then the default color from + * the set is returned. + * + * @param index Index of attribute to retrieve. + * @param defValue Value to return if the attribute is not defined or + * not a resource. + * + * @return Attribute color value, or defValue if not defined. + */ + @Override + public int getColor(int index, int defValue) { + if (index < 0 || index >= mResourceData.length) { + return defValue; + } + + if (mResourceData[index] == null) { + return defValue; + } + + ColorStateList colorStateList = ResourceHelper.getColorStateList( + mResourceData[index], mContext); + if (colorStateList != null) { + return colorStateList.getDefaultColor(); + } + + return defValue; + } + + /** + * Retrieve the ColorStateList for the attribute at <var>index</var>. + * The value may be either a single solid color or a reference to + * a color or complex {@link android.content.res.ColorStateList} description. + * + * @param index Index of attribute to retrieve. + * + * @return ColorStateList for the attribute, or null if not defined. + */ + @Override + public ColorStateList getColorStateList(int index) { + if (index < 0 || index >= mResourceData.length) { + return null; + } + + if (mResourceData[index] == null) { + return null; + } + + ResourceValue resValue = mResourceData[index]; + String value = resValue.getValue(); + + if (value == null) { + return null; + } + + if (RenderResources.REFERENCE_NULL.equals(value)) { + return null; + } + + // let the framework inflate the ColorStateList from the XML file. + File f = new File(value); + if (f.isFile()) { + try { + XmlPullParser parser = ParserFactory.create(f); + + BridgeXmlBlockParser blockParser = new BridgeXmlBlockParser( + parser, mContext, resValue.isFramework()); + try { + return ColorStateList.createFromXml(mContext.getResources(), blockParser); + } finally { + blockParser.ensurePopped(); + } + } catch (XmlPullParserException e) { + Bridge.getLog().error(LayoutLog.TAG_BROKEN, + "Failed to configure parser for " + value, e, null /*data*/); + return null; + } catch (Exception e) { + // this is an error and not warning since the file existence is checked before + // attempting to parse it. + Bridge.getLog().error(LayoutLog.TAG_RESOURCES_READ, + "Failed to parse file " + value, e, null /*data*/); + + return null; + } + } + + try { + int color = ResourceHelper.getColor(value); + return ColorStateList.valueOf(color); + } catch (NumberFormatException e) { + Bridge.getLog().error(LayoutLog.TAG_RESOURCES_FORMAT, e.getMessage(), e, null /*data*/); + } + + return null; + } + + /** + * Retrieve the integer value for the attribute at <var>index</var>. + * + * @param index Index of attribute to retrieve. + * @param defValue Value to return if the attribute is not defined or + * not a resource. + * + * @return Attribute integer value, or defValue if not defined. + */ + @Override + public int getInteger(int index, int defValue) { + return getInt(index, defValue); + } + + /** + * Retrieve a dimensional unit attribute at <var>index</var>. Unit + * conversions are based on the current {@link DisplayMetrics} + * associated with the resources this {@link TypedArray} object + * came from. + * + * @param index Index of attribute to retrieve. + * @param defValue Value to return if the attribute is not defined or + * not a resource. + * + * @return Attribute dimension value multiplied by the appropriate + * metric, or defValue if not defined. + * + * @see #getDimensionPixelOffset + * @see #getDimensionPixelSize + */ + @Override + public float getDimension(int index, float defValue) { + if (index < 0 || index >= mResourceData.length) { + return defValue; + } + + if (mResourceData[index] == null) { + return defValue; + } + + String s = mResourceData[index].getValue(); + + 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.parseFloatAttribute(mNames[index], s, mValue, true /*requireUnit*/)) { + return mValue.getDimension(mBridgeResources.getDisplayMetrics()); + } + + // 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; + } + + /** + * Retrieve a dimensional unit attribute at <var>index</var> for use + * as an offset in raw pixels. This is the same as + * {@link #getDimension}, except the returned value is converted to + * integer pixels for you. An offset conversion involves simply + * truncating the base value to an integer. + * + * @param index Index of attribute to retrieve. + * @param defValue Value to return if the attribute is not defined or + * not a resource. + * + * @return Attribute dimension value multiplied by the appropriate + * metric and truncated to integer pixels, or defValue if not defined. + * + * @see #getDimension + * @see #getDimensionPixelSize + */ + @Override + public int getDimensionPixelOffset(int index, int defValue) { + return (int) getDimension(index, defValue); + } + + /** + * Retrieve a dimensional unit attribute at <var>index</var> for use + * as a size in raw pixels. This is the same as + * {@link #getDimension}, except the returned value is converted to + * integer pixels for use as a size. A size conversion involves + * rounding the base value, and ensuring that a non-zero base value + * is at least one pixel in size. + * + * @param index Index of attribute to retrieve. + * @param defValue Value to return if the attribute is not defined or + * not a resource. + * + * @return Attribute dimension value multiplied by the appropriate + * metric and truncated to integer pixels, or defValue if not defined. + * + * @see #getDimension + * @see #getDimensionPixelOffset + */ + @Override + public int getDimensionPixelSize(int index, int defValue) { + try { + return getDimension(index); + } catch (RuntimeException e) { + if (mResourceData[index] != null) { + 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*/); + } + } + + return defValue; + } + } + + /** + * Special version of {@link #getDimensionPixelSize} for retrieving + * {@link android.view.ViewGroup}'s layout_width and layout_height + * attributes. This is only here for performance reasons; applications + * should use {@link #getDimensionPixelSize}. + * + * @param index Index of the attribute to retrieve. + * @param name Textual name of attribute for error reporting. + * + * @return Attribute dimension value multiplied by the appropriate + * metric and truncated to integer pixels. + */ + @Override + public int getLayoutDimension(int index, String name) { + 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 + public int getLayoutDimension(int index, int defValue) { + 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.parseFloatAttribute(mNames[index], s, mValue, true /*requireUnit*/)) { + float f = mValue.getDimension(mBridgeResources.getDisplayMetrics()); + + 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>. + * + * @param index Index of attribute to retrieve. + * @param base The base value of this fraction. In other words, a + * standard fraction is multiplied by this value. + * @param pbase The parent base value of this fraction. In other + * words, a parent fraction (nn%p) is multiplied by this + * value. + * @param defValue Value to return if the attribute is not defined or + * not a resource. + * + * @return Attribute fractional value multiplied by the appropriate + * base value, or defValue if not defined. + */ + @Override + public float getFraction(int index, int base, int pbase, float defValue) { + if (index < 0 || index >= mResourceData.length) { + return defValue; + } + + if (mResourceData[index] == null) { + return defValue; + } + + String value = mResourceData[index].getValue(); + if (value == null) { + return defValue; + } + + if (ResourceHelper.parseFloatAttribute(mNames[index], value, mValue, + false /*requireUnit*/)) { + return mValue.getFraction(base, pbase); + } + + // looks like we were unable to resolve the fraction value + Bridge.getLog().warning(LayoutLog.TAG_RESOURCES_FORMAT, + String.format( + "\"%1$s\" in attribute \"%2$s\" cannot be converted to a fraction.", + value, mNames[index]), null /*data*/); + + return defValue; + } + + /** + * Retrieve the resource identifier for the attribute at + * <var>index</var>. Note that attribute resource as resolved when + * the overall {@link TypedArray} object is retrieved. As a + * result, this function will return the resource identifier of the + * final resource value that was found, <em>not</em> necessarily the + * original resource that was specified by the attribute. + * + * @param index Index of attribute to retrieve. + * @param defValue Value to return if the attribute is not defined or + * not a resource. + * + * @return Attribute resource identifier, or defValue if not defined. + */ + @Override + public int getResourceId(int index, int defValue) { + if (index < 0 || index >= mResourceData.length) { + return defValue; + } + + // get the Resource for this index + ResourceValue resValue = mResourceData[index]; + + // no data, return the default value. + if (resValue == null) { + return defValue; + } + + // check if this is a style resource + if (resValue instanceof StyleResourceValue) { + // get the id that will represent this style. + return mContext.getDynamicIdByStyle((StyleResourceValue)resValue); + } + + if (RenderResources.REFERENCE_NULL.equals(resValue.getValue())) { + return defValue; + } + + // if the attribute was a reference to a resource, and not a declaration of an id (@+id), + // then the xml attribute value was "resolved" which leads us to a ResourceValue with a + // valid getType() and getName() returning a resource name. + // (and getValue() returning null!). We need to handle this! + if (resValue.getResourceType() != null) { + // if this is a framework id + if (mPlatformFile || resValue.isFramework()) { + // look for idName in the android R classes + return mContext.getFrameworkResourceValue( + resValue.getResourceType(), resValue.getName(), defValue); + } + + // look for idName in the project R class. + return mContext.getProjectResourceValue( + resValue.getResourceType(), resValue.getName(), defValue); + } + + // else, try to get the value, and resolve it somehow. + String value = resValue.getValue(); + if (value == null) { + return defValue; + } + + // if the value is just an integer, return it. + try { + int i = Integer.parseInt(value); + if (Integer.toString(i).equals(value)) { + return i; + } + } catch (NumberFormatException e) { + // pass + } + + // Handle the @id/<name>, @+id/<name> and @android:id/<name> + // We need to return the exact value that was compiled (from the various R classes), + // as these values can be reused internally with calls to findViewById(). + // There's a trick with platform layouts that not use "android:" but their IDs are in + // fact in the android.R and com.android.internal.R classes. + // The field mPlatformFile will indicate that all IDs are to be looked up in the android R + // classes exclusively. + + // if this is a reference to an id, find it. + if (value.startsWith("@id/") || value.startsWith("@+") || + value.startsWith("@android:id/")) { + + int pos = value.indexOf('/'); + String idName = value.substring(pos + 1); + + // if this is a framework id + if (mPlatformFile || value.startsWith("@android") || value.startsWith("@+android")) { + // look for idName in the android R classes + return mContext.getFrameworkResourceValue(ResourceType.ID, idName, defValue); + } + + // look for idName in the project R class. + return mContext.getProjectResourceValue(ResourceType.ID, idName, defValue); + } + + // not a direct id valid reference? resolve it + Integer idValue = null; + + if (resValue.isFramework()) { + idValue = Bridge.getResourceId(resValue.getResourceType(), + resValue.getName()); + } else { + idValue = mContext.getProjectCallback().getResourceId( + resValue.getResourceType(), resValue.getName()); + } + + if (idValue != null) { + return idValue.intValue(); + } + + Bridge.getLog().warning(LayoutLog.TAG_RESOURCES_RESOLVE, + String.format( + "Unable to resolve id \"%1$s\" for attribute \"%2$s\"", value, mNames[index]), + resValue); + + return defValue; + } + + /** + * Retrieve the Drawable for the attribute at <var>index</var>. This + * gets the resource ID of the selected attribute, and uses + * {@link Resources#getDrawable Resources.getDrawable} of the owning + * Resources object to retrieve its Drawable. + * + * @param index Index of attribute to retrieve. + * + * @return Drawable for the attribute, or null if not defined. + */ + @Override + public Drawable getDrawable(int index) { + if (index < 0 || index >= mResourceData.length) { + return null; + } + + if (mResourceData[index] == null) { + return null; + } + + ResourceValue value = mResourceData[index]; + String stringValue = value.getValue(); + if (stringValue == null || RenderResources.REFERENCE_NULL.equals(stringValue)) { + return null; + } + + return ResourceHelper.getDrawable(value, mContext); + } + + + /** + * Retrieve the CharSequence[] for the attribute at <var>index</var>. + * This gets the resource ID of the selected attribute, and uses + * {@link Resources#getTextArray Resources.getTextArray} of the owning + * Resources object to retrieve its String[]. + * + * @param index Index of attribute to retrieve. + * + * @return CharSequence[] for the attribute, or null if not defined. + */ + @Override + public CharSequence[] getTextArray(int index) { + if (index < 0 || index >= mResourceData.length) { + return null; + } + + if (mResourceData[index] == null) { + return null; + } + + String value = mResourceData[index].getValue(); + if (value != null) { + if (RenderResources.REFERENCE_NULL.equals(value)) { + return null; + } + + return new CharSequence[] { value }; + } + + Bridge.getLog().warning(LayoutLog.TAG_RESOURCES_FORMAT, + String.format( + String.format("Unknown value for getTextArray(%d) => %s", //DEBUG + index, mResourceData[index].getName())), null /*data*/); + + return null; + } + + /** + * Retrieve the raw TypedValue for the attribute at <var>index</var>. + * + * @param index Index of attribute to retrieve. + * @param outValue TypedValue object in which to place the attribute's + * data. + * + * @return Returns true if the value was retrieved, else false. + */ + @Override + public boolean getValue(int index, TypedValue outValue) { + if (index < 0 || index >= mResourceData.length) { + return false; + } + + if (mResourceData[index] == null) { + return false; + } + + String s = mResourceData[index].getValue(); + + return ResourceHelper.parseFloatAttribute(mNames[index], s, outValue, + false /*requireUnit*/); + } + + /** + * Determines whether there is an attribute at <var>index</var>. + * + * @param index Index of attribute to retrieve. + * + * @return True if the attribute has a value, false otherwise. + */ + @Override + public boolean hasValue(int index) { + if (index < 0 || index >= mResourceData.length) { + return false; + } + + return mResourceData[index] != null; + } + + /** + * Retrieve the raw TypedValue for the attribute at <var>index</var> + * and return a temporary object holding its data. This object is only + * valid until the next call on to {@link TypedArray}. + * + * @param index Index of attribute to retrieve. + * + * @return Returns a TypedValue object if the attribute is defined, + * containing its data; otherwise returns null. (You will not + * receive a TypedValue whose type is TYPE_NULL.) + */ + @Override + public TypedValue peekValue(int index) { + if (index < 0 || index >= mResourceData.length) { + return null; + } + + if (getValue(index, mValue)) { + return mValue; + } + + return null; + } + + /** + * Returns a message about the parser state suitable for printing error messages. + */ + @Override + public String getPositionDescription() { + return "<internal -- stub if needed>"; + } + + /** + * Give back a previously retrieved StyledAttributes, for later re-use. + */ + @Override + public void recycle() { + // pass + } + + @Override + public boolean getValueAt(int index, TypedValue outValue) { + // pass + return false; + } + + @Override + public String toString() { + return Arrays.toString(mResourceData); + } + } diff --git a/tools/layoutlib/bridge/src/android/graphics/BitmapFactory_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/BitmapFactory_Delegate.java index 080b85f..945b3cd 100644 --- a/tools/layoutlib/bridge/src/android/graphics/BitmapFactory_Delegate.java +++ b/tools/layoutlib/bridge/src/android/graphics/BitmapFactory_Delegate.java @@ -17,12 +17,12 @@ package android.graphics; import com.android.layoutlib.bridge.Bridge; -import com.android.layoutlib.bridge.android.BridgeResources.NinePatchInputStream; import com.android.layoutlib.bridge.impl.DelegateManager; import com.android.ninepatch.NinePatchChunk; import com.android.resources.Density; import com.android.tools.layoutlib.annotations.LayoutlibDelegate; +import android.content.res.BridgeResources.NinePatchInputStream; import android.graphics.BitmapFactory.Options; import java.io.FileDescriptor; diff --git a/tools/layoutlib/bridge/src/android/view/LayoutInflater_Delegate.java b/tools/layoutlib/bridge/src/android/view/LayoutInflater_Delegate.java index 3ef3288..64efa7e 100644 --- a/tools/layoutlib/bridge/src/android/view/LayoutInflater_Delegate.java +++ b/tools/layoutlib/bridge/src/android/view/LayoutInflater_Delegate.java @@ -38,11 +38,15 @@ import java.io.IOException; */ public class LayoutInflater_Delegate { + private static final String TAG_MERGE = "merge"; + public static boolean sIsInInclude = false; /** * Recursive method used to descend down the xml hierarchy and instantiate * views, instantiate their children, and then call onFinishInflate(). + * + * This implementation just records the merge status before calling the default implementation. */ @LayoutlibDelegate /*package*/ static void rInflate(LayoutInflater thisInflater, @@ -58,37 +62,7 @@ public class LayoutInflater_Delegate { // ---- START DEFAULT IMPLEMENTATION. - final int depth = parser.getDepth(); - int type; - - while (((type = parser.next()) != XmlPullParser.END_TAG || - parser.getDepth() > depth) && type != XmlPullParser.END_DOCUMENT) { - - if (type != XmlPullParser.START_TAG) { - continue; - } - - final String name = parser.getName(); - - if (LayoutInflater.TAG_REQUEST_FOCUS.equals(name)) { - thisInflater.parseRequestFocus(parser, parent); - } else if (LayoutInflater.TAG_INCLUDE.equals(name)) { - if (parser.getDepth() == 0) { - throw new InflateException("<include /> cannot be the root element"); - } - thisInflater.parseInclude(parser, parent, attrs); - } else if (LayoutInflater.TAG_MERGE.equals(name)) { - throw new InflateException("<merge /> must be the root element"); - } else { - final View view = thisInflater.createViewFromTag(parent, name, attrs); - final ViewGroup viewGroup = (ViewGroup) parent; - final ViewGroup.LayoutParams params = viewGroup.generateLayoutParams(attrs); - thisInflater.rInflate(parser, view, attrs, true); - viewGroup.addView(view, params); - } - } - - if (finishInflate) parent.onFinishInflate(); + thisInflater.rInflate_Original(parser, parent, attrs, finishInflate); // ---- END DEFAULT IMPLEMENTATION. @@ -138,7 +112,7 @@ public class LayoutInflater_Delegate { final String childName = childParser.getName(); - if (LayoutInflater.TAG_MERGE.equals(childName)) { + if (TAG_MERGE.equals(childName)) { // Inflate all children. thisInflater.rInflate(childParser, parent, childAttrs, false); } else { |