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
17package android.util;
18
19import com.android.ide.common.rendering.api.AttrResourceValue;
20import com.android.ide.common.rendering.api.RenderResources;
21import com.android.ide.common.rendering.api.ResourceValue;
22import com.android.internal.util.XmlUtils;
23import com.android.layoutlib.bridge.Bridge;
24import com.android.layoutlib.bridge.BridgeConstants;
25import com.android.layoutlib.bridge.android.BridgeContext;
26import com.android.layoutlib.bridge.impl.ResourceHelper;
27import com.android.resources.ResourceType;
28
29import org.xmlpull.v1.XmlPullParser;
30
31import android.annotation.NonNull;
32
33import java.util.Map;
34import java.util.function.Function;
35
36/**
37 * A correct implementation of the {@link AttributeSet} interface on top of a XmlPullParser
38 */
39public 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
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
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
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
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
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
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
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
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
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
213    public int getAttributeListValue(int index,
214            String[] options, int defaultValue) {
215        return XmlUtils.convertValueToList(
216            getAttributeValue(index), options, defaultValue);
217    }
218
219    @Override
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
236    public int getAttributeResourceValue(int index, int defaultValue) {
237        String value = getAttributeValue(index);
238
239        return resolveResourceValue(value, defaultValue);
240    }
241
242    @Override
243    public int getAttributeIntValue(int index, int defaultValue) {
244        return getAttributeIntValue(mParser.getAttributeNamespace(index),
245                getAttributeName(index)
246                , defaultValue);
247    }
248
249    @Override
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
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     */
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     */
295    private int resolveResourceValue(String value, int defaultValue) {
296        ResourceValue resource = getResourceValue(value);
297        if (resource != null) {
298            Integer id;
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