diff options
author | android-build-team Robot <android-build-team-robot@google.com> | 2017-05-21 07:30:14 +0000 |
---|---|---|
committer | android-build-team Robot <android-build-team-robot@google.com> | 2017-05-21 07:30:14 +0000 |
commit | 9dd04aeb909e03c82375e7a9439d624fd999fcba (patch) | |
tree | bf101c39f3a000faab5c87c58f5afb9c45797095 | |
parent | d1dcc2259c376d8e90f5ba65de68c7f4d0c68dcf (diff) | |
parent | c56293b035c7a73188b709b7effc340a022e46f4 (diff) | |
download | layoutlib-9dd04aeb909e03c82375e7a9439d624fd999fcba.tar.gz |
release-request-8aff77ce-b1d4-44c8-abb8-c39db5d1e998-for-git_oc-dr1-release-4029936 snap-temp-L09300000066301094
Change-Id: I61a7fbbfd86d14f7b7ed65618dc10530b43a2efc
21 files changed, 483 insertions, 137 deletions
diff --git a/.idea/modules.xml b/.idea/modules.xml index 6ffc1cc12b..4654a5c019 100644 --- a/.idea/modules.xml +++ b/.idea/modules.xml @@ -3,6 +3,7 @@ <component name="ProjectModuleManager"> <modules> <module fileurl="file://$PROJECT_DIR$/bridge/bridge.iml" filepath="$PROJECT_DIR$/bridge/bridge.iml" /> + <module fileurl="file://$PROJECT_DIR$/common/common.iml" filepath="$PROJECT_DIR$/common/common.iml" /> <module fileurl="file://$PROJECT_DIR$/create/create.iml" filepath="$PROJECT_DIR$/create/create.iml" /> <module fileurl="file://$PROJECT_DIR$/legacy/legacy.iml" filepath="$PROJECT_DIR$/legacy/legacy.iml" /> <module fileurl="file://$PROJECT_DIR$/studio-custom-widgets/studio-android-widgets.iml" filepath="$PROJECT_DIR$/studio-custom-widgets/studio-android-widgets.iml" /> diff --git a/bridge/Android.mk b/bridge/Android.mk index 3dd8002bcf..333e6bbc53 100644 --- a/bridge/Android.mk +++ b/bridge/Android.mk @@ -25,7 +25,8 @@ LOCAL_JAVA_LIBRARIES := \ LOCAL_STATIC_JAVA_LIBRARIES := \ temp_layoutlib \ - ninepatch-prebuilt + ninepatch-prebuilt \ + layoutlib-common LOCAL_MODULE := layoutlib diff --git a/bridge/bridge.iml b/bridge/bridge.iml index 45bb933996..90de760f41 100644 --- a/bridge/bridge.iml +++ b/bridge/bridge.iml @@ -88,5 +88,6 @@ <orderEntry type="library" scope="TEST" name="junit" level="project" /> <orderEntry type="library" scope="TEST" name="mockito" level="project" /> <orderEntry type="library" scope="TEST" name="objenesis" level="project" /> + <orderEntry type="module" module-name="common" /> </component> </module>
\ No newline at end of file diff --git a/bridge/src/android/content/res/BridgeTypedArray.java b/bridge/src/android/content/res/BridgeTypedArray.java index b5996afd79..69242ac170 100644 --- a/bridge/src/android/content/res/BridgeTypedArray.java +++ b/bridge/src/android/content/res/BridgeTypedArray.java @@ -70,6 +70,7 @@ public final class BridgeTypedArray extends TypedArray { private final BridgeContext mContext; private final boolean mPlatformFile; + private final int[] mResourceId; private final ResourceValue[] mResourceData; private final String[] mNames; private final boolean[] mIsFramework; @@ -85,6 +86,7 @@ public final class BridgeTypedArray extends TypedArray { mBridgeResources = resources; mContext = context; mPlatformFile = platformFile; + mResourceId = new int[len]; mResourceData = new ResourceValue[len]; mNames = new String[len]; mIsFramework = new boolean[len]; @@ -95,9 +97,12 @@ public final class BridgeTypedArray extends TypedArray { * @param index the index of the value in the TypedArray * @param name the name of the attribute * @param isFramework whether the attribute is in the android namespace. + * @param resourceId the reference id of this resource * @param value the value of the attribute */ - public void bridgeSetValue(int index, String name, boolean isFramework, ResourceValue value) { + public void bridgeSetValue(int index, String name, boolean isFramework, int resourceId, + ResourceValue value) { + mResourceId[index] = resourceId; mResourceData[index] = value; mNames[index] = name; mIsFramework[index] = isFramework; @@ -105,7 +110,7 @@ public final class BridgeTypedArray extends TypedArray { /** * Seals the array after all calls to - * {@link #bridgeSetValue(int, String, boolean, ResourceValue)} have been done. + * {@link #bridgeSetValue(int, String, boolean, int, ResourceValue)} have been done. * <p/>This allows to compute the list of non default values, permitting * {@link #getIndexCount()} to return the proper value. */ @@ -788,6 +793,9 @@ public final class BridgeTypedArray extends TypedArray { case TYPE_STRING: outValue.string = getString(index); return true; + case TYPE_REFERENCE: + outValue.resourceId = mResourceId[index]; + return true; default: // For back-compatibility, parse as float. String s = getString(index); diff --git a/bridge/src/android/content/res/Resources_Delegate.java b/bridge/src/android/content/res/Resources_Delegate.java index e57523d54f..0a5912974f 100644 --- a/bridge/src/android/content/res/Resources_Delegate.java +++ b/bridge/src/android/content/res/Resources_Delegate.java @@ -33,7 +33,9 @@ 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.resources.ResourceUrl; import com.android.tools.layoutlib.annotations.LayoutlibDelegate; +import com.android.tools.layoutlib.annotations.VisibleForTesting; import com.android.util.Pair; import org.xmlpull.v1.XmlPullParser; @@ -58,41 +60,70 @@ import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.InputStream; import java.util.Iterator; +import java.util.Objects; +import java.util.WeakHashMap; + +import static com.android.SdkConstants.ANDROID_PKG; +import static com.android.SdkConstants.PREFIX_RESOURCE_REF; @SuppressWarnings("deprecation") public class Resources_Delegate { + private static WeakHashMap<Resources, LayoutlibCallback> sLayoutlibCallbacks = new + WeakHashMap<>(); + private static WeakHashMap<Resources, BridgeContext> sContexts = new + WeakHashMap<>(); private static boolean[] mPlatformResourceFlag = new boolean[1]; // TODO: This cache is cleared every time a render session is disposed. Look into making this // more long lived. private static LruCache<String, Drawable.ConstantState> sDrawableCache = new LruCache<>(50); - public static Resources initSystem(BridgeContext context, - AssetManager assets, - DisplayMetrics metrics, - Configuration config, - LayoutlibCallback layoutlibCallback) { + public static Resources initSystem(@NonNull BridgeContext context, + @NonNull AssetManager assets, + @NonNull DisplayMetrics metrics, + @NonNull Configuration config, + @NonNull LayoutlibCallback layoutlibCallback) { + assert Resources.mSystem == null : + "Resources_Delegate.initSystem called twice before disposeSystem was called"; Resources resources = new Resources(Resources_Delegate.class.getClassLoader()); resources.setImpl(new ResourcesImpl(assets, metrics, config, new DisplayAdjustments())); - resources.mContext = context; - resources.mLayoutlibCallback = layoutlibCallback; + sContexts.put(resources, Objects.requireNonNull(context)); + sLayoutlibCallbacks.put(resources, Objects.requireNonNull(layoutlibCallback)); return Resources.mSystem = resources; } + /** Returns the {@link BridgeContext} associated to the given {@link Resources} */ + @VisibleForTesting + @NonNull + public static BridgeContext getContext(@NonNull Resources resources) { + assert sContexts.containsKey(resources) : + "Resources_Delegate.getContext called before initSystem"; + return sContexts.get(resources); + } + + /** Returns the {@link LayoutlibCallback} associated to the given {@link Resources} */ + @VisibleForTesting + @NonNull + public static LayoutlibCallback getLayoutlibCallback(@NonNull Resources resources) { + assert sLayoutlibCallbacks.containsKey(resources) : + "Resources_Delegate.getLayoutlibCallback called before initSystem"; + return sLayoutlibCallbacks.get(resources); + } + /** * 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() { sDrawableCache.evictAll(); - Resources.mSystem.mContext = null; - Resources.mSystem.mLayoutlibCallback = null; + sContexts.clear(); + sLayoutlibCallbacks.clear(); Resources.mSystem = null; } public static BridgeTypedArray newTypeArray(Resources resources, int numEntries, boolean platformFile) { - return new BridgeTypedArray(resources, resources.mContext, numEntries, platformFile); + return new BridgeTypedArray(resources, getContext(resources), numEntries, platformFile); } private static Pair<ResourceType, String> getResourceInfo(Resources resources, int id, @@ -100,10 +131,12 @@ public class Resources_Delegate { // first get the String related to this id in the framework Pair<ResourceType, String> resourceInfo = Bridge.resolveResourceId(id); + assert Resources.mSystem != null : "Resources_Delegate.initSystem wasn't called"; // 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 (resources != Resources.mSystem && + (!sContexts.containsKey(resources) || !sLayoutlibCallbacks.containsKey(resources))) { + sLayoutlibCallbacks.put(resources, getLayoutlibCallback(Resources.mSystem)); + sContexts.put(resources, getContext(Resources.mSystem)); } if (resourceInfo != null) { @@ -112,13 +145,11 @@ public class Resources_Delegate { } // didn't find a match in the framework? look in the project. - if (resources.mLayoutlibCallback != null) { - resourceInfo = resources.mLayoutlibCallback.resolveResourceId(id); + resourceInfo = getLayoutlibCallback(resources).resolveResourceId(id); - if (resourceInfo != null) { - platformResFlag_out[0] = false; - return resourceInfo; - } + if (resourceInfo != null) { + platformResFlag_out[0] = false; + return resourceInfo; } return null; } @@ -130,7 +161,7 @@ public class Resources_Delegate { if (resourceInfo != null) { String attributeName = resourceInfo.getSecond(); - RenderResources renderResources = resources.mContext.getRenderResources(); + RenderResources renderResources = getContext(resources).getRenderResources(); ResourceValue value = platformResFlag_out[0] ? renderResources.getFrameworkResource(resourceInfo.getFirst(), attributeName) : renderResources.getProjectResource(resourceInfo.getFirst(), attributeName); @@ -163,7 +194,7 @@ public class Resources_Delegate { drawable = constantState.newDrawable(resources, theme); } else { drawable = - ResourceHelper.getDrawable(value.getSecond(), resources.mContext, theme); + ResourceHelper.getDrawable(value.getSecond(), getContext(resources), theme); if (key != null) { sDrawableCache.put(key, drawable.getConstantState()); @@ -228,7 +259,7 @@ public class Resources_Delegate { if (resValue != null) { ColorStateList stateList = ResourceHelper.getColorStateList(resValue.getSecond(), - resources.mContext); + getContext(resources)); if (stateList != null) { return stateList.obtainForTheme(theme); } @@ -410,11 +441,11 @@ public class Resources_Delegate { @NonNull private static String resolveReference(Resources resources, @NonNull String ref, boolean forceFrameworkOnly) { - if (ref.startsWith(SdkConstants.PREFIX_RESOURCE_REF) || ref.startsWith + if (ref.startsWith(PREFIX_RESOURCE_REF) || ref.startsWith (SdkConstants.PREFIX_THEME_REF)) { ResourceValue rv = - resources.mContext.getRenderResources().findResValue(ref, forceFrameworkOnly); - rv = resources.mContext.getRenderResources().resolveResValue(rv); + getContext(resources).getRenderResources().findResValue(ref, forceFrameworkOnly); + rv = getContext(resources).getRenderResources().resolveResValue(rv); if (rv != null) { return rv.getValue(); } @@ -434,7 +465,7 @@ public class Resources_Delegate { try { // check if the current parser can provide us with a custom parser. if (!mPlatformResourceFlag[0]) { - parser = resources.mLayoutlibCallback.getParser(value); + parser = getLayoutlibCallback(resources).getParser(value); } // create a new one manually if needed. @@ -448,7 +479,7 @@ public class Resources_Delegate { } if (parser != null) { - return new BridgeXmlBlockParser(parser, resources.mContext, + return new BridgeXmlBlockParser(parser, getContext(resources), mPlatformResourceFlag[0]); } } catch (XmlPullParserException e) { @@ -483,7 +514,7 @@ public class Resources_Delegate { // give that to our XmlBlockParser parser = ParserFactory.create(xml); - return new BridgeXmlBlockParser(parser, resources.mContext, + return new BridgeXmlBlockParser(parser, getContext(resources), mPlatformResourceFlag[0]); } } catch (XmlPullParserException e) { @@ -505,7 +536,7 @@ public class Resources_Delegate { @LayoutlibDelegate static TypedArray obtainAttributes(Resources resources, AttributeSet set, int[] attrs) { - return resources.mContext.obtainStyledAttributes(set, attrs); + return getContext(resources).obtainStyledAttributes(set, attrs); } @LayoutlibDelegate @@ -680,7 +711,7 @@ public class Resources_Delegate { if (platformOut[0]) { packageName = SdkConstants.ANDROID_NS_NAME; } else { - packageName = resources.mContext.getPackageName(); + packageName = getContext(resources).getPackageName(); packageName = packageName == null ? SdkConstants.APP_PREFIX : packageName; } return packageName + ':' + resourceInfo.getFirst().getName() + '/' + @@ -698,7 +729,7 @@ public class Resources_Delegate { if (platformOut[0]) { return SdkConstants.ANDROID_NS_NAME; } - String packageName = resources.mContext.getPackageName(); + String packageName = getContext(resources).getPackageName(); return packageName == null ? SdkConstants.APP_PREFIX : packageName; } throwException(resid, null); @@ -793,7 +824,7 @@ public class Resources_Delegate { NotFoundException { Pair<String, ResourceValue> value = getResourceValue(resources, id, mPlatformResourceFlag); if (value != null) { - return ResourceHelper.getFont(value.getSecond(), resources.mContext, null); + return ResourceHelper.getFont(value.getSecond(), getContext(resources), null); } throwException(resources, id); @@ -807,7 +838,7 @@ public class Resources_Delegate { NotFoundException { Resources_Delegate.getValue(resources, id, outValue, true); if (outValue.string != null) { - return ResourceHelper.getFont(outValue.string.toString(), resources.mContext, null, + return ResourceHelper.getFont(outValue.string.toString(), getContext(resources), null, mPlatformResourceFlag[0]); } @@ -867,7 +898,7 @@ public class Resources_Delegate { try { XmlPullParser parser = ParserFactory.create(f); - return new BridgeXmlBlockParser(parser, resources.mContext, + return new BridgeXmlBlockParser(parser, getContext(resources), mPlatformResourceFlag[0]); } catch (XmlPullParserException e) { NotFoundException newE = new NotFoundException(); @@ -907,7 +938,7 @@ public class Resources_Delegate { try { XmlPullParser parser = ParserFactory.create(f); - return new BridgeXmlBlockParser(parser, resources.mContext, mPlatformResourceFlag[0]); + return new BridgeXmlBlockParser(parser, getContext(resources), mPlatformResourceFlag[0]); } catch (XmlPullParserException e) { NotFoundException newE = new NotFoundException(); newE.initCause(e); @@ -987,6 +1018,69 @@ public class Resources_Delegate { throw new UnsupportedOperationException(); } + @VisibleForTesting + @Nullable + static ResourceUrl resourceUrlFromName(@NonNull String name, @Nullable String defType, + @Nullable + String defPackage) { + int colonIdx = name.indexOf(':'); + int slashIdx = name.indexOf('/'); + + if (colonIdx != -1 && slashIdx != -1) { + // Easy case + return ResourceUrl.parse(PREFIX_RESOURCE_REF + name); + } + + if (colonIdx == -1 && slashIdx == -1) { + if (defType == null) { + throw new IllegalArgumentException("name does not define a type an no defType was" + + " passed"); + } + + // It does not define package or type + return ResourceUrl.parse( + PREFIX_RESOURCE_REF + (defPackage != null ? defPackage + ":" : "") + defType + + "/" + name); + } + + if (colonIdx != -1) { + if (defType == null) { + throw new IllegalArgumentException("name does not define a type an no defType was" + + " passed"); + } + // We have package but no type + String pkg = name.substring(0, colonIdx); + ResourceType type = ResourceType.getEnum(defType); + return type != null ? ResourceUrl.create(pkg, type, name.substring(colonIdx + 1)) : + null; + } + + ResourceType type = ResourceType.getEnum(name.substring(0, slashIdx)); + if (type == null) { + return null; + } + // We have type but no package + return ResourceUrl.create(defPackage, + type, + name.substring(slashIdx + 1)); + } + + @LayoutlibDelegate + static int getIdentifier(Resources resources, String name, String defType, String defPackage) { + if (name == null) { + return 0; + } + + ResourceUrl url = resourceUrlFromName(name, defType, defPackage); + Integer id = null; + if (url != null) { + id = ANDROID_PKG.equals(url.namespace) ? Bridge.getResourceId(url.type, url.name) : + getLayoutlibCallback(resources).getResourceId(url.type, url.name); + } + + return id != null ? id : 0; + } + /** * Builds and throws a {@link Resources.NotFoundException} based on a resource id and a resource * type. diff --git a/bridge/src/android/view/BridgeInflater.java b/bridge/src/android/view/BridgeInflater.java index b6e6ec0084..58d8c52746 100644 --- a/bridge/src/android/view/BridgeInflater.java +++ b/bridge/src/android/view/BridgeInflater.java @@ -370,10 +370,12 @@ public final class BridgeInflater extends LayoutInflater { } if (ReflectionUtils.isInstanceOf(view, RecyclerViewUtil.CN_RECYCLER_VIEW)) { Integer resourceId = null; - String attrVal = attrs.getAttributeValue(BridgeConstants.NS_TOOLS_URI, + String attrListItemValue = attrs.getAttributeValue(BridgeConstants.NS_TOOLS_URI, BridgeConstants.ATTR_LIST_ITEM); - if (attrVal != null && !attrVal.isEmpty()) { - ResourceValue resValue = bc.getRenderResources().findResValue(attrVal, false); + int attrItemCountValue = attrs.getAttributeIntValue(BridgeConstants.NS_TOOLS_URI, + BridgeConstants.ATTR_ITEM_COUNT, -1); + if (attrListItemValue != null && !attrListItemValue.isEmpty()) { + ResourceValue resValue = bc.getRenderResources().findResValue(attrListItemValue, false); if (resValue.isFramework()) { resourceId = Bridge.getResourceId(resValue.getResourceType(), resValue.getName()); @@ -385,7 +387,7 @@ public final class BridgeInflater extends LayoutInflater { if (resourceId == null) { resourceId = 0; } - RecyclerViewUtil.setAdapter(view, bc, mLayoutlibCallback, resourceId); + RecyclerViewUtil.setAdapter(view, bc, mLayoutlibCallback, resourceId, attrItemCountValue); } else if (ReflectionUtils.isInstanceOf(view, DrawerLayoutUtil.CN_DRAWER_LAYOUT)) { String attrVal = attrs.getAttributeValue(BridgeConstants.NS_TOOLS_URI, BridgeConstants.ATTR_OPEN_DRAWER); diff --git a/bridge/src/com/android/layoutlib/bridge/BridgeConstants.java b/bridge/src/com/android/layoutlib/bridge/BridgeConstants.java index 6228766957..d95c5088c9 100644 --- a/bridge/src/com/android/layoutlib/bridge/BridgeConstants.java +++ b/bridge/src/com/android/layoutlib/bridge/BridgeConstants.java @@ -55,4 +55,5 @@ public class BridgeConstants { @SuppressWarnings("SpellCheckingInspection") public static final String ATTR_LIST_ITEM = "listitem"; public static final String ATTR_OPEN_DRAWER = "openDrawer"; + public static final String ATTR_ITEM_COUNT = "itemCount"; } diff --git a/bridge/src/com/android/layoutlib/bridge/android/BridgeContext.java b/bridge/src/com/android/layoutlib/bridge/android/BridgeContext.java index 8bd924ea9c..1f0a04f1cb 100644 --- a/bridge/src/com/android/layoutlib/bridge/android/BridgeContext.java +++ b/bridge/src/com/android/layoutlib/bridge/android/BridgeContext.java @@ -212,11 +212,11 @@ public class BridgeContext extends Context { * @param config the Configuration object for this render. * @param targetSdkVersion the targetSdkVersion of the application. */ - public BridgeContext(Object projectKey, DisplayMetrics metrics, - RenderResources renderResources, - AssetRepository assets, - LayoutlibCallback layoutlibCallback, - Configuration config, + public BridgeContext(Object projectKey, @NonNull DisplayMetrics metrics, + @NonNull RenderResources renderResources, + @NonNull AssetRepository assets, + @NonNull LayoutlibCallback layoutlibCallback, + @NonNull Configuration config, int targetSdkVersion, boolean hasRtlSupport) { mProjectKey = projectKey; @@ -750,7 +750,7 @@ public class BridgeContext extends Context { return null; } - List<Pair<String, Boolean>> attributeList = searchAttrs(attrs); + List<AttributeHolder> attributeList = searchAttrs(attrs); BridgeTypedArray ta = Resources_Delegate.newTypeArray(mSystemResources, attrs.length, isPlatformFile); @@ -866,14 +866,14 @@ public class BridgeContext extends Context { if (attributeList != null) { for (int index = 0 ; index < attributeList.size() ; index++) { - Pair<String, Boolean> attribute = attributeList.get(index); + AttributeHolder attributeHolder = attributeList.get(index); - if (attribute == null) { + if (attributeHolder == null) { continue; } - String attrName = attribute.getFirst(); - boolean frameworkAttr = attribute.getSecond(); + String attrName = attributeHolder.name; + boolean frameworkAttr = attributeHolder.isFramework; String value = null; if (set != null) { value = set.getAttributeValue( @@ -965,11 +965,12 @@ public class BridgeContext extends Context { } } - ta.bridgeSetValue(index, attrName, frameworkAttr, defaultValue); + ta.bridgeSetValue(index, attrName, frameworkAttr, attributeHolder.resourceId, + defaultValue); } else { // there is a value in the XML, but we need to resolve it in case it's // referencing another resource or a theme value. - ta.bridgeSetValue(index, attrName, frameworkAttr, + ta.bridgeSetValue(index, attrName, frameworkAttr, attributeHolder.resourceId, mRenderResources.resolveValue(null, attrName, value, isPlatformFile)); } } @@ -1013,7 +1014,7 @@ public class BridgeContext extends Context { */ private Pair<BridgeTypedArray, PropertiesMap> createStyleBasedTypedArray( @Nullable StyleResourceValue style, int[] attrs) throws Resources.NotFoundException { - List<Pair<String, Boolean>> attributes = searchAttrs(attrs); + List<AttributeHolder> attributes = searchAttrs(attrs); BridgeTypedArray ta = Resources_Delegate.newTypeArray(mSystemResources, attrs.length, false); @@ -1021,13 +1022,13 @@ public class BridgeContext extends Context { PropertiesMap defaultPropMap = new PropertiesMap(); // for each attribute, get its name so that we can search it in the style for (int i = 0; i < attrs.length; i++) { - Pair<String, Boolean> attribute = attributes.get(i); + AttributeHolder attrHolder = attributes.get(i); - if (attribute != null) { + if (attrHolder != null) { // look for the value in the given style ResourceValue resValue; - String attrName = attribute.getFirst(); - boolean frameworkAttr = attribute.getSecond(); + String attrName = attrHolder.name; + boolean frameworkAttr = attrHolder.isFramework; if (style != null) { resValue = mRenderResources.findItemInStyle(style, attrName, frameworkAttr); } else { @@ -1039,7 +1040,8 @@ public class BridgeContext extends Context { String preResolve = resValue.getValue(); // resolve it to make sure there are no references left. resValue = mRenderResources.resolveResValue(resValue); - ta.bridgeSetValue(i, attrName, frameworkAttr, resValue); + ta.bridgeSetValue(i, attrName, frameworkAttr, attrHolder.resourceId, + resValue); defaultPropMap.put( frameworkAttr ? SdkConstants.ANDROID_PREFIX + attrName : attrName, new Property(preResolve, resValue.getValue())); @@ -1053,28 +1055,28 @@ public class BridgeContext extends Context { } /** - * The input int[] attrs is a list of attributes. The returns a list of information about + * The input int[] attributeIds is a list of attributes. The returns a list of information about * each attributes. The information is (name, isFramework) * <p/> * - * @param attrs An attribute array reference given to obtainStyledAttributes. + * @param attributeIds An attribute array reference given to obtainStyledAttributes. * @return List of attribute information. */ - private List<Pair<String, Boolean>> searchAttrs(int[] attrs) { - List<Pair<String, Boolean>> results = new ArrayList<>(attrs.length); + private List<AttributeHolder> searchAttrs(int[] attributeIds) { + List<AttributeHolder> results = new ArrayList<>(attributeIds.length); // for each attribute, get its name so that we can search it in the style - for (int attr : attrs) { - Pair<ResourceType, String> resolvedResource = Bridge.resolveResourceId(attr); + for (int id : attributeIds) { + Pair<ResourceType, String> resolvedResource = Bridge.resolveResourceId(id); boolean isFramework = false; if (resolvedResource != null) { isFramework = true; } else { - resolvedResource = mLayoutlibCallback.resolveResourceId(attr); + resolvedResource = mLayoutlibCallback.resolveResourceId(id); } if (resolvedResource != null) { - results.add(Pair.of(resolvedResource.getSecond(), isFramework)); + results.add(new AttributeHolder(id, resolvedResource.getSecond(), isFramework)); } else { results.add(null); } @@ -2008,6 +2010,18 @@ public class BridgeContext extends Context { return false; } + private class AttributeHolder { + private int resourceId; + private String name; + private boolean isFramework; + + private AttributeHolder(int resourceId, String name, boolean isFramework) { + this.resourceId = resourceId; + this.name = name; + this.isFramework = isFramework; + } + } + /** * The cached value depends on * <ol> diff --git a/bridge/src/com/android/layoutlib/bridge/android/support/RecyclerViewUtil.java b/bridge/src/com/android/layoutlib/bridge/android/support/RecyclerViewUtil.java index ab278195f3..c6e034f4a4 100644 --- a/bridge/src/com/android/layoutlib/bridge/android/support/RecyclerViewUtil.java +++ b/bridge/src/com/android/layoutlib/bridge/android/support/RecyclerViewUtil.java @@ -55,13 +55,17 @@ public class RecyclerViewUtil { * Any exceptions thrown during the process are logged in {@link Bridge#getLog()} */ public static void setAdapter(@NonNull View recyclerView, @NonNull BridgeContext context, - @NonNull LayoutlibCallback layoutlibCallback, int adapterLayout) { + @NonNull LayoutlibCallback layoutlibCallback, int adapterLayout, int itemCount) { try { setLayoutManager(recyclerView, context, layoutlibCallback); Object adapter = createAdapter(layoutlibCallback); if (adapter != null) { setProperty(recyclerView, CN_ADAPTER, adapter, "setAdapter"); setProperty(adapter, int.class, adapterLayout, "setLayoutId"); + + if (itemCount != -1) { + setProperty(adapter, int.class, itemCount, "setItemCount"); + } } } catch (ReflectionException e) { Throwable cause = getCause(e); diff --git a/bridge/tests/src/android/content/res/Resources_DelegateTest.java b/bridge/tests/src/android/content/res/Resources_DelegateTest.java new file mode 100644 index 0000000000..be39ab0712 --- /dev/null +++ b/bridge/tests/src/android/content/res/Resources_DelegateTest.java @@ -0,0 +1,69 @@ +/* + * Copyright (C) 2017 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.resources.ResourceType; +import com.android.resources.ResourceUrl; + +import org.junit.Test; + +import android.annotation.NonNull; +import android.annotation.Nullable; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.fail; + +public class Resources_DelegateTest { + private static void assertResourceUrl(@Nullable String pkg, @NonNull String name, + @NonNull ResourceType type, @Nullable ResourceUrl url) { + assertNotNull(url); + assertEquals(type, url.type); + assertEquals(pkg, url.namespace); + assertEquals(name, url.name); + } + + @Test + public void resourceUrlFromName() { + try { + Resources_Delegate.resourceUrlFromName("pkg:name", null, null); + fail("Expected IllegalArgumentException since no type was defined"); + } catch (IllegalArgumentException ignored) { + } + + assertNull(Resources_Delegate.resourceUrlFromName("package:invalid/name", null, null)); + assertNull(Resources_Delegate.resourceUrlFromName("package:name", "invalid", null)); + assertResourceUrl("package", "name", ResourceType.ID, + Resources_Delegate.resourceUrlFromName("package:name", "id", null)); + assertResourceUrl("package", "name", ResourceType.ID, + Resources_Delegate.resourceUrlFromName("name", "id", "package")); + assertResourceUrl("package", "test", ResourceType.STRING, + Resources_Delegate.resourceUrlFromName("package:string/test", null, null)); + assertResourceUrl(null, "test", ResourceType.STRING, + Resources_Delegate.resourceUrlFromName("string/test", null, null)); + + + // Type and package in the name take precedence over the passed defType and defPackage + assertResourceUrl("p1", "r1", ResourceType.STRING, + Resources_Delegate.resourceUrlFromName("p1:string/r1", "id", "p2")); + assertResourceUrl("p2", "r1", ResourceType.STRING, + Resources_Delegate.resourceUrlFromName("string/r1", "id", "p2")); + assertResourceUrl("p1", "r1", ResourceType.ID, + Resources_Delegate.resourceUrlFromName("p1:r1", "id", "p2")); + } +}
\ No newline at end of file diff --git a/bridge/tests/src/com/android/layoutlib/bridge/intensive/RenderTests.java b/bridge/tests/src/com/android/layoutlib/bridge/intensive/RenderTests.java index 833652a3a6..208c2d7030 100644 --- a/bridge/tests/src/com/android/layoutlib/bridge/intensive/RenderTests.java +++ b/bridge/tests/src/com/android/layoutlib/bridge/intensive/RenderTests.java @@ -20,6 +20,7 @@ import com.android.ide.common.rendering.api.RenderSession; import com.android.ide.common.rendering.api.SessionParams; import com.android.ide.common.rendering.api.SessionParams.RenderingMode; import com.android.ide.common.rendering.api.ViewInfo; +import com.android.internal.R; import com.android.layoutlib.bridge.android.BridgeContext; import com.android.layoutlib.bridge.android.RenderParamsFlags; import com.android.layoutlib.bridge.impl.RenderAction; @@ -35,6 +36,7 @@ import org.junit.Test; import android.content.res.AssetManager; import android.content.res.Configuration; import android.content.res.Resources; +import android.content.res.Resources_Delegate; import android.util.DisplayMetrics; import android.util.TypedValue; @@ -47,6 +49,7 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotEquals; import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; /** @@ -412,26 +415,28 @@ public class RenderTests extends RenderTestBase { AssetManager assetManager = AssetManager.getSystem(); DisplayMetrics metrics = new DisplayMetrics(); Configuration configuration = RenderAction.getConfiguration(params); - //noinspection deprecation - Resources resources = new Resources(assetManager, metrics, configuration); - resources.mLayoutlibCallback = params.getLayoutlibCallback(); - resources.mContext = - new BridgeContext(params.getProjectKey(), metrics, params.getResources(), - params.getAssets(), params.getLayoutlibCallback(), configuration, - params.getTargetSdkVersion(), params.isRtlSupported()); + BridgeContext context = new BridgeContext(params.getProjectKey(), metrics, params.getResources(), + params.getAssets(), params.getLayoutlibCallback(), configuration, + params.getTargetSdkVersion(), params.isRtlSupported()); + Resources resources = Resources_Delegate.initSystem(context, assetManager, metrics, + configuration, params.getLayoutlibCallback()); // Test assertEquals("android:style/ButtonBar", resources.getResourceName(android.R.style.ButtonBar)); assertEquals("android", resources.getResourcePackageName(android.R.style.ButtonBar)); assertEquals("ButtonBar", resources.getResourceEntryName(android.R.style.ButtonBar)); assertEquals("style", resources.getResourceTypeName(android.R.style.ButtonBar)); - int id = resources.mLayoutlibCallback.getResourceId(ResourceType.STRING, "app_name"); + int id = Resources_Delegate.getLayoutlibCallback(resources).getResourceId( + ResourceType.STRING, + "app_name"); assertEquals("com.android.layoutlib.test.myapplication:string/app_name", resources.getResourceName(id)); assertEquals("com.android.layoutlib.test.myapplication", resources.getResourcePackageName(id)); assertEquals("string", resources.getResourceTypeName(id)); assertEquals("app_name", resources.getResourceEntryName(id)); + + context.disposeResources(); } @Test @@ -449,20 +454,22 @@ public class RenderTests extends RenderTestBase { AssetManager assetManager = AssetManager.getSystem(); DisplayMetrics metrics = new DisplayMetrics(); Configuration configuration = RenderAction.getConfiguration(params); - //noinspection deprecation - Resources resources = new Resources(assetManager, metrics, configuration); - resources.mLayoutlibCallback = params.getLayoutlibCallback(); - resources.mContext = - new BridgeContext(params.getProjectKey(), metrics, params.getResources(), - params.getAssets(), params.getLayoutlibCallback(), configuration, - params.getTargetSdkVersion(), params.isRtlSupported()); - - int id = resources.mLayoutlibCallback.getResourceId(ResourceType.ARRAY, "string_array"); + BridgeContext context = new BridgeContext(params.getProjectKey(), metrics, params.getResources(), + params.getAssets(), params.getLayoutlibCallback(), configuration, + params.getTargetSdkVersion(), params.isRtlSupported()); + Resources resources = Resources_Delegate.initSystem(context, assetManager, metrics, + configuration, params.getLayoutlibCallback()); + + int id = Resources_Delegate.getLayoutlibCallback(resources).getResourceId( + ResourceType.ARRAY, + "string_array"); String[] strings = resources.getStringArray(id); assertArrayEquals( new String[]{"mystring", "Hello world!", "candidates", "Unknown", "?EC"}, strings); assertTrue(sRenderMessages.isEmpty()); + + context.disposeResources(); } @Test @@ -509,19 +516,16 @@ public class RenderTests extends RenderTestBase { layoutLibCallback.initResources(); SessionParams params = getSessionParams(parser, ConfigGenerator.NEXUS_4, layoutLibCallback, "AppTheme", true, RenderingMode.NORMAL, 22); - AssetManager assetManager = AssetManager.getSystem(); DisplayMetrics metrics = new DisplayMetrics(); Configuration configuration = RenderAction.getConfiguration(params); - //noinspection deprecation - Resources resources = new Resources(assetManager, metrics, configuration); - resources.mLayoutlibCallback = params.getLayoutlibCallback(); - resources.mContext = + + BridgeContext mContext = new BridgeContext(params.getProjectKey(), metrics, params.getResources(), params.getAssets(), params.getLayoutlibCallback(), configuration, params.getTargetSdkVersion(), params.isRtlSupported()); TypedValue outValue = new TypedValue(); - resources.mContext.resolveThemeAttribute(android.R.attr.colorPrimary, outValue, true); + mContext.resolveThemeAttribute(android.R.attr.colorPrimary, outValue, true); assertEquals(TypedValue.TYPE_INT_COLOR_ARGB8, outValue.type); assertNotEquals(0, outValue.data); assertTrue(sRenderMessages.isEmpty()); @@ -531,4 +535,42 @@ public class RenderTests extends RenderTestBase { public void testRectangleShadow() throws Exception { renderAndVerify("shadows_test.xml", "shadows_test.png"); } + + @Test + public void testResourcesGetIdentifier() throws Exception { + // Setup + // Create the layout pull parser for our resources (empty.xml can not be part of the test + // app as it won't compile). + LayoutPullParser parser = LayoutPullParser.createFromPath("/empty.xml"); + // Create LayoutLibCallback. + LayoutLibTestCallback layoutLibCallback = + new LayoutLibTestCallback(getLogger(), mDefaultClassLoader); + layoutLibCallback.initResources(); + SessionParams params = getSessionParams(parser, ConfigGenerator.NEXUS_4, + layoutLibCallback, "AppTheme", true, RenderingMode.NORMAL, 22); + AssetManager assetManager = AssetManager.getSystem(); + DisplayMetrics metrics = new DisplayMetrics(); + Configuration configuration = RenderAction.getConfiguration(params); + BridgeContext context = new BridgeContext(params.getProjectKey(), metrics, params.getResources(), + params.getAssets(), params.getLayoutlibCallback(), configuration, + params.getTargetSdkVersion(), params.isRtlSupported()); + Resources resources = Resources_Delegate.initSystem(context, assetManager, metrics, + configuration, params.getLayoutlibCallback()); + int id = Resources_Delegate.getLayoutlibCallback(resources).getResourceId( + ResourceType.STRING, + "app_name"); + assertEquals(id, resources.getIdentifier("string/app_name", null, null)); + assertEquals(id, resources.getIdentifier("app_name", "string", null)); + assertEquals(0, resources.getIdentifier("string/does_not_exist", null, null)); + assertEquals(R.string.accept, resources.getIdentifier("android:string/accept", null, + null)); + assertEquals(R.string.accept, resources.getIdentifier("string/accept", null, + "android")); + assertEquals(R.id.message, resources.getIdentifier("id/message", null, + "android")); + assertEquals(R.string.accept, resources.getIdentifier("accept", "string", + "android")); + + context.disposeResources(); + } } diff --git a/common/Android.mk b/common/Android.mk new file mode 100644 index 0000000000..5e22fddfe4 --- /dev/null +++ b/common/Android.mk @@ -0,0 +1,27 @@ +# +# 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. +# +LOCAL_PATH := $(call my-dir) +include $(CLEAR_VARS) + +LOCAL_SRC_FILES := $(call all-java-files-under,src) + +LOCAL_MODULE := layoutlib-common + +include $(BUILD_HOST_JAVA_LIBRARY) + +# Build all sub-directories +include $(call all-makefiles-under,$(LOCAL_PATH)) + diff --git a/common/common.iml b/common/common.iml new file mode 100644 index 0000000000..c90834f2d6 --- /dev/null +++ b/common/common.iml @@ -0,0 +1,11 @@ +<?xml version="1.0" encoding="UTF-8"?> +<module type="JAVA_MODULE" version="4"> + <component name="NewModuleRootManager" inherit-compiler-output="true"> + <exclude-output /> + <content url="file://$MODULE_DIR$"> + <sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" /> + </content> + <orderEntry type="inheritedJdk" /> + <orderEntry type="sourceFolder" forTests="false" /> + </component> +</module>
\ No newline at end of file diff --git a/common/src/com/android/tools/layoutlib/annotations/LayoutlibDelegate.java b/common/src/com/android/tools/layoutlib/annotations/LayoutlibDelegate.java new file mode 100644 index 0000000000..ca480d7f62 --- /dev/null +++ b/common/src/com/android/tools/layoutlib/annotations/LayoutlibDelegate.java @@ -0,0 +1,27 @@ +/* + * Copyright (C) 2017 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.annotations; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +/** + * Denotes a method that has been converted to a delegate by layoutlib_create. + */ +@Retention(RetentionPolicy.RUNTIME) +public @interface LayoutlibDelegate { +} diff --git a/common/src/com/android/tools/layoutlib/annotations/Nullable.java b/common/src/com/android/tools/layoutlib/annotations/Nullable.java new file mode 100644 index 0000000000..3d91f92087 --- /dev/null +++ b/common/src/com/android/tools/layoutlib/annotations/Nullable.java @@ -0,0 +1,35 @@ +/* + * Copyright (C) 2017 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.annotations; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +/** + * Denotes a parameter or field can be null. + * <p/> + * When decorating a method call parameter, this denotes the parameter can + * legitimately be null and the method will gracefully deal with it. Typically used + * on optional parameters. + * <p/> + * When decorating a method, this denotes the method might legitimately return null. + * <p/> + * This is a marker annotation and it has no specific attributes. + */ +@Retention(RetentionPolicy.SOURCE) +public @interface Nullable { +} diff --git a/common/src/com/android/tools/layoutlib/annotations/VisibleForTesting.java b/common/src/com/android/tools/layoutlib/annotations/VisibleForTesting.java new file mode 100644 index 0000000000..487860854d --- /dev/null +++ b/common/src/com/android/tools/layoutlib/annotations/VisibleForTesting.java @@ -0,0 +1,50 @@ +/* + * Copyright (C) 2017 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.annotations; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +/** + * Denotes that the class, method or field has its visibility relaxed so + * that unit tests can access it. + * <p/> + * The <code>visibility</code> argument can be used to specific what the original + * visibility should have been if it had not been made public or package-private for testing. + * The default is to consider the element private. + */ +@Retention(RetentionPolicy.SOURCE) +public @interface VisibleForTesting { + /** + * Intended visibility if the element had not been made public or package-private for + * testing. + */ + enum Visibility { + /** The element should be considered protected. */ + PROTECTED, + /** The element should be considered package-private. */ + PACKAGE, + /** The element should be considered private. */ + PRIVATE + } + + /** + * Intended visibility if the element had not been made public or package-private for testing. + * If not specified, one should assume the element originally intended to be private. + */ + Visibility visibility() default Visibility.PRIVATE; +} diff --git a/create/Android.mk b/create/Android.mk index 7611cde3f3..0874022105 100644 --- a/create/Android.mk +++ b/create/Android.mk @@ -20,7 +20,8 @@ LOCAL_SRC_FILES := $(call all-java-files-under,src) LOCAL_JAR_MANIFEST := manifest.txt LOCAL_STATIC_JAVA_LIBRARIES := \ - asm-5.2 + asm-5.2 \ + layoutlib-common LOCAL_MODULE := layoutlib_create diff --git a/create/create.iml b/create/create.iml index 237d85bec3..39f232af94 100644 --- a/create/create.iml +++ b/create/create.iml @@ -23,5 +23,6 @@ </library> </orderEntry> <orderEntry type="library" scope="TEST" name="junit" level="project" /> + <orderEntry type="module" module-name="common" /> </component> </module>
\ No newline at end of file diff --git a/create/src/com/android/tools/layoutlib/create/AsmGenerator.java b/create/src/com/android/tools/layoutlib/create/AsmGenerator.java index d59b419801..395bfb8fa4 100644 --- a/create/src/com/android/tools/layoutlib/create/AsmGenerator.java +++ b/create/src/com/android/tools/layoutlib/create/AsmGenerator.java @@ -378,10 +378,6 @@ public class AsmGenerator { 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); } diff --git a/create/src/com/android/tools/layoutlib/create/CreateInfo.java b/create/src/com/android/tools/layoutlib/create/CreateInfo.java index cb0bc6d3c4..5951a67f6b 100644 --- a/create/src/com/android/tools/layoutlib/create/CreateInfo.java +++ b/create/src/com/android/tools/layoutlib/create/CreateInfo.java @@ -133,6 +133,7 @@ public final class CreateInfo implements ICreateInfo { "android.content.res.Resources#getDimensionPixelSize", "android.content.res.Resources#getDrawable", "android.content.res.Resources#getFont", + "android.content.res.Resources#getIdentifier", "android.content.res.Resources#getIntArray", "android.content.res.Resources#getInteger", "android.content.res.Resources#getLayout", diff --git a/create/src/com/android/tools/layoutlib/create/FieldInjectorAdapter.java b/create/src/com/android/tools/layoutlib/create/FieldInjectorAdapter.java deleted file mode 100644 index 4608a84af3..0000000000 --- a/create/src/com/android/tools/layoutlib/create/FieldInjectorAdapter.java +++ /dev/null @@ -1,40 +0,0 @@ -/* - * 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(); - } -} |