1 /*
2  * Copyright (C) 2008 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 package android.util;
17 
18 import com.android.ide.common.rendering.api.AttrResourceValue;
19 import com.android.ide.common.rendering.api.ResourceNamespace;
20 import com.android.ide.common.rendering.api.ResourceReference;
21 import com.android.ide.common.rendering.api.ResourceValue;
22 import com.android.internal.util.XmlUtils;
23 import com.android.layoutlib.bridge.Bridge;
24 import com.android.layoutlib.bridge.BridgeConstants;
25 import com.android.layoutlib.bridge.android.BridgeContext;
26 import com.android.layoutlib.bridge.android.UnresolvedResourceValue;
27 import com.android.layoutlib.bridge.android.XmlPullParserResolver;
28 import com.android.layoutlib.bridge.impl.ResourceHelper;
29 import com.android.resources.ResourceType;
30 
31 import org.xmlpull.v1.XmlPullParser;
32 
33 import android.annotation.NonNull;
34 import android.annotation.Nullable;
35 
36 import java.util.Map;
37 import java.util.function.Function;
38 
39 /**
40  * A correct implementation of the {@link AttributeSet} interface on top of a XmlPullParser
41  */
42 public class BridgeXmlPullAttributes extends XmlPullAttributes implements ResolvingAttributeSet {
43 
44     interface EnumValueSupplier {
45         @Nullable
getEnumValues( @onNull ResourceNamespace namespace, @NonNull String attrName)46         Map<String, Integer> getEnumValues(
47                 @NonNull ResourceNamespace namespace, @NonNull String attrName);
48     }
49 
50     private final BridgeContext mContext;
51     private final ResourceNamespace mXmlFileResourceNamespace;
52     private final ResourceNamespace.Resolver mResourceNamespaceResolver;
53     private final Function<String, Map<String, Integer>> mFrameworkEnumValueSupplier;
54     private final EnumValueSupplier mProjectEnumValueSupplier;
55 
56     // VisibleForTesting
BridgeXmlPullAttributes( @onNull XmlPullParser parser, @NonNull BridgeContext context, @NonNull ResourceNamespace xmlFileResourceNamespace, @NonNull Function<String, Map<String, Integer>> frameworkEnumValueSupplier, @NonNull EnumValueSupplier projectEnumValueSupplier)57     BridgeXmlPullAttributes(
58             @NonNull XmlPullParser parser,
59             @NonNull BridgeContext context,
60             @NonNull ResourceNamespace xmlFileResourceNamespace,
61             @NonNull Function<String, Map<String, Integer>> frameworkEnumValueSupplier,
62             @NonNull EnumValueSupplier projectEnumValueSupplier) {
63         super(parser);
64         mContext = context;
65         mFrameworkEnumValueSupplier = frameworkEnumValueSupplier;
66         mProjectEnumValueSupplier = projectEnumValueSupplier;
67         mXmlFileResourceNamespace = xmlFileResourceNamespace;
68         mResourceNamespaceResolver =
69                 new XmlPullParserResolver(
70                         mParser, context.getLayoutlibCallback().getImplicitNamespaces());
71     }
72 
BridgeXmlPullAttributes( @onNull XmlPullParser parser, @NonNull BridgeContext context, @NonNull ResourceNamespace xmlFileResourceNamespace)73     public BridgeXmlPullAttributes(
74             @NonNull XmlPullParser parser,
75             @NonNull BridgeContext context,
76             @NonNull ResourceNamespace xmlFileResourceNamespace) {
77         this(parser, context, xmlFileResourceNamespace, Bridge::getEnumValues, (ns, attrName) -> {
78             ResourceValue attr =
79                     context.getRenderResources().getUnresolvedResource(
80                             ResourceReference.attr(ns, attrName));
81             return attr instanceof AttrResourceValue
82                     ? ((AttrResourceValue) attr).getAttributeValues()
83                     : null;
84         });
85     }
86 
87     /*
88      * (non-Javadoc)
89      * @see android.util.XmlPullAttributes#getAttributeNameResource(int)
90      *
91      * This methods must return com.android.internal.R.attr.<name> matching
92      * the name of the attribute.
93      * It returns 0 if it doesn't find anything.
94      */
95     @Override
getAttributeNameResource(int index)96     public int getAttributeNameResource(int index) {
97         // get the attribute name.
98         String name = getAttributeName(index);
99 
100         // get the attribute namespace
101         String ns = mParser.getAttributeNamespace(index);
102 
103         if (BridgeConstants.NS_RESOURCES.equals(ns)) {
104             return Bridge.getResourceId(ResourceType.ATTR, name);
105         }
106 
107         // this is not an attribute in the android namespace, we query the customviewloader, if
108         // the namespaces match.
109         if (mContext.getLayoutlibCallback().getNamespace().equals(ns)) {
110             // TODO(namespaces): cache the namespace objects.
111             ResourceNamespace namespace = ResourceNamespace.fromNamespaceUri(ns);
112             if (namespace != null) {
113                 return mContext.getLayoutlibCallback().getOrGenerateResourceId(
114                         ResourceReference.attr(namespace, name));
115             }
116         }
117 
118         return 0;
119     }
120 
121     @Override
getAttributeListValue(String namespace, String attribute, String[] options, int defaultValue)122     public int getAttributeListValue(String namespace, String attribute,
123             String[] options, int defaultValue) {
124         String value = getAttributeValue(namespace, attribute);
125         if (value != null) {
126             ResourceValue r = getResourceValue(value);
127 
128             if (r != null) {
129                 value = r.getValue();
130             }
131 
132             return XmlUtils.convertValueToList(value, options, defaultValue);
133         }
134 
135         return defaultValue;
136     }
137 
138     @Override
getAttributeBooleanValue(String namespace, String attribute, boolean defaultValue)139     public boolean getAttributeBooleanValue(String namespace, String attribute,
140             boolean defaultValue) {
141         String value = getAttributeValue(namespace, attribute);
142         if (value != null) {
143             ResourceValue r = getResourceValue(value);
144 
145             if (r != null) {
146                 value = r.getValue();
147             }
148 
149             return XmlUtils.convertValueToBoolean(value, defaultValue);
150         }
151 
152         return defaultValue;
153     }
154 
155     @Override
getAttributeResourceValue(String namespace, String attribute, int defaultValue)156     public int getAttributeResourceValue(String namespace, String attribute, int defaultValue) {
157         String value = getAttributeValue(namespace, attribute);
158 
159         return resolveResourceValue(value, defaultValue);
160     }
161 
162     @Override
getAttributeIntValue(String namespace, String attribute, int defaultValue)163     public int getAttributeIntValue(String namespace, String attribute, int defaultValue) {
164         String value = getAttributeValue(namespace, attribute);
165         if (value == null) {
166             return defaultValue;
167         }
168 
169         ResourceValue r = getResourceValue(value);
170 
171         if (r != null) {
172             value = r.getValue();
173         }
174 
175         if (value.charAt(0) == '#') {
176             return ResourceHelper.getColor(value);
177         }
178 
179         try {
180             return XmlUtils.convertValueToInt(value, defaultValue);
181         } catch (NumberFormatException e) {
182             // This is probably an enum
183             Map<String, Integer> enumValues = null;
184             if (BridgeConstants.NS_RESOURCES.equals(namespace)) {
185                 enumValues = mFrameworkEnumValueSupplier.apply(attribute);
186             } else {
187                 ResourceNamespace attrNamespace = ResourceNamespace.fromNamespaceUri(namespace);
188                 if (attrNamespace != null) {
189                     enumValues = mProjectEnumValueSupplier.getEnumValues(attrNamespace, attribute);
190                 }
191             }
192 
193             Integer enumValue = enumValues != null ? enumValues.get(value) : null;
194             if (enumValue != null) {
195                 return enumValue;
196             }
197 
198             // We weren't able to find the enum int value
199             throw e;
200         }
201     }
202 
203     @Override
getAttributeUnsignedIntValue(String namespace, String attribute, int defaultValue)204     public int getAttributeUnsignedIntValue(String namespace, String attribute,
205             int defaultValue) {
206         String value = getAttributeValue(namespace, attribute);
207         if (value != null) {
208             ResourceValue r = getResourceValue(value);
209 
210             if (r != null) {
211                 value = r.getValue();
212             }
213 
214             return XmlUtils.convertValueToUnsignedInt(value, defaultValue);
215         }
216 
217         return defaultValue;
218     }
219 
220     @Override
getAttributeFloatValue(String namespace, String attribute, float defaultValue)221     public float getAttributeFloatValue(String namespace, String attribute,
222             float defaultValue) {
223         String s = getAttributeValue(namespace, attribute);
224         if (s != null) {
225             ResourceValue r = getResourceValue(s);
226 
227             if (r != null) {
228                 s = r.getValue();
229             }
230 
231             return Float.parseFloat(s);
232         }
233 
234         return defaultValue;
235     }
236 
237     @Override
getAttributeListValue(int index, String[] options, int defaultValue)238     public int getAttributeListValue(int index,
239             String[] options, int defaultValue) {
240         return XmlUtils.convertValueToList(
241             getAttributeValue(index), options, defaultValue);
242     }
243 
244     @Override
getAttributeBooleanValue(int index, boolean defaultValue)245     public boolean getAttributeBooleanValue(int index, boolean defaultValue) {
246         String value = getAttributeValue(index);
247         if (value != null) {
248             ResourceValue r = getResourceValue(value);
249 
250             if (r != null) {
251                 value = r.getValue();
252             }
253 
254             return XmlUtils.convertValueToBoolean(value, defaultValue);
255         }
256 
257         return defaultValue;
258     }
259 
260     @Override
getAttributeResourceValue(int index, int defaultValue)261     public int getAttributeResourceValue(int index, int defaultValue) {
262         String value = getAttributeValue(index);
263 
264         return resolveResourceValue(value, defaultValue);
265     }
266 
267     @Override
getAttributeIntValue(int index, int defaultValue)268     public int getAttributeIntValue(int index, int defaultValue) {
269         return getAttributeIntValue(
270                 mParser.getAttributeNamespace(index), getAttributeName(index), defaultValue);
271     }
272 
273     @Override
getAttributeUnsignedIntValue(int index, int defaultValue)274     public int getAttributeUnsignedIntValue(int index, int defaultValue) {
275         String value = getAttributeValue(index);
276         if (value != null) {
277             ResourceValue r = getResourceValue(value);
278 
279             if (r != null) {
280                 value = r.getValue();
281             }
282 
283             return XmlUtils.convertValueToUnsignedInt(value, defaultValue);
284         }
285 
286         return defaultValue;
287     }
288 
289     @Override
getAttributeFloatValue(int index, float defaultValue)290     public float getAttributeFloatValue(int index, float defaultValue) {
291         String s = getAttributeValue(index);
292         if (s != null) {
293             ResourceValue r = getResourceValue(s);
294 
295             if (r != null) {
296                 s = r.getValue();
297             }
298 
299             return Float.parseFloat(s);
300         }
301 
302         return defaultValue;
303     }
304 
305     @Override
306     @Nullable
getResolvedAttributeValue(@ullable String namespace, @NonNull String name)307     public ResourceValue getResolvedAttributeValue(@Nullable String namespace,
308             @NonNull String name) {
309         String s = getAttributeValue(namespace, name);
310         return s == null ? null : getResourceValue(s);
311     }
312 
313     // -- private helper methods
314 
315     /**
316      * Returns a resolved {@link ResourceValue} from a given value.
317      */
getResourceValue(String value)318     private ResourceValue getResourceValue(String value) {
319         // now look for this particular value
320         return mContext.getRenderResources().resolveResValue(
321                 new UnresolvedResourceValue(
322                         value, mXmlFileResourceNamespace, mResourceNamespaceResolver));
323     }
324 
325     /**
326      * Resolves and return a value to its associated integer.
327      */
resolveResourceValue(String value, int defaultValue)328     private int resolveResourceValue(String value, int defaultValue) {
329         ResourceValue resource = getResourceValue(value);
330         if (resource != null) {
331             return resource.isFramework() ?
332                     Bridge.getResourceId(resource.getResourceType(), resource.getName()) :
333                     mContext.getLayoutlibCallback().getOrGenerateResourceId(resource.asReference());
334         }
335 
336         return defaultValue;
337     }
338 }
339