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