diff --git a/tools/layoutlib/.idea/libraries/mockito.xml b/tools/layoutlib/.idea/libraries/mockito.xml new file mode 100644 index 0000000000000..032963e40b940 --- /dev/null +++ b/tools/layoutlib/.idea/libraries/mockito.xml @@ -0,0 +1,9 @@ + + + + + + + + + \ No newline at end of file diff --git a/tools/layoutlib/.idea/libraries/objenesis.xml b/tools/layoutlib/.idea/libraries/objenesis.xml new file mode 100644 index 0000000000000..1484de59be6a7 --- /dev/null +++ b/tools/layoutlib/.idea/libraries/objenesis.xml @@ -0,0 +1,9 @@ + + + + + + + + + \ No newline at end of file diff --git a/tools/layoutlib/bridge/bridge.iml b/tools/layoutlib/bridge/bridge.iml index fbaed520fff90..85ec3eb73b923 100644 --- a/tools/layoutlib/bridge/bridge.iml +++ b/tools/layoutlib/bridge/bridge.iml @@ -86,5 +86,7 @@ + + \ No newline at end of file diff --git a/tools/layoutlib/bridge/src/android/util/BridgeXmlPullAttributes.java b/tools/layoutlib/bridge/src/android/util/BridgeXmlPullAttributes.java index 138b2d5fca3d0..9fd1e1557537c 100644 --- a/tools/layoutlib/bridge/src/android/util/BridgeXmlPullAttributes.java +++ b/tools/layoutlib/bridge/src/android/util/BridgeXmlPullAttributes.java @@ -16,6 +16,7 @@ package android.util; +import com.android.ide.common.rendering.api.AttrResourceValue; import com.android.ide.common.rendering.api.RenderResources; import com.android.ide.common.rendering.api.ResourceValue; import com.android.internal.util.XmlUtils; @@ -27,6 +28,11 @@ import com.android.resources.ResourceType; import org.xmlpull.v1.XmlPullParser; +import android.annotation.NonNull; + +import java.util.Map; +import java.util.function.Function; + /** * A correct implementation of the {@link AttributeSet} interface on top of a XmlPullParser */ @@ -34,12 +40,30 @@ public class BridgeXmlPullAttributes extends XmlPullAttributes { private final BridgeContext mContext; private final boolean mPlatformFile; + private final Function> mFrameworkEnumValueSupplier; + private final Function> mProjectEnumValueSupplier; - public BridgeXmlPullAttributes(XmlPullParser parser, BridgeContext context, - boolean platformFile) { + // VisibleForTesting + BridgeXmlPullAttributes(@NonNull XmlPullParser parser, @NonNull BridgeContext context, + boolean platformFile, + @NonNull Function> frameworkEnumValueSupplier, + @NonNull Function> projectEnumValueSupplier) { super(parser); mContext = context; mPlatformFile = platformFile; + mFrameworkEnumValueSupplier = frameworkEnumValueSupplier; + mProjectEnumValueSupplier = projectEnumValueSupplier; + } + + public BridgeXmlPullAttributes(@NonNull XmlPullParser parser, @NonNull BridgeContext context, + boolean platformFile) { + this(parser, context, platformFile, Bridge::getEnumValues, attrName -> { + // get the styleable matching the resolved name + RenderResources res = context.getRenderResources(); + ResourceValue attr = res.getProjectResource(ResourceType.ATTR, attrName); + return attr instanceof AttrResourceValue ? + ((AttrResourceValue) attr).getAttributeValues() : null; + }); } /* @@ -59,12 +83,8 @@ public class BridgeXmlPullAttributes extends XmlPullAttributes { String ns = mParser.getAttributeNamespace(index); if (BridgeConstants.NS_RESOURCES.equals(ns)) { - Integer v = Bridge.getResourceId(ResourceType.ATTR, name); - if (v != null) { - return v.intValue(); - } + return Bridge.getResourceId(ResourceType.ATTR, name); - return 0; } // this is not an attribute in the android namespace, we query the customviewloader, if @@ -72,7 +92,7 @@ public class BridgeXmlPullAttributes extends XmlPullAttributes { if (mContext.getLayoutlibCallback().getNamespace().equals(ns)) { Integer v = mContext.getLayoutlibCallback().getResourceId(ResourceType.ATTR, name); if (v != null) { - return v.intValue(); + return v; } } @@ -121,20 +141,38 @@ public class BridgeXmlPullAttributes extends XmlPullAttributes { } @Override - public int getAttributeIntValue(String namespace, String attribute, - int defaultValue) { + public int getAttributeIntValue(String namespace, String attribute, int defaultValue) { String value = getAttributeValue(namespace, attribute); - if (value != null) { - ResourceValue r = getResourceValue(value); - - if (r != null) { - value = r.getValue(); - } - - return XmlUtils.convertValueToInt(value, defaultValue); + if (value == null) { + return defaultValue; } - return defaultValue; + ResourceValue r = getResourceValue(value); + + if (r != null) { + value = r.getValue(); + } + + if (value.charAt(0) == '#') { + return ResourceHelper.getColor(value); + } + + try { + return XmlUtils.convertValueToInt(value, defaultValue); + } catch (NumberFormatException e) { + // This is probably an enum + Map enumValues = BridgeConstants.NS_RESOURCES.equals(namespace) ? + mFrameworkEnumValueSupplier.apply(attribute) : + mProjectEnumValueSupplier.apply(attribute); + + Integer enumValue = enumValues != null ? enumValues.get(value) : null; + if (enumValue != null) { + return enumValue; + } + + // We weren't able to find the enum int value + throw e; + } } @Override @@ -203,21 +241,9 @@ public class BridgeXmlPullAttributes extends XmlPullAttributes { @Override public int getAttributeIntValue(int index, int defaultValue) { - String value = getAttributeValue(index); - if (value != null) { - ResourceValue r = getResourceValue(value); - - if (r != null) { - value = r.getValue(); - } - - if (value.charAt(0) == '#') { - return ResourceHelper.getColor(value); - } - return XmlUtils.convertValueToInt(value, defaultValue); - } - - return defaultValue; + return getAttributeIntValue(mParser.getAttributeNamespace(index), + getAttributeName(index) + , defaultValue); } @Override diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeContext.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeContext.java index dff4f6905db55..c9b04dcf8ed42 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 @@ -111,7 +111,7 @@ import static com.android.layoutlib.bridge.android.RenderParamsFlags.FLAG_KEY_AP * Custom implementation of Context/Activity to handle non compiled resources. */ @SuppressWarnings("deprecation") // For use of Pair. -public final class BridgeContext extends Context { +public class BridgeContext extends Context { private static final String PREFIX_THEME_APPCOMPAT = "Theme.AppCompat"; private static final Map FRAMEWORK_PATCHED_VALUES = new HashMap<>(2); diff --git a/tools/layoutlib/bridge/tests/Android.mk b/tools/layoutlib/bridge/tests/Android.mk index 33d55dea6f79c..1b65eee729e65 100644 --- a/tools/layoutlib/bridge/tests/Android.mk +++ b/tools/layoutlib/bridge/tests/Android.mk @@ -31,7 +31,8 @@ LOCAL_JAVA_LIBRARIES := layoutlib \ tools-common-prebuilt \ sdk-common \ junit-host \ - guavalib + guavalib \ + mockito-host include $(BUILD_HOST_JAVA_LIBRARY) diff --git a/tools/layoutlib/bridge/tests/src/android/util/BridgeXmlPullAttributesTest.java b/tools/layoutlib/bridge/tests/src/android/util/BridgeXmlPullAttributesTest.java new file mode 100644 index 0000000000000..2fcec8e98585e --- /dev/null +++ b/tools/layoutlib/bridge/tests/src/android/util/BridgeXmlPullAttributesTest.java @@ -0,0 +1,110 @@ +/* + * 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.util; + +import com.android.ide.common.rendering.api.RenderResources; +import com.android.layoutlib.bridge.BridgeConstants; +import com.android.layoutlib.bridge.android.BridgeContext; + +import org.junit.Test; +import org.xmlpull.v1.XmlPullParser; + +import com.google.common.collect.ImmutableMap; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +public class BridgeXmlPullAttributesTest { + @Test + public void testGetAttributeIntValueForEnums() { + RenderResources renderResources = new RenderResources(); + + XmlPullParser parser = mock(XmlPullParser.class); + when(parser.getAttributeValue(BridgeConstants.NS_RESOURCES, "layout_width")) + .thenReturn("match_parent"); + when(parser.getAttributeName(0)).thenReturn("layout_width"); + when(parser.getAttributeNamespace(0)).thenReturn(BridgeConstants.NS_RESOURCES); + // Return every value twice since there is one test using name and other using index + when(parser.getAttributeValue("http://custom", "my_custom_attr")) + .thenReturn("a", "a", "b", "b", "invalid", "invalid"); + when(parser.getAttributeName(1)).thenReturn("my_custom_attr"); + when(parser.getAttributeNamespace(1)).thenReturn("http://custom"); + + BridgeContext context = mock(BridgeContext.class); + when(context.getRenderResources()).thenReturn(renderResources); + + BridgeXmlPullAttributes attributes = new BridgeXmlPullAttributes( + parser, + context, + false, + attrName -> { + if ("layout_width".equals(attrName)) { + return ImmutableMap.of( + "match_parent", 123); + } + return ImmutableMap.of(); + }, + attrName -> { + if ("my_custom_attr".equals(attrName)) { + return ImmutableMap.of( + "a", 1, + "b", 2 + ); + } + return ImmutableMap.of(); + }); + + // Test a framework defined enum attribute + assertEquals(123, attributes.getAttributeIntValue(BridgeConstants.NS_RESOURCES, + "layout_width", 500)); + assertEquals(123, attributes.getAttributeIntValue(0, 500)); + // Test non existing attribute (it should return the default value) + assertEquals(500, attributes.getAttributeIntValue(BridgeConstants.NS_RESOURCES, + "layout_height", 500)); + assertEquals(500, attributes.getAttributeIntValue(2, 500)); + + // Test project defined enum attribute + assertEquals(1, attributes.getAttributeIntValue("http://custom", + "my_custom_attr", 500)); + assertEquals(1, attributes.getAttributeIntValue(1, 500)); + assertEquals(2, attributes.getAttributeIntValue("http://custom", + "my_custom_attr", 500)); + assertEquals(2, attributes.getAttributeIntValue(1, 500)); + // Test an invalid enum + boolean exception = false; + try { + attributes.getAttributeIntValue("http://custom", "my_custom_attr", 500); + } catch(NumberFormatException e) { + exception = true; + } + assertTrue(exception); + exception = false; + try { + attributes.getAttributeIntValue(1, 500); + } catch(NumberFormatException e) { + exception = true; + } + assertTrue(exception); + + // Test non existing project attribute + assertEquals(500, attributes.getAttributeIntValue("http://custom", + "my_other_attr", 500)); + } + +} \ No newline at end of file