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 
17 package android.util;
18 
19 import com.android.ide.common.rendering.api.AttrResourceValue;
20 import com.android.ide.common.rendering.api.RenderResources;
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.impl.ResourceHelper;
27 import com.android.resources.ResourceType;
28 
29 import org.xmlpull.v1.XmlPullParser;
30 
31 import android.annotation.NonNull;
32 
33 import java.util.Map;
34 import java.util.function.Function;
35 
36 /**
37  * A correct implementation of the {@link AttributeSet} interface on top of a XmlPullParser
38  */
39 public class BridgeXmlPullAttributes extends XmlPullAttributes {
40 
41     private final BridgeContext mContext;
42     private final boolean mPlatformFile;
43     private final Function<String, Map<String, Integer>> mFrameworkEnumValueSupplier;
44     private final Function<String, Map<String, Integer>> mProjectEnumValueSupplier;
45 
46     // VisibleForTesting
BridgeXmlPullAttributes(@onNull XmlPullParser parser, @NonNull BridgeContext context, boolean platformFile, @NonNull Function<String, Map<String, Integer>> frameworkEnumValueSupplier, @NonNull Function<String, Map<String, Integer>> projectEnumValueSupplier)47     BridgeXmlPullAttributes(@NonNull XmlPullParser parser, @NonNull BridgeContext context,
48             boolean platformFile,
49             @NonNull Function<String, Map<String, Integer>> frameworkEnumValueSupplier,
50             @NonNull Function<String, Map<String, Integer>> projectEnumValueSupplier) {
51         super(parser);
52         mContext = context;
53         mPlatformFile = platformFile;
54         mFrameworkEnumValueSupplier = frameworkEnumValueSupplier;
55         mProjectEnumValueSupplier = projectEnumValueSupplier;
56     }
57 
BridgeXmlPullAttributes(@onNull XmlPullParser parser, @NonNull BridgeContext context, boolean platformFile)58     public BridgeXmlPullAttributes(@NonNull XmlPullParser parser, @NonNull BridgeContext context,
59             boolean platformFile) {
60         this(parser, context, platformFile, Bridge::getEnumValues, attrName -> {
61             // get the styleable matching the resolved name
62             RenderResources res = context.getRenderResources();
63             ResourceValue attr = res.getProjectResource(ResourceType.ATTR, attrName);
64             return attr instanceof AttrResourceValue ?
65                     ((AttrResourceValue) attr).getAttributeValues() : null;
66         });
67     }
68 
69     /*
70      * (non-Javadoc)
71      * @see android.util.XmlPullAttributes#getAttributeNameResource(int)
72      *
73      * This methods must return com.android.internal.R.attr.<name> matching
74      * the name of the attribute.
75      * It returns 0 if it doesn't find anything.
76      */
77     @Override
getAttributeNameResource(int index)78     public int getAttributeNameResource(int index) {
79         // get the attribute name.
80         String name = getAttributeName(index);
81 
82         // get the attribute namespace
83         String ns = mParser.getAttributeNamespace(index);
84 
85         if (BridgeConstants.NS_RESOURCES.equals(ns)) {
86             return Bridge.getResourceId(ResourceType.ATTR, name);
87 
88         }
89 
90         // this is not an attribute in the android namespace, we query the customviewloader, if
91         // the namespaces match.
92         if (mContext.getLayoutlibCallback().getNamespace().equals(ns)) {
93             Integer v = mContext.getLayoutlibCallback().getResourceId(ResourceType.ATTR, name);
94             if (v != null) {
95                 return v;
96             }
97         }
98 
99         return 0;
100     }
101 
102     @Override
getAttributeListValue(String namespace, String attribute, String[] options, int defaultValue)103     public int getAttributeListValue(String namespace, String attribute,
104             String[] options, int defaultValue) {
105         String value = getAttributeValue(namespace, attribute);
106         if (value != null) {
107             ResourceValue r = getResourceValue(value);
108 
109             if (r != null) {
110                 value = r.getValue();
111             }
112 
113             return XmlUtils.convertValueToList(value, options, defaultValue);
114         }
115 
116         return defaultValue;
117     }
118 
119     @Override
getAttributeBooleanValue(String namespace, String attribute, boolean defaultValue)120     public boolean getAttributeBooleanValue(String namespace, String attribute,
121             boolean defaultValue) {
122         String value = getAttributeValue(namespace, attribute);
123         if (value != null) {
124             ResourceValue r = getResourceValue(value);
125 
126             if (r != null) {
127                 value = r.getValue();
128             }
129 
130             return XmlUtils.convertValueToBoolean(value, defaultValue);
131         }
132 
133         return defaultValue;
134     }
135 
136     @Override
getAttributeResourceValue(String namespace, String attribute, int defaultValue)137     public int getAttributeResourceValue(String namespace, String attribute, int defaultValue) {
138         String value = getAttributeValue(namespace, attribute);
139 
140         return resolveResourceValue(value, defaultValue);
141     }
142 
143     @Override
getAttributeIntValue(String namespace, String attribute, int defaultValue)144     public int getAttributeIntValue(String namespace, String attribute, int defaultValue) {
145         String value = getAttributeValue(namespace, attribute);
146         if (value == null) {
147             return defaultValue;
148         }
149 
150         ResourceValue r = getResourceValue(value);
151 
152         if (r != null) {
153             value = r.getValue();
154         }
155 
156         if (value.charAt(0) == '#') {
157             return ResourceHelper.getColor(value);
158         }
159 
160         try {
161             return XmlUtils.convertValueToInt(value, defaultValue);
162         } catch (NumberFormatException e) {
163             // This is probably an enum
164             Map<String, Integer> enumValues = BridgeConstants.NS_RESOURCES.equals(namespace) ?
165                     mFrameworkEnumValueSupplier.apply(attribute) :
166                     mProjectEnumValueSupplier.apply(attribute);
167 
168             Integer enumValue = enumValues != null ? enumValues.get(value) : null;
169             if (enumValue != null) {
170                 return enumValue;
171             }
172 
173             // We weren't able to find the enum int value
174             throw e;
175         }
176     }
177 
178     @Override
getAttributeUnsignedIntValue(String namespace, String attribute, int defaultValue)179     public int getAttributeUnsignedIntValue(String namespace, String attribute,
180             int defaultValue) {
181         String value = getAttributeValue(namespace, attribute);
182         if (value != null) {
183             ResourceValue r = getResourceValue(value);
184 
185             if (r != null) {
186                 value = r.getValue();
187             }
188 
189             return XmlUtils.convertValueToUnsignedInt(value, defaultValue);
190         }
191 
192         return defaultValue;
193     }
194 
195     @Override
getAttributeFloatValue(String namespace, String attribute, float defaultValue)196     public float getAttributeFloatValue(String namespace, String attribute,
197             float defaultValue) {
198         String s = getAttributeValue(namespace, attribute);
199         if (s != null) {
200             ResourceValue r = getResourceValue(s);
201 
202             if (r != null) {
203                 s = r.getValue();
204             }
205 
206             return Float.parseFloat(s);
207         }
208 
209         return defaultValue;
210     }
211 
212     @Override
getAttributeListValue(int index, String[] options, int defaultValue)213     public int getAttributeListValue(int index,
214             String[] options, int defaultValue) {
215         return XmlUtils.convertValueToList(
216             getAttributeValue(index), options, defaultValue);
217     }
218 
219     @Override
getAttributeBooleanValue(int index, boolean defaultValue)220     public boolean getAttributeBooleanValue(int index, boolean defaultValue) {
221         String value = getAttributeValue(index);
222         if (value != null) {
223             ResourceValue r = getResourceValue(value);
224 
225             if (r != null) {
226                 value = r.getValue();
227             }
228 
229             return XmlUtils.convertValueToBoolean(value, defaultValue);
230         }
231 
232         return defaultValue;
233     }
234 
235     @Override
getAttributeResourceValue(int index, int defaultValue)236     public int getAttributeResourceValue(int index, int defaultValue) {
237         String value = getAttributeValue(index);
238 
239         return resolveResourceValue(value, defaultValue);
240     }
241 
242     @Override
getAttributeIntValue(int index, int defaultValue)243     public int getAttributeIntValue(int index, int defaultValue) {
244         return getAttributeIntValue(mParser.getAttributeNamespace(index),
245                 getAttributeName(index)
246                 , defaultValue);
247     }
248 
249     @Override
getAttributeUnsignedIntValue(int index, int defaultValue)250     public int getAttributeUnsignedIntValue(int index, int defaultValue) {
251         String value = getAttributeValue(index);
252         if (value != null) {
253             ResourceValue r = getResourceValue(value);
254 
255             if (r != null) {
256                 value = r.getValue();
257             }
258 
259             return XmlUtils.convertValueToUnsignedInt(value, defaultValue);
260         }
261 
262         return defaultValue;
263     }
264 
265     @Override
getAttributeFloatValue(int index, float defaultValue)266     public float getAttributeFloatValue(int index, float defaultValue) {
267         String s = getAttributeValue(index);
268         if (s != null) {
269             ResourceValue r = getResourceValue(s);
270 
271             if (r != null) {
272                 s = r.getValue();
273             }
274 
275             return Float.parseFloat(s);
276         }
277 
278         return defaultValue;
279     }
280 
281     // -- private helper methods
282 
283     /**
284      * Returns a resolved {@link ResourceValue} from a given value.
285      */
getResourceValue(String value)286     private ResourceValue getResourceValue(String value) {
287         // now look for this particular value
288         RenderResources resources = mContext.getRenderResources();
289         return resources.resolveResValue(resources.findResValue(value, mPlatformFile));
290     }
291 
292     /**
293      * Resolves and return a value to its associated integer.
294      */
resolveResourceValue(String value, int defaultValue)295     private int resolveResourceValue(String value, int defaultValue) {
296         ResourceValue resource = getResourceValue(value);
297         if (resource != null) {
298             Integer id = null;
299             if (mPlatformFile || resource.isFramework()) {
300                 id = Bridge.getResourceId(resource.getResourceType(), resource.getName());
301             } else {
302                 id = mContext.getLayoutlibCallback().getResourceId(
303                         resource.getResourceType(), resource.getName());
304             }
305 
306             if (id != null) {
307                 return id;
308             }
309         }
310 
311         return defaultValue;
312     }
313 }
314