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         // TODO(b/156609434): cache the namespace objects.
108         ResourceNamespace namespace = ResourceNamespace.fromNamespaceUri(ns);
109         if (namespace != null) {
110             return mContext.getLayoutlibCallback().getOrGenerateResourceId(
111                     ResourceReference.attr(namespace, name));
112         }
113 
114         return 0;
115     }
116 
117     @Override
getAttributeListValue(String namespace, String attribute, String[] options, int defaultValue)118     public int getAttributeListValue(String namespace, String attribute,
119             String[] options, int defaultValue) {
120         String value = getAttributeValue(namespace, attribute);
121         if (value != null) {
122             ResourceValue r = getResourceValue(value);
123 
124             if (r != null) {
125                 value = r.getValue();
126             }
127 
128             return XmlUtils.convertValueToList(value, options, defaultValue);
129         }
130 
131         return defaultValue;
132     }
133 
134     @Override
getAttributeBooleanValue(String namespace, String attribute, boolean defaultValue)135     public boolean getAttributeBooleanValue(String namespace, String attribute,
136             boolean defaultValue) {
137         String value = getAttributeValue(namespace, attribute);
138         if (value != null) {
139             ResourceValue r = getResourceValue(value);
140 
141             if (r != null) {
142                 value = r.getValue();
143             }
144 
145             return XmlUtils.convertValueToBoolean(value, defaultValue);
146         }
147 
148         return defaultValue;
149     }
150 
151     @Override
getAttributeResourceValue(String namespace, String attribute, int defaultValue)152     public int getAttributeResourceValue(String namespace, String attribute, int defaultValue) {
153         String value = getAttributeValue(namespace, attribute);
154 
155         return resolveResourceValue(value, defaultValue);
156     }
157 
158     @Override
getAttributeIntValue(String namespace, String attribute, int defaultValue)159     public int getAttributeIntValue(String namespace, String attribute, int defaultValue) {
160         String value = getAttributeValue(namespace, attribute);
161         if (value == null) {
162             return defaultValue;
163         }
164 
165         ResourceValue r = getResourceValue(value);
166 
167         if (r != null) {
168             value = r.getValue();
169         }
170 
171         if (value.charAt(0) == '#') {
172             return ResourceHelper.getColor(value);
173         }
174 
175         try {
176             return XmlUtils.convertValueToInt(value, defaultValue);
177         } catch (NumberFormatException e) {
178             // This is probably an enum
179             Map<String, Integer> enumValues = null;
180             if (BridgeConstants.NS_RESOURCES.equals(namespace)) {
181                 enumValues = mFrameworkEnumValueSupplier.apply(attribute);
182             } else {
183                 ResourceNamespace attrNamespace = ResourceNamespace.fromNamespaceUri(namespace);
184                 if (attrNamespace != null) {
185                     enumValues = mProjectEnumValueSupplier.getEnumValues(attrNamespace, attribute);
186                 }
187             }
188 
189             Integer enumValue = enumValues != null ? enumValues.get(value) : null;
190             if (enumValue != null) {
191                 return enumValue;
192             }
193 
194             // We weren't able to find the enum int value
195             throw e;
196         }
197     }
198 
199     @Override
getAttributeUnsignedIntValue(String namespace, String attribute, int defaultValue)200     public int getAttributeUnsignedIntValue(String namespace, String attribute,
201             int defaultValue) {
202         String value = getAttributeValue(namespace, attribute);
203         if (value != null) {
204             ResourceValue r = getResourceValue(value);
205 
206             if (r != null) {
207                 value = r.getValue();
208             }
209 
210             return XmlUtils.convertValueToUnsignedInt(value, defaultValue);
211         }
212 
213         return defaultValue;
214     }
215 
216     @Override
getAttributeFloatValue(String namespace, String attribute, float defaultValue)217     public float getAttributeFloatValue(String namespace, String attribute,
218             float defaultValue) {
219         String s = getAttributeValue(namespace, attribute);
220         if (s != null) {
221             ResourceValue r = getResourceValue(s);
222 
223             if (r != null) {
224                 s = r.getValue();
225             }
226 
227             return Float.parseFloat(s);
228         }
229 
230         return defaultValue;
231     }
232 
233     @Override
getAttributeListValue(int index, String[] options, int defaultValue)234     public int getAttributeListValue(int index,
235             String[] options, int defaultValue) {
236         return XmlUtils.convertValueToList(
237             getAttributeValue(index), options, defaultValue);
238     }
239 
240     @Override
getAttributeBooleanValue(int index, boolean defaultValue)241     public boolean getAttributeBooleanValue(int index, boolean defaultValue) {
242         String value = getAttributeValue(index);
243         if (value != null) {
244             ResourceValue r = getResourceValue(value);
245 
246             if (r != null) {
247                 value = r.getValue();
248             }
249 
250             return XmlUtils.convertValueToBoolean(value, defaultValue);
251         }
252 
253         return defaultValue;
254     }
255 
256     @Override
getAttributeResourceValue(int index, int defaultValue)257     public int getAttributeResourceValue(int index, int defaultValue) {
258         String value = getAttributeValue(index);
259 
260         return resolveResourceValue(value, defaultValue);
261     }
262 
263     @Override
getAttributeIntValue(int index, int defaultValue)264     public int getAttributeIntValue(int index, int defaultValue) {
265         return getAttributeIntValue(
266                 mParser.getAttributeNamespace(index), getAttributeName(index), defaultValue);
267     }
268 
269     @Override
getAttributeUnsignedIntValue(int index, int defaultValue)270     public int getAttributeUnsignedIntValue(int index, int defaultValue) {
271         String value = getAttributeValue(index);
272         if (value != null) {
273             ResourceValue r = getResourceValue(value);
274 
275             if (r != null) {
276                 value = r.getValue();
277             }
278 
279             return XmlUtils.convertValueToUnsignedInt(value, defaultValue);
280         }
281 
282         return defaultValue;
283     }
284 
285     @Override
getAttributeFloatValue(int index, float defaultValue)286     public float getAttributeFloatValue(int index, float defaultValue) {
287         String s = getAttributeValue(index);
288         if (s != null) {
289             ResourceValue r = getResourceValue(s);
290 
291             if (r != null) {
292                 s = r.getValue();
293             }
294 
295             return Float.parseFloat(s);
296         }
297 
298         return defaultValue;
299     }
300 
301     @Override
302     @Nullable
getResolvedAttributeValue(@ullable String namespace, @NonNull String name)303     public ResourceValue getResolvedAttributeValue(@Nullable String namespace,
304             @NonNull String name) {
305         String s = getAttributeValue(namespace, name);
306         return s == null ? null : getResourceValue(s);
307     }
308 
309     // -- private helper methods
310 
311     /**
312      * Returns a resolved {@link ResourceValue} from a given value.
313      */
getResourceValue(String value)314     private ResourceValue getResourceValue(String value) {
315         // now look for this particular value
316         return mContext.getRenderResources().resolveResValue(
317                 new UnresolvedResourceValue(
318                         value, mXmlFileResourceNamespace, mResourceNamespaceResolver));
319     }
320 
321     /**
322      * Resolves and return a value to its associated integer.
323      */
resolveResourceValue(String value, int defaultValue)324     private int resolveResourceValue(String value, int defaultValue) {
325         ResourceValue resource = getResourceValue(value);
326         if (resource != null) {
327             return resource.isFramework() ?
328                     Bridge.getResourceId(resource.getResourceType(), resource.getName()) :
329                     mContext.getLayoutlibCallback().getOrGenerateResourceId(resource.asReference());
330         }
331 
332         return defaultValue;
333     }
334 }
335