Merge "Add guard around getXmlFileParser() call" into mnc-ub-dev
diff --git a/tools/layoutlib/bridge/src/android/content/res/BridgeTypedArray.java b/tools/layoutlib/bridge/src/android/content/res/BridgeTypedArray.java
index d2c21cb..ef681d2 100644
--- a/tools/layoutlib/bridge/src/android/content/res/BridgeTypedArray.java
+++ b/tools/layoutlib/bridge/src/android/content/res/BridgeTypedArray.java
@@ -72,7 +72,7 @@
*/
public final class BridgeTypedArray extends TypedArray {
- private final BridgeResources mBridgeResources;
+ private final Resources mBridgeResources;
private final BridgeContext mContext;
private final boolean mPlatformFile;
@@ -85,7 +85,7 @@
@Nullable
private int[] mEmptyIds;
- public BridgeTypedArray(BridgeResources resources, BridgeContext context, int len,
+ public BridgeTypedArray(Resources resources, BridgeContext context, int len,
boolean platformFile) {
super(resources, null, null, 0);
mBridgeResources = resources;
@@ -762,7 +762,8 @@
if (resVal instanceof ArrayResourceValue) {
ArrayResourceValue array = (ArrayResourceValue) resVal;
int count = array.getElementCount();
- return count >= 0 ? mBridgeResources.fillValues(array, new CharSequence[count]) : null;
+ return count >= 0 ? Resources_Delegate.fillValues(mBridgeResources, array, new CharSequence[count]) :
+ null;
}
int id = getResourceId(index, 0);
String resIdMessage = id > 0 ? " (resource id 0x" + Integer.toHexString(id) + ')' : "";
@@ -1010,7 +1011,6 @@
}
static TypedArray obtain(Resources res, int len) {
- return res instanceof BridgeResources ?
- new BridgeTypedArray(((BridgeResources) res), null, len, true) : null;
+ return new BridgeTypedArray(res, null, len, true);
}
}
diff --git a/tools/layoutlib/bridge/src/android/content/res/BridgeResources.java b/tools/layoutlib/bridge/src/android/content/res/Resources_Delegate.java
similarity index 63%
rename from tools/layoutlib/bridge/src/android/content/res/BridgeResources.java
rename to tools/layoutlib/bridge/src/android/content/res/Resources_Delegate.java
index 0e39243..6e8e42f 100644
--- a/tools/layoutlib/bridge/src/android/content/res/BridgeResources.java
+++ b/tools/layoutlib/bridge/src/android/content/res/Resources_Delegate.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2008 The Android Open Source Project
+ * Copyright (C) 2016 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.
@@ -28,8 +28,10 @@
import com.android.layoutlib.bridge.android.BridgeXmlBlockParser;
import com.android.layoutlib.bridge.impl.ParserFactory;
import com.android.layoutlib.bridge.impl.ResourceHelper;
+import com.android.layoutlib.bridge.util.NinePatchInputStream;
import com.android.ninepatch.NinePatch;
import com.android.resources.ResourceType;
+import com.android.tools.layoutlib.annotations.LayoutlibDelegate;
import com.android.util.Pair;
import org.xmlpull.v1.XmlPullParser;
@@ -37,6 +39,8 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.content.res.Resources.NotFoundException;
+import android.content.res.Resources.Theme;
import android.graphics.drawable.Drawable;
import android.util.AttributeSet;
import android.util.DisplayMetrics;
@@ -49,131 +53,102 @@
import java.io.InputStream;
import java.util.Iterator;
-public final class BridgeResources extends Resources {
+@SuppressWarnings("deprecation")
+public class Resources_Delegate {
- private BridgeContext mContext;
- private LayoutlibCallback mLayoutlibCallback;
- private boolean[] mPlatformResourceFlag = new boolean[1];
- private TypedValue mTmpValue = new TypedValue();
+ private static 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.
- */
public static Resources initSystem(BridgeContext context,
AssetManager assets,
DisplayMetrics metrics,
Configuration config,
LayoutlibCallback layoutlibCallback) {
- return Resources.mSystem = new BridgeResources(context,
- assets,
- metrics,
- config,
- layoutlibCallback);
+ Resources resources = new Resources(assets, metrics, config);
+ resources.mContext = context;
+ resources.mLayoutlibCallback = layoutlibCallback;
+ return Resources.mSystem = resources;
}
/**
- * Disposes the static {@link Resources#mSystem} to make sure we don't leave objects
- * around that would prevent us from unloading the library.
+ * Disposes the static {@link Resources#mSystem} to make sure we don't leave objects around that
+ * would prevent us from unloading the library.
*/
public static void disposeSystem() {
- if (Resources.mSystem instanceof BridgeResources) {
- ((BridgeResources)(Resources.mSystem)).mContext = null;
- ((BridgeResources)(Resources.mSystem)).mLayoutlibCallback = null;
- }
+ Resources.mSystem.mContext = null;
+ Resources.mSystem.mLayoutlibCallback = null;
Resources.mSystem = null;
}
- private BridgeResources(BridgeContext context, AssetManager assets, DisplayMetrics metrics,
- Configuration config, LayoutlibCallback layoutlibCallback) {
- super(assets, metrics, config);
- mContext = context;
- mLayoutlibCallback = layoutlibCallback;
+ public static BridgeTypedArray newTypeArray(Resources resources, int numEntries,
+ boolean platformFile) {
+ return new BridgeTypedArray(resources, resources.mContext, numEntries, platformFile);
}
- public BridgeTypedArray newTypeArray(int numEntries, boolean platformFile) {
- return new BridgeTypedArray(this, mContext, numEntries, platformFile);
- }
-
- private Pair<String, ResourceValue> getResourceValue(int id, boolean[] platformResFlag_out) {
+ private static Pair<String, ResourceValue> getResourceValue(Resources resources, int id,
+ boolean[] platformResFlag_out) {
// first get the String related to this id in the framework
Pair<ResourceType, String> resourceInfo = Bridge.resolveResourceId(id);
+ // Set the layoutlib callback and context for resources
+ if (resources != Resources.mSystem && resources.mLayoutlibCallback == null) {
+ resources.mLayoutlibCallback = Resources.mSystem.mLayoutlibCallback;
+ resources.mContext = Resources.mSystem.mContext;
+ }
+
if (resourceInfo != null) {
platformResFlag_out[0] = true;
String attributeName = resourceInfo.getSecond();
- return Pair.of(attributeName, mContext.getRenderResources().getFrameworkResource(
- resourceInfo.getFirst(), attributeName));
+ return Pair.of(attributeName,
+ resources.mContext.getRenderResources().getFrameworkResource(
+ resourceInfo.getFirst(), attributeName));
}
// didn't find a match in the framework? look in the project.
- if (mLayoutlibCallback != null) {
- resourceInfo = mLayoutlibCallback.resolveResourceId(id);
+ if (resources.mLayoutlibCallback != null) {
+ resourceInfo = resources.mLayoutlibCallback.resolveResourceId(id);
if (resourceInfo != null) {
platformResFlag_out[0] = false;
String attributeName = resourceInfo.getSecond();
- return Pair.of(attributeName, mContext.getRenderResources().getProjectResource(
- resourceInfo.getFirst(), attributeName));
+ return Pair.of(attributeName,
+ resources.mContext.getRenderResources().getProjectResource(
+ resourceInfo.getFirst(), attributeName));
}
}
return null;
}
- @Override
- public Drawable getDrawable(int id, Theme theme) {
- Pair<String, ResourceValue> value = getResourceValue(id, mPlatformResourceFlag);
+ @LayoutlibDelegate
+ static Drawable getDrawable(Resources resources, int id) {
+ return getDrawable(resources, id, null);
+ }
+
+ @LayoutlibDelegate
+ static Drawable getDrawable(Resources resources, int id, Theme theme) {
+ Pair<String, ResourceValue> value = getResourceValue(resources, id, mPlatformResourceFlag);
if (value != null) {
- return ResourceHelper.getDrawable(value.getSecond(), mContext, theme);
+ return ResourceHelper.getDrawable(value.getSecond(), resources.mContext, theme);
}
// id was not found or not resolved. Throw a NotFoundException.
- throwException(id);
+ throwException(resources, id);
// this is not used since the method above always throws
return null;
}
- @Override
- public int getColor(int id, Theme theme) throws NotFoundException {
- Pair<String, ResourceValue> value = getResourceValue(id, mPlatformResourceFlag);
+ @LayoutlibDelegate
+ static int getColor(Resources resources, int id) {
+ return getColor(resources, id, null);
+ }
+
+ @LayoutlibDelegate
+ static int getColor(Resources resources, int id, Theme theme) throws NotFoundException {
+ Pair<String, ResourceValue> value = getResourceValue(resources, id, mPlatformResourceFlag);
if (value != null) {
ResourceValue resourceValue = value.getSecond();
@@ -185,7 +160,7 @@
String message;
if (new File(resourceValue.getValue()).isFile()) {
String resource = (resourceValue.isFramework() ? "@android:" : "@") + "color/"
- + resourceValue.getName();
+ + resourceValue.getName();
message = "Hexadecimal color expected, found Color State List for " + resource;
} else {
message = e.getMessage();
@@ -198,31 +173,57 @@
// Suppress possible NPE. getColorStateList will never return null, it will instead
// throw an exception, but intelliJ can't figure that out
//noinspection ConstantConditions
- return getColorStateList(id, theme).getDefaultColor();
+ return getColorStateList(resources, id, theme).getDefaultColor();
}
- @Override
- public ColorStateList getColorStateList(int id, Theme theme) throws NotFoundException {
- Pair<String, ResourceValue> resValue = getResourceValue(id, mPlatformResourceFlag);
+ @LayoutlibDelegate
+ static ColorStateList getColorStateList(Resources resources, int id) throws NotFoundException {
+ return getColorStateList(resources, id, null);
+ }
+
+ @LayoutlibDelegate
+ static ColorStateList getColorStateList(Resources resources, int id, Theme theme)
+ throws NotFoundException {
+ Pair<String, ResourceValue> resValue =
+ getResourceValue(resources, id, mPlatformResourceFlag);
if (resValue != null) {
ColorStateList stateList = ResourceHelper.getColorStateList(resValue.getSecond(),
- mContext);
+ resources.mContext);
if (stateList != null) {
return stateList.obtainForTheme(theme);
}
}
// id was not found or not resolved. Throw a NotFoundException.
- throwException(id);
+ throwException(resources, 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);
+ @LayoutlibDelegate
+ static CharSequence getText(Resources resources, int id, CharSequence def) {
+ Pair<String, ResourceValue> value = getResourceValue(resources, id, mPlatformResourceFlag);
+
+ if (value != null) {
+ ResourceValue resValue = value.getSecond();
+
+ assert resValue != null;
+ if (resValue != null) {
+ String v = resValue.getValue();
+ if (v != null) {
+ return v;
+ }
+ }
+ }
+
+ return def;
+ }
+
+ @LayoutlibDelegate
+ static CharSequence getText(Resources resources, int id) throws NotFoundException {
+ Pair<String, ResourceValue> value = getResourceValue(resources, id, mPlatformResourceFlag);
if (value != null) {
ResourceValue resValue = value.getSecond();
@@ -237,38 +238,38 @@
}
// id was not found or not resolved. Throw a NotFoundException.
- throwException(id);
+ throwException(resources, id);
// this is not used since the method above always throws
return null;
}
- @Override
- public CharSequence[] getTextArray(int id) throws NotFoundException {
- ResourceValue resValue = getArrayResourceValue(id);
+ @LayoutlibDelegate
+ static CharSequence[] getTextArray(Resources resources, int id) throws NotFoundException {
+ ResourceValue resValue = getArrayResourceValue(resources, id);
if (resValue == null) {
// Error already logged by getArrayResourceValue.
return new CharSequence[0];
} else if (!(resValue instanceof ArrayResourceValue)) {
return new CharSequence[]{
- resolveReference(resValue.getValue(), resValue.isFramework())};
+ resolveReference(resources, resValue.getValue(), resValue.isFramework())};
}
ArrayResourceValue arv = ((ArrayResourceValue) resValue);
- return fillValues(arv, new CharSequence[arv.getElementCount()]);
+ return fillValues(resources, arv, new CharSequence[arv.getElementCount()]);
}
- @Override
- public String[] getStringArray(int id) throws NotFoundException {
- ResourceValue resValue = getArrayResourceValue(id);
+ @LayoutlibDelegate
+ static String[] getStringArray(Resources resources, int id) throws NotFoundException {
+ ResourceValue resValue = getArrayResourceValue(resources, id);
if (resValue == null) {
// Error already logged by getArrayResourceValue.
return new String[0];
} else if (!(resValue instanceof ArrayResourceValue)) {
return new String[]{
- resolveReference(resValue.getValue(), resValue.isFramework())};
+ resolveReference(resources, resValue.getValue(), resValue.isFramework())};
}
ArrayResourceValue arv = ((ArrayResourceValue) resValue);
- return fillValues(arv, new String[arv.getElementCount()]);
+ return fillValues(resources, arv, new String[arv.getElementCount()]);
}
/**
@@ -276,25 +277,26 @@
* always Strings. The ideal signature for the method should be <T super String>, but java
* generics don't support it.
*/
- <T extends CharSequence> T[] fillValues(ArrayResourceValue resValue, T[] values) {
+ static <T extends CharSequence> T[] fillValues(Resources resources, ArrayResourceValue resValue,
+ T[] values) {
int i = 0;
for (Iterator<String> iterator = resValue.iterator(); iterator.hasNext(); i++) {
@SuppressWarnings("unchecked")
- T s = (T) resolveReference(iterator.next(), resValue.isFramework());
+ T s = (T) resolveReference(resources, iterator.next(), resValue.isFramework());
values[i] = s;
}
return values;
}
- @Override
- public int[] getIntArray(int id) throws NotFoundException {
- ResourceValue rv = getArrayResourceValue(id);
+ @LayoutlibDelegate
+ static int[] getIntArray(Resources resources, int id) throws NotFoundException {
+ ResourceValue rv = getArrayResourceValue(resources, id);
if (rv == null) {
// Error already logged by getArrayResourceValue.
return new int[0];
} else if (!(rv instanceof ArrayResourceValue)) {
// This is an older IDE that can only give us the first element of the array.
- String firstValue = resolveReference(rv.getValue(), rv.isFramework());
+ String firstValue = resolveReference(resources, rv.getValue(), rv.isFramework());
try {
return new int[]{getInt(firstValue)};
} catch (NumberFormatException e) {
@@ -308,7 +310,7 @@
int[] values = new int[resValue.getElementCount()];
int i = 0;
for (Iterator<String> iterator = resValue.iterator(); iterator.hasNext(); i++) {
- String element = resolveReference(iterator.next(), resValue.isFramework());
+ String element = resolveReference(resources, iterator.next(), resValue.isFramework());
try {
values[i] = getInt(element);
} catch (NumberFormatException e) {
@@ -328,11 +330,13 @@
* method returns the ResourceValue. This happens on older versions of the IDE, which did not
* parse the array resources properly.
* <p/>
+ *
* @throws NotFoundException if no resource if found
*/
@Nullable
- private ResourceValue getArrayResourceValue(int id) throws NotFoundException {
- Pair<String, ResourceValue> v = getResourceValue(id, mPlatformResourceFlag);
+ private static ResourceValue getArrayResourceValue(Resources resources, int id)
+ throws NotFoundException {
+ Pair<String, ResourceValue> v = getResourceValue(resources, id, mPlatformResourceFlag);
if (v != null) {
ResourceValue resValue = v.getSecond();
@@ -358,19 +362,20 @@
}
// id was not found or not resolved. Throw a NotFoundException.
- throwException(id);
+ throwException(resources, id);
// this is not used since the method above always throws
return null;
}
@NonNull
- private String resolveReference(@NonNull String ref, boolean forceFrameworkOnly) {
+ private static String resolveReference(Resources resources, @NonNull String ref,
+ boolean forceFrameworkOnly) {
if (ref.startsWith(SdkConstants.PREFIX_RESOURCE_REF) || ref.startsWith
(SdkConstants.PREFIX_THEME_REF)) {
ResourceValue rv =
- mContext.getRenderResources().findResValue(ref, forceFrameworkOnly);
- rv = mContext.getRenderResources().resolveResValue(rv);
+ resources.mContext.getRenderResources().findResValue(ref, forceFrameworkOnly);
+ rv = resources.mContext.getRenderResources().resolveResValue(rv);
if (rv != null) {
return rv.getValue();
} else {
@@ -382,9 +387,9 @@
return ref;
}
- @Override
- public XmlResourceParser getLayout(int id) throws NotFoundException {
- Pair<String, ResourceValue> v = getResourceValue(id, mPlatformResourceFlag);
+ @LayoutlibDelegate
+ static XmlResourceParser getLayout(Resources resources, int id) throws NotFoundException {
+ Pair<String, ResourceValue> v = getResourceValue(resources, id, mPlatformResourceFlag);
if (v != null) {
ResourceValue value = v.getSecond();
@@ -392,8 +397,8 @@
try {
// check if the current parser can provide us with a custom parser.
- if (mPlatformResourceFlag[0] == false) {
- parser = mLayoutlibCallback.getParser(value);
+ if (!mPlatformResourceFlag[0]) {
+ parser = resources.mLayoutlibCallback.getParser(value);
}
// create a new one manually if needed.
@@ -407,7 +412,8 @@
}
if (parser != null) {
- return new BridgeXmlBlockParser(parser, mContext, mPlatformResourceFlag[0]);
+ return new BridgeXmlBlockParser(parser, resources.mContext,
+ mPlatformResourceFlag[0]);
}
} catch (XmlPullParserException e) {
Bridge.getLog().error(LayoutLog.TAG_BROKEN,
@@ -420,19 +426,19 @@
}
// id was not found or not resolved. Throw a NotFoundException.
- throwException(id);
+ throwException(resources, 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);
+ @LayoutlibDelegate
+ static XmlResourceParser getAnimation(Resources resources, int id) throws NotFoundException {
+ Pair<String, ResourceValue> v = getResourceValue(resources, id, mPlatformResourceFlag);
if (v != null) {
ResourceValue value = v.getSecond();
- XmlPullParser parser = null;
+ XmlPullParser parser;
try {
File xml = new File(value.getValue());
@@ -441,7 +447,8 @@
// give that to our XmlBlockParser
parser = ParserFactory.create(xml);
- return new BridgeXmlBlockParser(parser, mContext, mPlatformResourceFlag[0]);
+ return new BridgeXmlBlockParser(parser, resources.mContext,
+ mPlatformResourceFlag[0]);
}
} catch (XmlPullParserException e) {
Bridge.getLog().error(LayoutLog.TAG_BROKEN,
@@ -454,26 +461,31 @@
}
// id was not found or not resolved. Throw a NotFoundException.
- throwException(id);
+ throwException(resources, 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);
+ @LayoutlibDelegate
+ static TypedArray obtainAttributes(Resources resources, AttributeSet set, int[] attrs) {
+ return resources.mContext.obtainStyledAttributes(set, attrs);
}
- @Override
- public TypedArray obtainTypedArray(int id) throws NotFoundException {
+ @LayoutlibDelegate
+ static TypedArray obtainAttributes(Resources resources, Resources.Theme theme, AttributeSet
+ set, int[] attrs) {
+ return Resources.obtainAttributes_Original(resources, theme, set, attrs);
+ }
+
+ @LayoutlibDelegate
+ static TypedArray obtainTypedArray(Resources resources, int id) throws NotFoundException {
throw new UnsupportedOperationException();
}
-
- @Override
- public float getDimension(int id) throws NotFoundException {
- Pair<String, ResourceValue> value = getResourceValue(id, mPlatformResourceFlag);
+ @LayoutlibDelegate
+ static float getDimension(Resources resources, int id) throws NotFoundException {
+ Pair<String, ResourceValue> value = getResourceValue(resources, id, mPlatformResourceFlag);
if (value != null) {
ResourceValue resValue = value.getSecond();
@@ -488,26 +500,26 @@
} else if (v.equals(BridgeConstants.WRAP_CONTENT)) {
return LayoutParams.WRAP_CONTENT;
}
-
+ TypedValue tmpValue = new TypedValue();
if (ResourceHelper.parseFloatAttribute(
- value.getFirst(), v, mTmpValue, true /*requireUnit*/) &&
- mTmpValue.type == TypedValue.TYPE_DIMENSION) {
- return mTmpValue.getDimension(getDisplayMetrics());
+ value.getFirst(), v, tmpValue, true /*requireUnit*/) &&
+ tmpValue.type == TypedValue.TYPE_DIMENSION) {
+ return tmpValue.getDimension(resources.getDisplayMetrics());
}
}
}
}
// id was not found or not resolved. Throw a NotFoundException.
- throwException(id);
+ throwException(resources, 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);
+ @LayoutlibDelegate
+ static int getDimensionPixelOffset(Resources resources, int id) throws NotFoundException {
+ Pair<String, ResourceValue> value = getResourceValue(resources, id, mPlatformResourceFlag);
if (value != null) {
ResourceValue resValue = value.getSecond();
@@ -516,26 +528,27 @@
if (resValue != null) {
String v = resValue.getValue();
if (v != null) {
+ TypedValue tmpValue = new TypedValue();
if (ResourceHelper.parseFloatAttribute(
- value.getFirst(), v, mTmpValue, true /*requireUnit*/) &&
- mTmpValue.type == TypedValue.TYPE_DIMENSION) {
- return TypedValue.complexToDimensionPixelOffset(mTmpValue.data,
- getDisplayMetrics());
+ value.getFirst(), v, tmpValue, true /*requireUnit*/) &&
+ tmpValue.type == TypedValue.TYPE_DIMENSION) {
+ return TypedValue.complexToDimensionPixelOffset(tmpValue.data,
+ resources.getDisplayMetrics());
}
}
}
}
// id was not found or not resolved. Throw a NotFoundException.
- throwException(id);
+ throwException(resources, 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);
+ @LayoutlibDelegate
+ static int getDimensionPixelSize(Resources resources, int id) throws NotFoundException {
+ Pair<String, ResourceValue> value = getResourceValue(resources, id, mPlatformResourceFlag);
if (value != null) {
ResourceValue resValue = value.getSecond();
@@ -544,26 +557,27 @@
if (resValue != null) {
String v = resValue.getValue();
if (v != null) {
+ TypedValue tmpValue = new TypedValue();
if (ResourceHelper.parseFloatAttribute(
- value.getFirst(), v, mTmpValue, true /*requireUnit*/) &&
- mTmpValue.type == TypedValue.TYPE_DIMENSION) {
- return TypedValue.complexToDimensionPixelSize(mTmpValue.data,
- getDisplayMetrics());
+ value.getFirst(), v, tmpValue, true /*requireUnit*/) &&
+ tmpValue.type == TypedValue.TYPE_DIMENSION) {
+ return TypedValue.complexToDimensionPixelSize(tmpValue.data,
+ resources.getDisplayMetrics());
}
}
}
}
// id was not found or not resolved. Throw a NotFoundException.
- throwException(id);
+ throwException(resources, 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);
+ @LayoutlibDelegate
+ static int getInteger(Resources resources, int id) throws NotFoundException {
+ Pair<String, ResourceValue> value = getResourceValue(resources, id, mPlatformResourceFlag);
if (value != null) {
ResourceValue resValue = value.getSecond();
@@ -582,15 +596,15 @@
}
// id was not found or not resolved. Throw a NotFoundException.
- throwException(id);
+ throwException(resources, id);
// this is not used since the method above always throws
return 0;
}
- @Override
- public boolean getBoolean(int id) throws NotFoundException {
- Pair<String, ResourceValue> value = getResourceValue(id, mPlatformResourceFlag);
+ @LayoutlibDelegate
+ static boolean getBoolean(Resources resources, int id) throws NotFoundException {
+ Pair<String, ResourceValue> value = getResourceValue(resources, id, mPlatformResourceFlag);
if (value != null) {
ResourceValue resValue = value.getSecond();
@@ -605,61 +619,62 @@
}
// id was not found or not resolved. Throw a NotFoundException.
- throwException(id);
+ throwException(resources, id);
// this is not used since the method above always throws
return false;
}
- @Override
- public String getResourceEntryName(int resid) throws NotFoundException {
+ @LayoutlibDelegate
+ static String getResourceEntryName(Resources resources, int resid) throws NotFoundException {
throw new UnsupportedOperationException();
}
- @Override
- public String getResourceName(int resid) throws NotFoundException {
+ @LayoutlibDelegate
+ static String getResourceName(Resources resources, int resid) throws NotFoundException {
throw new UnsupportedOperationException();
}
- @Override
- public String getResourceTypeName(int resid) throws NotFoundException {
+ @LayoutlibDelegate
+ static String getResourceTypeName(Resources resources, int resid) throws NotFoundException {
throw new UnsupportedOperationException();
}
- @Override
- public String getString(int id, Object... formatArgs) throws NotFoundException {
- String s = getString(id);
+ @LayoutlibDelegate
+ static String getString(Resources resources, int id, Object... formatArgs)
+ throws NotFoundException {
+ String s = getString(resources, id);
if (s != null) {
return String.format(s, formatArgs);
}
// id was not found or not resolved. Throw a NotFoundException.
- throwException(id);
+ throwException(resources, 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);
+ @LayoutlibDelegate
+ static String getString(Resources resources, int id) throws NotFoundException {
+ Pair<String, ResourceValue> value = getResourceValue(resources, 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);
+ throwException(resources, id);
// this is not used since the method above always throws
return null;
}
- @Override
- public void getValue(int id, TypedValue outValue, boolean resolveRefs)
+ @LayoutlibDelegate
+ static void getValue(Resources resources, int id, TypedValue outValue, boolean resolveRefs)
throws NotFoundException {
- Pair<String, ResourceValue> value = getResourceValue(id, mPlatformResourceFlag);
+ Pair<String, ResourceValue> value = getResourceValue(resources, id, mPlatformResourceFlag);
if (value != null) {
ResourceValue resVal = value.getSecond();
@@ -672,7 +687,7 @@
}
if (resVal instanceof DensityBasedResourceValue) {
outValue.density =
- ((DensityBasedResourceValue) resVal).getResourceDensity().getDpiValue();
+ ((DensityBasedResourceValue) resVal).getResourceDensity().getDpiValue();
}
// else it's a string
@@ -683,18 +698,18 @@
}
// id was not found or not resolved. Throw a NotFoundException.
- throwException(id);
+ throwException(resources, id);
}
- @Override
- public void getValue(String name, TypedValue outValue, boolean resolveRefs)
+ @LayoutlibDelegate
+ static void getValue(Resources resources, 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);
+ @LayoutlibDelegate
+ static XmlResourceParser getXml(Resources resources, int id) throws NotFoundException {
+ Pair<String, ResourceValue> value = getResourceValue(resources, id, mPlatformResourceFlag);
if (value != null) {
String v = value.getSecond().getValue();
@@ -706,7 +721,8 @@
try {
XmlPullParser parser = ParserFactory.create(f);
- return new BridgeXmlBlockParser(parser, mContext, mPlatformResourceFlag[0]);
+ return new BridgeXmlBlockParser(parser, resources.mContext,
+ mPlatformResourceFlag[0]);
} catch (XmlPullParserException e) {
NotFoundException newE = new NotFoundException();
newE.initCause(e);
@@ -721,25 +737,31 @@
}
// id was not found or not resolved. Throw a NotFoundException.
- throwException(id);
+ throwException(resources, id);
// this is not used since the method above always throws
return null;
}
- @Override
- public XmlResourceParser loadXmlResourceParser(String file, int id,
+ @LayoutlibDelegate
+ static XmlResourceParser loadXmlResourceParser(Resources resources, int id,
+ String type) throws NotFoundException {
+ return resources.loadXmlResourceParser_Original(id, type);
+ }
+
+ @LayoutlibDelegate
+ static XmlResourceParser loadXmlResourceParser(Resources resources, 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);
+ getResourceValue(resources, id, mPlatformResourceFlag);
File f = new File(file);
try {
XmlPullParser parser = ParserFactory.create(f);
- return new BridgeXmlBlockParser(parser, mContext, mPlatformResourceFlag[0]);
+ return new BridgeXmlBlockParser(parser, resources.mContext, mPlatformResourceFlag[0]);
} catch (XmlPullParserException e) {
NotFoundException newE = new NotFoundException();
newE.initCause(e);
@@ -751,9 +773,9 @@
}
}
- @Override
- public InputStream openRawResource(int id) throws NotFoundException {
- Pair<String, ResourceValue> value = getResourceValue(id, mPlatformResourceFlag);
+ @LayoutlibDelegate
+ static InputStream openRawResource(Resources resources, int id) throws NotFoundException {
+ Pair<String, ResourceValue> value = getResourceValue(resources, id, mPlatformResourceFlag);
if (value != null) {
String path = value.getSecond().getValue();
@@ -780,15 +802,16 @@
}
// id was not found or not resolved. Throw a NotFoundException.
- throwException(id);
+ throwException(resources, 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);
+ @LayoutlibDelegate
+ static InputStream openRawResource(Resources resources, int id, TypedValue value) throws
+ NotFoundException {
+ getValue(resources, id, value, true);
String path = value.string.toString();
@@ -812,23 +835,27 @@
throw new NotFoundException();
}
- @Override
- public AssetFileDescriptor openRawResourceFd(int id) throws NotFoundException {
+ @LayoutlibDelegate
+ static AssetFileDescriptor openRawResourceFd(Resources resources, int id) throws
+ NotFoundException {
throw new UnsupportedOperationException();
}
/**
- * Builds and throws a {@link Resources.NotFoundException} based on a resource id and a resource type.
+ * 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 {
+ private static void throwException(Resources resources, 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 && mLayoutlibCallback != null) {
- resourceInfo = mLayoutlibCallback.resolveResourceId(id);
+ if (resourceInfo == null && resources.mLayoutlibCallback != null) {
+ resourceInfo = resources.mLayoutlibCallback.resolveResourceId(id);
}
String message;
@@ -844,7 +871,7 @@
throw new NotFoundException(message);
}
- private int getInt(String v) throws NumberFormatException {
+ private static int getInt(String v) throws NumberFormatException {
int radix = 10;
if (v.startsWith("0x")) {
v = v.substring(2);
diff --git a/tools/layoutlib/bridge/src/android/graphics/BitmapFactory_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/BitmapFactory_Delegate.java
index 8d5863b..8bd2a7a 100644
--- a/tools/layoutlib/bridge/src/android/graphics/BitmapFactory_Delegate.java
+++ b/tools/layoutlib/bridge/src/android/graphics/BitmapFactory_Delegate.java
@@ -23,7 +23,7 @@
import com.android.tools.layoutlib.annotations.LayoutlibDelegate;
import android.annotation.Nullable;
-import android.content.res.BridgeResources.NinePatchInputStream;
+import com.android.layoutlib.bridge.util.NinePatchInputStream;
import android.graphics.BitmapFactory.Options;
import android.graphics.Bitmap_Delegate.BitmapCreateFlags;
diff --git a/tools/layoutlib/bridge/src/android/graphics/Path_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/Path_Delegate.java
index d0dd22f..a10ac00 100644
--- a/tools/layoutlib/bridge/src/android/graphics/Path_Delegate.java
+++ b/tools/layoutlib/bridge/src/android/graphics/Path_Delegate.java
@@ -388,21 +388,18 @@
@LayoutlibDelegate
/*package*/ static void native_addRoundRect(long nPath, float left, float top, float right,
float bottom, float[] radii, int dir) {
- // Java2D doesn't support different rounded corners in each corner, so just use the
- // first value.
- native_addRoundRect(nPath, left, top, right, bottom, radii[0], radii[1], dir);
- // there can be a case where this API is used but with similar values for all corners, so
- // in that case we don't warn.
- // we only care if 2 corners are different so just compare to the next one.
- for (int i = 0 ; i < 3 ; i++) {
- if (radii[i * 2] != radii[(i + 1) * 2] || radii[i * 2 + 1] != radii[(i + 1) * 2 + 1]) {
- Bridge.getLog().fidelityWarning(LayoutLog.TAG_UNSUPPORTED,
- "Different corner sizes are not supported in Path.addRoundRect.",
- null, null /*data*/);
- break;
- }
+ Path_Delegate pathDelegate = sManager.getDelegate(nPath);
+ if (pathDelegate == null) {
+ return;
}
+
+ float[] cornerDimensions = new float[radii.length];
+ for (int i = 0; i < radii.length; i++) {
+ cornerDimensions[i] = 2 * radii[i];
+ }
+ pathDelegate.mPath.append(new RoundRectangle(left, top, right - left, bottom - top,
+ cornerDimensions), false);
}
@LayoutlibDelegate
diff --git a/tools/layoutlib/bridge/src/android/graphics/RoundRectangle.java b/tools/layoutlib/bridge/src/android/graphics/RoundRectangle.java
new file mode 100644
index 0000000..edd36e5
--- /dev/null
+++ b/tools/layoutlib/bridge/src/android/graphics/RoundRectangle.java
@@ -0,0 +1,370 @@
+/*
+ * Copyright (C) 2016 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.graphics;
+
+import java.awt.geom.AffineTransform;
+import java.awt.geom.PathIterator;
+import java.awt.geom.Rectangle2D;
+import java.awt.geom.RectangularShape;
+import java.awt.geom.RoundRectangle2D;
+import java.util.EnumSet;
+import java.util.NoSuchElementException;
+
+/**
+ * Defines a rectangle with rounded corners, where the sizes of the corners
+ * are potentially different.
+ */
+public class RoundRectangle extends RectangularShape {
+ public double x;
+ public double y;
+ public double width;
+ public double height;
+ public double ulWidth;
+ public double ulHeight;
+ public double urWidth;
+ public double urHeight;
+ public double lrWidth;
+ public double lrHeight;
+ public double llWidth;
+ public double llHeight;
+
+ private enum Zone {
+ CLOSE_OUTSIDE,
+ CLOSE_INSIDE,
+ MIDDLE,
+ FAR_INSIDE,
+ FAR_OUTSIDE
+ }
+
+ private final EnumSet<Zone> close = EnumSet.of(Zone.CLOSE_OUTSIDE, Zone.CLOSE_INSIDE);
+ private final EnumSet<Zone> far = EnumSet.of(Zone.FAR_OUTSIDE, Zone.FAR_INSIDE);
+
+ /**
+ * @param cornerDimensions array of 8 floating-point number corresponding to the width and
+ * the height of each corner in the following order: upper-left, upper-right, lower-right,
+ * lower-left. It assumes for the size the same convention as {@link RoundRectangle2D}, that
+ * is that the width and height of a corner correspond to the total width and height of the
+ * ellipse that corner is a quarter of.
+ */
+ public RoundRectangle(float x, float y, float width, float height, float[] cornerDimensions) {
+ if (cornerDimensions.length != 8) {
+ throw new IllegalArgumentException("The array of corner dimensions must have eight " +
+ "elements");
+ }
+
+ this.x = x;
+ this.y = y;
+ this.width = width;
+ this.height = height;
+
+ float[] dimensions = cornerDimensions.clone();
+ // If a value is negative, the corresponding corner is squared
+ for (int i = 0; i < dimensions.length; i += 2) {
+ if (dimensions[i] < 0 || dimensions[i + 1] < 0) {
+ dimensions[i] = 0;
+ dimensions[i + 1] = 0;
+ }
+ }
+
+ double topCornerWidth = (dimensions[0] + dimensions[2]) / 2d;
+ double bottomCornerWidth = (dimensions[4] + dimensions[6]) / 2d;
+ double leftCornerHeight = (dimensions[1] + dimensions[7]) / 2d;
+ double rightCornerHeight = (dimensions[3] + dimensions[5]) / 2d;
+
+ // Rescale the corner dimensions if they are bigger than the rectangle
+ double scale = Math.min(1.0, width / topCornerWidth);
+ scale = Math.min(scale, width / bottomCornerWidth);
+ scale = Math.min(scale, height / leftCornerHeight);
+ scale = Math.min(scale, height / rightCornerHeight);
+
+ this.ulWidth = dimensions[0] * scale;
+ this.ulHeight = dimensions[1] * scale;
+ this.urWidth = dimensions[2] * scale;
+ this.urHeight = dimensions[3] * scale;
+ this.lrWidth = dimensions[4] * scale;
+ this.lrHeight = dimensions[5] * scale;
+ this.llWidth = dimensions[6] * scale;
+ this.llHeight = dimensions[7] * scale;
+ }
+
+ @Override
+ public double getX() {
+ return x;
+ }
+
+ @Override
+ public double getY() {
+ return y;
+ }
+
+ @Override
+ public double getWidth() {
+ return width;
+ }
+
+ @Override
+ public double getHeight() {
+ return height;
+ }
+
+ @Override
+ public boolean isEmpty() {
+ return (width <= 0d) || (height <= 0d);
+ }
+
+ @Override
+ public void setFrame(double x, double y, double w, double h) {
+ this.x = x;
+ this.y = y;
+ this.width = w;
+ this.height = h;
+ }
+
+ @Override
+ public Rectangle2D getBounds2D() {
+ return new Rectangle2D.Double(x, y, width, height);
+ }
+
+ @Override
+ public boolean contains(double x, double y) {
+ if (isEmpty()) {
+ return false;
+ }
+
+ double x0 = getX();
+ double y0 = getY();
+ double x1 = x0 + getWidth();
+ double y1 = y0 + getHeight();
+ // Check for trivial rejection - point is outside bounding rectangle
+ if (x < x0 || y < y0 || x >= x1 || y >= y1) {
+ return false;
+ }
+
+ double insideTopX0 = x0 + ulWidth / 2d;
+ double insideLeftY0 = y0 + ulHeight / 2d;
+ if (x < insideTopX0 && y < insideLeftY0) {
+ // In the upper-left corner
+ return isInsideCorner(x - insideTopX0, y - insideLeftY0, ulWidth / 2d, ulHeight / 2d);
+ }
+
+ double insideTopX1 = x1 - urWidth / 2d;
+ double insideRightY0 = y0 + urHeight / 2d;
+ if (x > insideTopX1 && y < insideRightY0) {
+ // In the upper-right corner
+ return isInsideCorner(x - insideTopX1, y - insideRightY0, urWidth / 2d, urHeight / 2d);
+ }
+
+ double insideBottomX1 = x1 - lrWidth / 2d;
+ double insideRightY1 = y1 - lrHeight / 2d;
+ if (x > insideBottomX1 && y > insideRightY1) {
+ // In the lower-right corner
+ return isInsideCorner(x - insideBottomX1, y - insideRightY1, lrWidth / 2d,
+ lrHeight / 2d);
+ }
+
+ double insideBottomX0 = x0 + llWidth / 2d;
+ double insideLeftY1 = y1 - llHeight / 2d;
+ if (x < insideBottomX0 && y > insideLeftY1) {
+ // In the lower-left corner
+ return isInsideCorner(x - insideBottomX0, y - insideLeftY1, llWidth / 2d,
+ llHeight / 2d);
+ }
+
+ // In the central part of the rectangle
+ return true;
+ }
+
+ private boolean isInsideCorner(double x, double y, double width, double height) {
+ double squareDist = height * height * x * x + width * width * y * y;
+ return squareDist <= width * width * height * height;
+ }
+
+ private Zone classify(double coord, double side1, double arcSize1, double side2,
+ double arcSize2) {
+ if (coord < side1) {
+ return Zone.CLOSE_OUTSIDE;
+ } else if (coord < side1 + arcSize1) {
+ return Zone.CLOSE_INSIDE;
+ } else if (coord < side2 - arcSize2) {
+ return Zone.MIDDLE;
+ } else if (coord < side2) {
+ return Zone.FAR_INSIDE;
+ } else {
+ return Zone.FAR_OUTSIDE;
+ }
+ }
+
+ public boolean intersects(double x, double y, double w, double h) {
+ if (isEmpty() || w <= 0 || h <= 0) {
+ return false;
+ }
+ double x0 = getX();
+ double y0 = getY();
+ double x1 = x0 + getWidth();
+ double y1 = y0 + getHeight();
+ // Check for trivial rejection - bounding rectangles do not intersect
+ if (x + w <= x0 || x >= x1 || y + h <= y0 || y >= y1) {
+ return false;
+ }
+
+ double maxLeftCornerWidth = Math.max(ulWidth, llWidth) / 2d;
+ double maxRightCornerWidth = Math.max(urWidth, lrWidth) / 2d;
+ double maxUpperCornerHeight = Math.max(ulHeight, urHeight) / 2d;
+ double maxLowerCornerHeight = Math.max(llHeight, lrHeight) / 2d;
+ Zone x0class = classify(x, x0, maxLeftCornerWidth, x1, maxRightCornerWidth);
+ Zone x1class = classify(x + w, x0, maxLeftCornerWidth, x1, maxRightCornerWidth);
+ Zone y0class = classify(y, y0, maxUpperCornerHeight, y1, maxLowerCornerHeight);
+ Zone y1class = classify(y + h, y0, maxUpperCornerHeight, y1, maxLowerCornerHeight);
+
+ // Trivially accept if any point is inside inner rectangle
+ if (x0class == Zone.MIDDLE || x1class == Zone.MIDDLE || y0class == Zone.MIDDLE || y1class == Zone.MIDDLE) {
+ return true;
+ }
+ // Trivially accept if either edge spans inner rectangle
+ if ((close.contains(x0class) && far.contains(x1class)) || (close.contains(y0class) &&
+ far.contains(y1class))) {
+ return true;
+ }
+
+ // Since neither edge spans the center, then one of the corners
+ // must be in one of the rounded edges. We detect this case if
+ // a [xy]0class is 3 or a [xy]1class is 1. One of those two cases
+ // must be true for each direction.
+ // We now find a "nearest point" to test for being inside a rounded
+ // corner.
+ if (x1class == Zone.CLOSE_INSIDE && y1class == Zone.CLOSE_INSIDE) {
+ // Potentially in upper-left corner
+ x = x + w - x0 - ulWidth / 2d;
+ y = y + h - y0 - ulHeight / 2d;
+ return x > 0 || y > 0 || isInsideCorner(x, y, ulWidth / 2d, ulHeight / 2d);
+ }
+ if (x1class == Zone.CLOSE_INSIDE) {
+ // Potentially in lower-left corner
+ x = x + w - x0 - llWidth / 2d;
+ y = y - y1 + llHeight / 2d;
+ return x > 0 || y < 0 || isInsideCorner(x, y, llWidth / 2d, llHeight / 2d);
+ }
+ if (y1class == Zone.CLOSE_INSIDE) {
+ //Potentially in the upper-right corner
+ x = x - x1 + urWidth / 2d;
+ y = y + h - y0 - urHeight / 2d;
+ return x < 0 || y > 0 || isInsideCorner(x, y, urWidth / 2d, urHeight / 2d);
+ }
+ // Potentially in the lower-right corner
+ x = x - x1 + lrWidth / 2d;
+ y = y - y1 + lrHeight / 2d;
+ return x < 0 || y < 0 || isInsideCorner(x, y, lrWidth / 2d, lrHeight / 2d);
+ }
+
+ @Override
+ public boolean contains(double x, double y, double w, double h) {
+ if (isEmpty() || w <= 0 || h <= 0) {
+ return false;
+ }
+ return (contains(x, y) &&
+ contains(x + w, y) &&
+ contains(x, y + h) &&
+ contains(x + w, y + h));
+ }
+
+ @Override
+ public PathIterator getPathIterator(final AffineTransform at) {
+ return new PathIterator() {
+ int index;
+
+ // ArcIterator.btan(Math.PI/2)
+ public static final double CtrlVal = 0.5522847498307933;
+ private final double ncv = 1.0 - CtrlVal;
+
+ // Coordinates of control points for Bezier curves approximating the straight lines
+ // and corners of the rounded rectangle.
+ private final double[][] ctrlpts = {
+ {0.0, 0.0, 0.0, ulHeight},
+ {0.0, 0.0, 1.0, -llHeight},
+ {0.0, 0.0, 1.0, -llHeight * ncv, 0.0, ncv * llWidth, 1.0, 0.0, 0.0, llWidth,
+ 1.0, 0.0},
+ {1.0, -lrWidth, 1.0, 0.0},
+ {1.0, -lrWidth * ncv, 1.0, 0.0, 1.0, 0.0, 1.0, -lrHeight * ncv, 1.0, 0.0, 1.0,
+ -lrHeight},
+ {1.0, 0.0, 0.0, urHeight},
+ {1.0, 0.0, 0.0, ncv * urHeight, 1.0, -urWidth * ncv, 0.0, 0.0, 1.0, -urWidth,
+ 0.0, 0.0},
+ {0.0, ulWidth, 0.0, 0.0},
+ {0.0, ncv * ulWidth, 0.0, 0.0, 0.0, 0.0, 0.0, ncv * ulHeight, 0.0, 0.0, 0.0,
+ ulHeight},
+ {}
+ };
+ private final int[] types = {
+ SEG_MOVETO,
+ SEG_LINETO, SEG_CUBICTO,
+ SEG_LINETO, SEG_CUBICTO,
+ SEG_LINETO, SEG_CUBICTO,
+ SEG_LINETO, SEG_CUBICTO,
+ SEG_CLOSE,
+ };
+
+ @Override
+ public int getWindingRule() {
+ return WIND_NON_ZERO;
+ }
+
+ @Override
+ public boolean isDone() {
+ return index >= ctrlpts.length;
+ }
+
+ @Override
+ public void next() {
+ index++;
+ }
+
+ @Override
+ public int currentSegment(float[] coords) {
+ if (isDone()) {
+ throw new NoSuchElementException("roundrect iterator out of bounds");
+ }
+ int nc = 0;
+ double ctrls[] = ctrlpts[index];
+ for (int i = 0; i < ctrls.length; i += 4) {
+ coords[nc++] = (float) (x + ctrls[i] * width + ctrls[i + 1] / 2d);
+ coords[nc++] = (float) (y + ctrls[i + 2] * height + ctrls[i + 3] / 2d);
+ }
+ if (at != null) {
+ at.transform(coords, 0, coords, 0, nc / 2);
+ }
+ return types[index];
+ }
+
+ @Override
+ public int currentSegment(double[] coords) {
+ if (isDone()) {
+ throw new NoSuchElementException("roundrect iterator out of bounds");
+ }
+ int nc = 0;
+ double ctrls[] = ctrlpts[index];
+ for (int i = 0; i < ctrls.length; i += 4) {
+ coords[nc++] = x + ctrls[i] * width + ctrls[i + 1] / 2d;
+ coords[nc++] = y + ctrls[i + 2] * height + ctrls[i + 3] / 2d;
+ }
+ if (at != null) {
+ at.transform(coords, 0, coords, 0, nc / 2);
+ }
+ return types[index];
+ }
+ };
+ }
+}
diff --git a/tools/layoutlib/bridge/src/android/view/AttachInfo_Accessor.java b/tools/layoutlib/bridge/src/android/view/AttachInfo_Accessor.java
index 4901f72..94f3f54 100644
--- a/tools/layoutlib/bridge/src/android/view/AttachInfo_Accessor.java
+++ b/tools/layoutlib/bridge/src/android/view/AttachInfo_Accessor.java
@@ -45,4 +45,10 @@
public static void dispatchOnPreDraw(View view) {
view.mAttachInfo.mTreeObserver.dispatchOnPreDraw();
}
+
+ public static void detachFromWindow(View view) {
+ if (view != null) {
+ view.dispatchDetachedFromWindow();
+ }
+ }
}
diff --git a/tools/layoutlib/bridge/src/android/view/ViewRootImpl_RunQueue_Delegate.java b/tools/layoutlib/bridge/src/android/view/ViewRootImpl_RunQueue_Delegate.java
new file mode 100644
index 0000000..51b42a6
--- /dev/null
+++ b/tools/layoutlib/bridge/src/android/view/ViewRootImpl_RunQueue_Delegate.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2016 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.view;
+
+import com.android.tools.layoutlib.annotations.LayoutlibDelegate;
+
+/**
+ * Delegate used to provide new implementation of a select few methods of
+ * {@link ViewRootImpl.RunQueue}
+ *
+ * Through the layoutlib_create tool, the original methods of ViewRootImpl.RunQueue have been
+ * replaced by calls to methods of the same name in this delegate class.
+ *
+ */
+public class ViewRootImpl_RunQueue_Delegate {
+
+ @LayoutlibDelegate
+ /*package*/ static void postDelayed(ViewRootImpl.RunQueue thisQueue, Runnable action, long
+ delayMillis) {
+ // The actual RunQueue is never run and therefore never cleared. This method avoids
+ // runnables to be added to the RunQueue so they do not leak resources.
+ }
+}
diff --git a/tools/layoutlib/bridge/src/android/widget/SimpleMonthView_Delegate.java b/tools/layoutlib/bridge/src/android/widget/SimpleMonthView_Delegate.java
deleted file mode 100644
index 8e41e51..0000000
--- a/tools/layoutlib/bridge/src/android/widget/SimpleMonthView_Delegate.java
+++ /dev/null
@@ -1,99 +0,0 @@
-/*
- * Copyright (C) 2015 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.widget;
-
-import com.android.tools.layoutlib.annotations.LayoutlibDelegate;
-
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.icu.text.SimpleDateFormat;
-import android.text.format.DateFormat;
-
-import java.util.Calendar;
-import java.util.Locale;
-
-/**
- * Delegate that provides implementation for some methods in {@link SimpleMonthView}.
- * <p/>
- * Through the layoutlib_create tool, selected methods of SimpleMonthView have been replaced by
- * calls to methods of the same name in this delegate class.
- * <p/>
- * The main purpose of this class is to use {@link android.icu.text.SimpleDateFormat} instead of
- * {@link java.text.SimpleDateFormat}.
- */
-public class SimpleMonthView_Delegate {
-
- private static final String DEFAULT_TITLE_FORMAT = "MMMMy";
- private static final String DAY_OF_WEEK_FORMAT = "EEEEE";
-
- // Maintain a cache of the last view used, so that the formatters can be reused.
- @Nullable private static SimpleMonthView sLastView;
- @Nullable private static SimpleMonthView_Delegate sLastDelegate;
-
- private SimpleDateFormat mTitleFormatter;
- private SimpleDateFormat mDayOfWeekFormatter;
-
- private Locale locale;
-
- @LayoutlibDelegate
- /*package*/ static CharSequence getTitle(SimpleMonthView view) {
- if (view.mTitle == null) {
- SimpleMonthView_Delegate delegate = getDelegate(view);
- if (delegate.mTitleFormatter == null) {
- delegate.mTitleFormatter = new SimpleDateFormat(DateFormat.getBestDateTimePattern(
- getLocale(delegate, view), DEFAULT_TITLE_FORMAT));
- }
- view.mTitle = delegate.mTitleFormatter.format(view.mCalendar.getTime());
- }
- return view.mTitle;
- }
-
- @LayoutlibDelegate
- /*package*/ static String getDayOfWeekLabel(SimpleMonthView view, int dayOfWeek) {
- view.mDayOfWeekLabelCalendar.set(Calendar.DAY_OF_WEEK, dayOfWeek);
- SimpleMonthView_Delegate delegate = getDelegate(view);
- if (delegate.mDayOfWeekFormatter == null) {
- delegate.mDayOfWeekFormatter =
- new SimpleDateFormat(DAY_OF_WEEK_FORMAT, getLocale(delegate, view));
- }
- return delegate.mDayOfWeekFormatter.format(view.mDayOfWeekLabelCalendar.getTime());
- }
-
- private static Locale getLocale(SimpleMonthView_Delegate delegate, SimpleMonthView view) {
- if (delegate.locale == null) {
- delegate.locale = view.getContext().getResources().getConfiguration().locale;
- }
- return delegate.locale;
- }
-
- @NonNull
- private static SimpleMonthView_Delegate getDelegate(SimpleMonthView view) {
- if (view == sLastView) {
- assert sLastDelegate != null;
- return sLastDelegate;
- } else {
- sLastView = view;
- sLastDelegate = new SimpleMonthView_Delegate();
- return sLastDelegate;
- }
- }
-
- public static void clearCache() {
- sLastView = null;
- sLastDelegate = null;
- }
-}
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 2ac212c..fea633e 100644
--- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/BridgeRenderSession.java
+++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/BridgeRenderSession.java
@@ -208,6 +208,9 @@
@Override
public void dispose() {
+ if (mSession != null) {
+ mSession.dispose();
+ }
}
/*package*/ BridgeRenderSession(RenderSessionImpl scene, Result lastResult) {
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 a32128f..2fd1f18 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
@@ -52,11 +52,11 @@
import android.content.pm.PackageManager;
import android.content.res.AssetManager;
import android.content.res.BridgeAssetManager;
-import android.content.res.BridgeResources;
import android.content.res.BridgeTypedArray;
import android.content.res.Configuration;
import android.content.res.Resources;
import android.content.res.Resources.Theme;
+import android.content.res.Resources_Delegate;
import android.database.DatabaseErrorHandler;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteDatabase.CursorFactory;
@@ -167,8 +167,8 @@
RTL_ATTRS.put("?android:attr/paddingRight", "paddingEnd");
RTL_ATTRS.put("?android:attr/layout_marginLeft", "layout_marginStart");
RTL_ATTRS.put("?android:attr/layout_marginRight", "layout_marginEnd");
- RTL_ATTRS.put("?android:attr/layout_toLeft", "layout_toStartOf");
- RTL_ATTRS.put("?android:attr/layout_toRight", "layout_toEndOf");
+ RTL_ATTRS.put("?android:attr/layout_toLeftOf", "layout_toStartOf");
+ RTL_ATTRS.put("?android:attr/layout_toRightOf", "layout_toEndOf");
RTL_ATTRS.put("?android:attr/layout_alignParentLeft", "layout_alignParentStart");
RTL_ATTRS.put("?android:attr/layout_alignParentRight", "layout_alignParentEnd");
RTL_ATTRS.put("?android:attr/drawableLeft", "drawableStart");
@@ -223,7 +223,7 @@
public void initResources() {
AssetManager assetManager = AssetManager.getSystem();
- mSystemResources = BridgeResources.initSystem(
+ mSystemResources = Resources_Delegate.initSystem(
this,
assetManager,
mMetrics,
@@ -236,7 +236,7 @@
* Disposes the {@link Resources} singleton.
*/
public void disposeResources() {
- BridgeResources.disposeSystem();
+ Resources_Delegate.disposeSystem();
}
public void setBridgeInflater(BridgeInflater inflater) {
@@ -705,8 +705,8 @@
List<Pair<String, Boolean>> attributeList = searchAttrs(attrs);
- BridgeTypedArray ta = ((BridgeResources) mSystemResources).newTypeArray(attrs.length,
- isPlatformFile);
+ BridgeTypedArray ta =
+ Resources_Delegate.newTypeArray(mSystemResources, attrs.length, isPlatformFile);
// look for a custom style.
String customStyle = null;
@@ -940,7 +940,7 @@
List<Pair<String, Boolean>> attributes = searchAttrs(attrs);
- BridgeTypedArray ta = ((BridgeResources) mSystemResources).newTypeArray(attrs.length,
+ BridgeTypedArray ta = Resources_Delegate.newTypeArray(mSystemResources, attrs.length,
false);
// for each attribute, get its name so that we can search it in the style
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 6d9e872..4e4fcd0 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
@@ -36,7 +36,6 @@
import android.view.ViewConfiguration_Accessor;
import android.view.inputmethod.InputMethodManager;
import android.view.inputmethod.InputMethodManager_Accessor;
-import android.widget.SimpleMonthView_Delegate;
import java.util.Locale;
import java.util.concurrent.TimeUnit;
@@ -278,7 +277,6 @@
mContext.getRenderResources().setLogger(null);
}
ParserFactory.setParserFactory(null);
- SimpleMonthView_Delegate.clearCache();
}
public static BridgeContext getCurrentContext() {
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 ec50cfe..99af226 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
@@ -1396,4 +1396,21 @@
public RenderSession getSession() {
return mScene;
}
+
+ public void dispose() {
+ AttachInfo_Accessor.detachFromWindow(mViewRoot);
+ if (mCanvas != null) {
+ mCanvas.release();
+ mCanvas = null;
+ }
+ if (mViewInfoList != null) {
+ mViewInfoList.clear();
+ }
+ if (mSystemViewInfoList != null) {
+ mSystemViewInfoList.clear();
+ }
+ mImage = null;
+ mViewRoot = null;
+ mContentRoot = null;
+ }
}
diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/util/NinePatchInputStream.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/util/NinePatchInputStream.java
new file mode 100644
index 0000000..96b795a
--- /dev/null
+++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/util/NinePatchInputStream.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) 2016 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.util;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+
+/**
+ * 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;
+ }
+}
diff --git a/tools/layoutlib/bridge/tests/src/com/android/layoutlib/bridge/intensive/Main.java b/tools/layoutlib/bridge/tests/src/com/android/layoutlib/bridge/intensive/Main.java
index fe16a3e..6b23da7 100644
--- a/tools/layoutlib/bridge/tests/src/com/android/layoutlib/bridge/intensive/Main.java
+++ b/tools/layoutlib/bridge/tests/src/com/android/layoutlib/bridge/intensive/Main.java
@@ -291,7 +291,6 @@
@Test
public void testActivity() throws ClassNotFoundException {
renderAndVerify("activity.xml", "activity.png");
-
}
/** Test allwidgets.xml */
@@ -431,6 +430,8 @@
ImageUtils.requireSimilar(goldenImagePath, session.getImage());
} catch (IOException e) {
getLogger().error(e, e.getMessage());
+ } finally {
+ session.dispose();
}
}
diff --git a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/AsmGenerator.java b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/AsmGenerator.java
index 8f0ad01..414b255 100644
--- a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/AsmGenerator.java
+++ b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/AsmGenerator.java
@@ -367,6 +367,10 @@
ClassVisitor cv = cw;
+ // FIXME Generify
+ if ("android/content/res/Resources".equals(className)) {
+ cv = new FieldInjectorAdapter(cv);
+ }
if (mReplaceMethodCallsClasses.contains(className)) {
cv = new ReplaceMethodCallsAdapter(cv, className);
}
@@ -445,4 +449,5 @@
}
return buffer.toByteArray();
}
+
}
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 cb84a1b..e17f1f8 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
@@ -158,6 +158,31 @@
*/
public final static String[] DELEGATE_METHODS = new String[] {
"android.app.Fragment#instantiate", //(Landroid/content/Context;Ljava/lang/String;Landroid/os/Bundle;)Landroid/app/Fragment;",
+ "android.content.res.Resources#getAnimation",
+ "android.content.res.Resources#getBoolean",
+ "android.content.res.Resources#getColor",
+ "android.content.res.Resources#getColorStateList",
+ "android.content.res.Resources#getDimension",
+ "android.content.res.Resources#getDimensionPixelOffset",
+ "android.content.res.Resources#getDimensionPixelSize",
+ "android.content.res.Resources#getDrawable",
+ "android.content.res.Resources#getIntArray",
+ "android.content.res.Resources#getInteger",
+ "android.content.res.Resources#getLayout",
+ "android.content.res.Resources#getResourceEntryName",
+ "android.content.res.Resources#getResourceName",
+ "android.content.res.Resources#getResourceTypeName",
+ "android.content.res.Resources#getString",
+ "android.content.res.Resources#getStringArray",
+ "android.content.res.Resources#getText",
+ "android.content.res.Resources#getTextArray",
+ "android.content.res.Resources#getValue",
+ "android.content.res.Resources#getXml",
+ "android.content.res.Resources#loadXmlResourceParser",
+ "android.content.res.Resources#obtainAttributes",
+ "android.content.res.Resources#obtainTypedArray",
+ "android.content.res.Resources#openRawResource",
+ "android.content.res.Resources#openRawResourceFd",
"android.content.res.Resources$Theme#obtainStyledAttributes",
"android.content.res.Resources$Theme#resolveAttribute",
"android.content.res.Resources$Theme#resolveAttributes",
@@ -185,6 +210,7 @@
"android.view.View#getWindowToken",
"android.view.View#isInEditMode",
"android.view.ViewRootImpl#isInTouchMode",
+ "android.view.ViewRootImpl$RunQueue#postDelayed",
"android.view.WindowManagerGlobal#getWindowManagerService",
"android.view.inputmethod.InputMethodManager#getInstance",
"android.view.MenuInflater#registerMenu",
@@ -216,8 +242,6 @@
"android.view.RenderNode#nGetScaleY",
"android.view.RenderNode#nIsPivotExplicitlySet",
"android.view.ViewGroup#drawChild",
- "android.widget.SimpleMonthView#getTitle",
- "android.widget.SimpleMonthView#getDayOfWeekLabel",
"android.widget.TimePickerClockDelegate#getAmOrPmKeyCode",
"com.android.internal.view.menu.MenuBuilder#createNewMenuItem",
"com.android.internal.util.XmlUtils#convertValueToInt",
@@ -315,6 +339,10 @@
"java.lang.IntegralToString", "com.android.tools.layoutlib.java.IntegralToString",
"java.lang.UnsafeByteSequence", "com.android.tools.layoutlib.java.UnsafeByteSequence",
"java.nio.charset.StandardCharsets", "com.android.tools.layoutlib.java.Charsets",
+ // Use android.icu.text versions of DateFormat and SimpleDateFormat since the
+ // original ones do not match the Android implementation
+ "java.text.DateFormat", "android.icu.text.DateFormat",
+ "java.text.SimpleDateFormat", "android.icu.text.SimpleDateFormat",
};
private final static String[] EXCLUDED_CLASSES =
diff --git a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/FieldInjectorAdapter.java b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/FieldInjectorAdapter.java
new file mode 100644
index 0000000..4608a84
--- /dev/null
+++ b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/FieldInjectorAdapter.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2016 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.tools.layoutlib.create;
+
+import org.objectweb.asm.ClassVisitor;
+import org.objectweb.asm.Opcodes;
+
+/**
+ * Injects fields in a class.
+ * <p>
+ * TODO: Generify
+ */
+public class FieldInjectorAdapter extends ClassVisitor {
+ public FieldInjectorAdapter(ClassVisitor cv) {
+ super(Opcodes.ASM4, cv);
+ }
+
+ @Override
+ public void visitEnd() {
+ super.visitField(Opcodes.ACC_PUBLIC, "mLayoutlibCallback",
+ "Lcom/android/ide/common/rendering/api/LayoutlibCallback;", null, null);
+ super.visitField(Opcodes.ACC_PUBLIC, "mContext",
+ "Lcom/android/layoutlib/bridge/android/BridgeContext;", null, null);
+ super.visitEnd();
+ }
+}