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