1 /* 2 * Copyright (C) 2011 The Android Open Source Project 3 * 4 * Licensed under the Eclipse Public License, Version 1.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.eclipse.org/org/documents/epl-v10.php 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 com.android.ide.eclipse.adt.internal.resources; 18 19 import static com.android.SdkConstants.ANDROID_PREFIX; 20 import static com.android.SdkConstants.ANDROID_STYLE_RESOURCE_PREFIX; 21 import static com.android.SdkConstants.ANDROID_URI; 22 import static com.android.SdkConstants.ATTR_COLOR; 23 import static com.android.SdkConstants.ATTR_NAME; 24 import static com.android.SdkConstants.ATTR_TYPE; 25 import static com.android.SdkConstants.DOT_XML; 26 import static com.android.SdkConstants.EXT_XML; 27 import static com.android.SdkConstants.FD_RESOURCES; 28 import static com.android.SdkConstants.FD_RES_VALUES; 29 import static com.android.SdkConstants.PREFIX_RESOURCE_REF; 30 import static com.android.SdkConstants.STYLE_RESOURCE_PREFIX; 31 import static com.android.SdkConstants.TAG_ITEM; 32 import static com.android.SdkConstants.TAG_RESOURCES; 33 import static com.android.ide.eclipse.adt.AdtConstants.WS_SEP; 34 35 import com.android.ide.common.rendering.api.ResourceValue; 36 import com.android.ide.common.resources.ResourceDeltaKind; 37 import com.android.ide.common.resources.ResourceResolver; 38 import com.android.ide.common.resources.ResourceUrl; 39 import com.android.ide.common.resources.configuration.CountryCodeQualifier; 40 import com.android.ide.common.resources.configuration.DensityQualifier; 41 import com.android.ide.common.resources.configuration.FolderConfiguration; 42 import com.android.ide.common.resources.configuration.KeyboardStateQualifier; 43 import com.android.ide.common.resources.configuration.LayoutDirectionQualifier; 44 import com.android.ide.common.resources.configuration.LocaleQualifier; 45 import com.android.ide.common.resources.configuration.NavigationMethodQualifier; 46 import com.android.ide.common.resources.configuration.NavigationStateQualifier; 47 import com.android.ide.common.resources.configuration.NetworkCodeQualifier; 48 import com.android.ide.common.resources.configuration.NightModeQualifier; 49 import com.android.ide.common.resources.configuration.ResourceQualifier; 50 import com.android.ide.common.resources.configuration.ScreenDimensionQualifier; 51 import com.android.ide.common.resources.configuration.ScreenHeightQualifier; 52 import com.android.ide.common.resources.configuration.ScreenOrientationQualifier; 53 import com.android.ide.common.resources.configuration.ScreenRatioQualifier; 54 import com.android.ide.common.resources.configuration.ScreenSizeQualifier; 55 import com.android.ide.common.resources.configuration.ScreenWidthQualifier; 56 import com.android.ide.common.resources.configuration.SmallestScreenWidthQualifier; 57 import com.android.ide.common.resources.configuration.TextInputMethodQualifier; 58 import com.android.ide.common.resources.configuration.TouchScreenQualifier; 59 import com.android.ide.common.resources.configuration.UiModeQualifier; 60 import com.android.ide.common.resources.configuration.VersionQualifier; 61 import com.android.ide.eclipse.adt.AdtPlugin; 62 import com.android.ide.eclipse.adt.internal.editors.AndroidXmlEditor; 63 import com.android.ide.eclipse.adt.internal.editors.Hyperlinks; 64 import com.android.ide.eclipse.adt.internal.editors.IconFactory; 65 import com.android.ide.eclipse.adt.internal.editors.layout.gle2.ImageUtils; 66 import com.android.ide.eclipse.adt.internal.editors.layout.refactoring.VisualRefactoring; 67 import com.android.ide.eclipse.adt.internal.wizards.newxmlfile.NewXmlFileWizard; 68 import com.android.resources.FolderTypeRelationship; 69 import com.android.resources.ResourceFolderType; 70 import com.android.resources.ResourceType; 71 import com.android.utils.Pair; 72 73 import org.eclipse.core.resources.IFile; 74 import org.eclipse.core.resources.IProject; 75 import org.eclipse.core.resources.IResource; 76 import org.eclipse.core.resources.IResourceDelta; 77 import org.eclipse.core.runtime.CoreException; 78 import org.eclipse.core.runtime.IPath; 79 import org.eclipse.core.runtime.Path; 80 import org.eclipse.jface.text.IRegion; 81 import org.eclipse.jface.text.Region; 82 import org.eclipse.swt.graphics.Image; 83 import org.eclipse.swt.graphics.RGB; 84 import org.eclipse.wst.sse.core.StructuredModelManager; 85 import org.eclipse.wst.sse.core.internal.provisional.IModelManager; 86 import org.eclipse.wst.sse.core.internal.provisional.IStructuredModel; 87 import org.eclipse.wst.sse.core.internal.provisional.IndexedRegion; 88 import org.eclipse.wst.sse.core.internal.provisional.text.IStructuredDocument; 89 import org.eclipse.wst.xml.core.internal.document.ElementImpl; 90 import org.eclipse.wst.xml.core.internal.provisional.document.IDOMModel; 91 import org.w3c.dom.Attr; 92 import org.w3c.dom.Document; 93 import org.w3c.dom.Element; 94 import org.w3c.dom.NamedNodeMap; 95 import org.w3c.dom.Node; 96 import org.w3c.dom.NodeList; 97 import org.w3c.dom.Text; 98 import org.xml.sax.InputSource; 99 100 import java.io.BufferedInputStream; 101 import java.io.ByteArrayInputStream; 102 import java.io.File; 103 import java.io.FileInputStream; 104 import java.io.IOException; 105 import java.io.InputStream; 106 import java.io.UnsupportedEncodingException; 107 import java.util.HashMap; 108 import java.util.List; 109 import java.util.Map; 110 import java.util.Set; 111 112 import javax.xml.parsers.DocumentBuilder; 113 import javax.xml.parsers.DocumentBuilderFactory; 114 115 /** 116 * Helper class to deal with SWT specifics for the resources. 117 */ 118 @SuppressWarnings("restriction") // XML model 119 public class ResourceHelper { 120 121 private final static Map<Class<?>, Image> sIconMap = new HashMap<Class<?>, Image>( 122 FolderConfiguration.getQualifierCount()); 123 124 static { 125 try { 126 IconFactory factory = IconFactory.getInstance(); 127 sIconMap.put(CountryCodeQualifier.class, factory.getIcon("mcc")); //$NON-NLS-1$ 128 sIconMap.put(NetworkCodeQualifier.class, factory.getIcon("mnc")); //$NON-NLS-1$ 129 sIconMap.put(LocaleQualifier.class, factory.getIcon("language")); //$NON-NLS-1$ 130 sIconMap.put(LayoutDirectionQualifier.class, factory.getIcon("bidi")); //$NON-NLS-1$ 131 sIconMap.put(ScreenSizeQualifier.class, factory.getIcon("size")); //$NON-NLS-1$ 132 sIconMap.put(ScreenRatioQualifier.class, factory.getIcon("ratio")); //$NON-NLS-1$ 133 sIconMap.put(ScreenOrientationQualifier.class, factory.getIcon("orientation")); //$NON-NLS-1$ 134 sIconMap.put(UiModeQualifier.class, factory.getIcon("dockmode")); //$NON-NLS-1$ 135 sIconMap.put(NightModeQualifier.class, factory.getIcon("nightmode")); //$NON-NLS-1$ 136 sIconMap.put(DensityQualifier.class, factory.getIcon("dpi")); //$NON-NLS-1$ 137 sIconMap.put(TouchScreenQualifier.class, factory.getIcon("touch")); //$NON-NLS-1$ 138 sIconMap.put(KeyboardStateQualifier.class, factory.getIcon("keyboard")); //$NON-NLS-1$ 139 sIconMap.put(TextInputMethodQualifier.class, factory.getIcon("text_input")); //$NON-NLS-1$ 140 sIconMap.put(NavigationStateQualifier.class, factory.getIcon("navpad")); //$NON-NLS-1$ 141 sIconMap.put(NavigationMethodQualifier.class, factory.getIcon("navpad")); //$NON-NLS-1$ 142 sIconMap.put(ScreenDimensionQualifier.class, factory.getIcon("dimension")); //$NON-NLS-1$ 143 sIconMap.put(VersionQualifier.class, factory.getIcon("version")); //$NON-NLS-1$ 144 sIconMap.put(ScreenWidthQualifier.class, factory.getIcon("width")); //$NON-NLS-1$ 145 sIconMap.put(ScreenHeightQualifier.class, factory.getIcon("height")); //$NON-NLS-1$ 146 sIconMap.put(SmallestScreenWidthQualifier.class,factory.getIcon("swidth")); //$NON-NLS-1$ 147 } catch (Throwable t) { 148 AdtPlugin.log(t , null); 149 } 150 } 151 152 /** 153 * Returns the icon for the qualifier. 154 */ 155 public static Image getIcon(Class<? extends ResourceQualifier> theClass) { 156 return sIconMap.get(theClass); 157 } 158 159 /** 160 * Returns a {@link ResourceDeltaKind} from an {@link IResourceDelta} value. 161 * @param kind a {@link IResourceDelta} integer constant. 162 * @return a matching {@link ResourceDeltaKind} or null. 163 * 164 * @see IResourceDelta#ADDED 165 * @see IResourceDelta#REMOVED 166 * @see IResourceDelta#CHANGED 167 */ 168 public static ResourceDeltaKind getResourceDeltaKind(int kind) { 169 switch (kind) { 170 case IResourceDelta.ADDED: 171 return ResourceDeltaKind.ADDED; 172 case IResourceDelta.REMOVED: 173 return ResourceDeltaKind.REMOVED; 174 case IResourceDelta.CHANGED: 175 return ResourceDeltaKind.CHANGED; 176 } 177 178 return null; 179 } 180 181 /** 182 * Is this a resource that can be defined in any file within the "values" folder? 183 * <p> 184 * Some resource types can be defined <b>both</b> as a separate XML file as well 185 * as defined within a value XML file. This method will return true for these types 186 * as well. In other words, a ResourceType can return true for both 187 * {@link #isValueBasedResourceType} and {@link #isFileBasedResourceType}. 188 * 189 * @param type the resource type to check 190 * @return true if the given resource type can be represented as a value under the 191 * values/ folder 192 */ 193 public static boolean isValueBasedResourceType(ResourceType type) { 194 List<ResourceFolderType> folderTypes = FolderTypeRelationship.getRelatedFolders(type); 195 for (ResourceFolderType folderType : folderTypes) { 196 if (folderType == ResourceFolderType.VALUES) { 197 return true; 198 } 199 } 200 201 return false; 202 } 203 204 /** 205 * Is this a resource that is defined in a file named by the resource plus the XML 206 * extension? 207 * <p> 208 * Some resource types can be defined <b>both</b> as a separate XML file as well as 209 * defined within a value XML file along with other properties. This method will 210 * return true for these resource types as well. In other words, a ResourceType can 211 * return true for both {@link #isValueBasedResourceType} and 212 * {@link #isFileBasedResourceType}. 213 * 214 * @param type the resource type to check 215 * @return true if the given resource type is stored in a file named by the resource 216 */ 217 public static boolean isFileBasedResourceType(ResourceType type) { 218 List<ResourceFolderType> folderTypes = FolderTypeRelationship.getRelatedFolders(type); 219 for (ResourceFolderType folderType : folderTypes) { 220 if (folderType != ResourceFolderType.VALUES) { 221 222 if (type == ResourceType.ID) { 223 // The folder types for ID is not only VALUES but also 224 // LAYOUT and MENU. However, unlike resources, they are only defined 225 // inline there so for the purposes of isFileBasedResourceType 226 // (where the intent is to figure out files that are uniquely identified 227 // by a resource's name) this method should return false anyway. 228 return false; 229 } 230 231 return true; 232 } 233 } 234 235 return false; 236 } 237 238 /** 239 * Returns true if this class can create the given resource 240 * 241 * @param resource the resource to be created 242 * @return true if the {@link #createResource} method can create this resource 243 */ 244 public static boolean canCreateResource(String resource) { 245 // Cannot create framework resources 246 if (resource.startsWith(ANDROID_PREFIX)) { 247 return false; 248 } 249 250 ResourceUrl parsed = ResourceUrl.parse(resource); 251 if (parsed != null) { 252 if (parsed.framework) { 253 return false; 254 } 255 ResourceType type = parsed.type; 256 String name = parsed.name; 257 258 // Make sure the name is valid 259 ResourceNameValidator validator = 260 ResourceNameValidator.create(false, (Set<String>) null /* existing */, type); 261 if (validator.isValid(name) != null) { 262 return false; 263 } 264 265 return canCreateResourceType(type); 266 } 267 268 return false; 269 } 270 271 /** 272 * Returns true if this class can create resources of the given resource 273 * type 274 * 275 * @param type the type of resource to be created 276 * @return true if the {@link #createResource} method can create resources 277 * of this type (provided the name parameter is also valid) 278 */ 279 public static boolean canCreateResourceType(ResourceType type) { 280 // We can create all value types 281 if (isValueBasedResourceType(type)) { 282 return true; 283 } 284 285 // We can create -some- file-based types - those supported by the New XML wizard: 286 for (ResourceFolderType folderType : FolderTypeRelationship.getRelatedFolders(type)) { 287 if (NewXmlFileWizard.canCreateXmlFile(folderType)) { 288 return true; 289 } 290 } 291 292 return false; 293 } 294 295 /** Creates a file-based resource, like a layout. Used by {@link #createResource} */ 296 private static Pair<IFile,IRegion> createFileResource(IProject project, ResourceType type, 297 String name) { 298 299 ResourceFolderType folderType = null; 300 for (ResourceFolderType f : FolderTypeRelationship.getRelatedFolders(type)) { 301 if (NewXmlFileWizard.canCreateXmlFile(f)) { 302 folderType = f; 303 break; 304 } 305 } 306 if (folderType == null) { 307 return null; 308 } 309 310 // Find "dimens.xml" file in res/values/ (or corresponding name for other 311 // value types) 312 IPath projectPath = new Path(FD_RESOURCES + WS_SEP + folderType.getName() + WS_SEP 313 + name + '.' + EXT_XML); 314 IFile file = project.getFile(projectPath); 315 return NewXmlFileWizard.createXmlFile(project, file, folderType); 316 } 317 318 /** 319 * Creates a resource of a given type, name and (if applicable) value 320 * 321 * @param project the project to contain the resource 322 * @param type the type of resource 323 * @param name the name of the resource 324 * @param value the value of the resource, if it is a value-type resource 325 * @return a pair of the file containing the resource and a region where the value 326 * appears 327 */ 328 public static Pair<IFile,IRegion> createResource(IProject project, ResourceType type, 329 String name, String value) { 330 if (!isValueBasedResourceType(type)) { 331 return createFileResource(project, type, name); 332 } 333 334 // Find "dimens.xml" file in res/values/ (or corresponding name for other 335 // value types) 336 String typeName = type.getName(); 337 String fileName = typeName + 's'; 338 String projectPath = FD_RESOURCES + WS_SEP + FD_RES_VALUES + WS_SEP 339 + fileName + '.' + EXT_XML; 340 Object editRequester = project; 341 IResource member = project.findMember(projectPath); 342 String tagName = Hyperlinks.getTagName(type); 343 boolean createEmptyTag = type == ResourceType.ID; 344 if (member != null) { 345 if (member instanceof IFile) { 346 IFile file = (IFile) member; 347 // File exists: Must add item to the XML 348 IModelManager manager = StructuredModelManager.getModelManager(); 349 IStructuredModel model = null; 350 try { 351 model = manager.getExistingModelForEdit(file); 352 if (model == null) { 353 model = manager.getModelForEdit(file); 354 } 355 if (model instanceof IDOMModel) { 356 model.beginRecording(editRequester, String.format("Add %1$s", 357 type.getDisplayName())); 358 IDOMModel domModel = (IDOMModel) model; 359 Document document = domModel.getDocument(); 360 Element root = document.getDocumentElement(); 361 IStructuredDocument structuredDocument = model.getStructuredDocument(); 362 Node lastElement = null; 363 NodeList childNodes = root.getChildNodes(); 364 String indent = null; 365 for (int i = childNodes.getLength() - 1; i >= 0; i--) { 366 Node node = childNodes.item(i); 367 if (node.getNodeType() == Node.ELEMENT_NODE) { 368 lastElement = node; 369 indent = AndroidXmlEditor.getIndent(structuredDocument, node); 370 break; 371 } 372 } 373 if (indent == null || indent.length() == 0) { 374 indent = " "; //$NON-NLS-1$ 375 } 376 Node nextChild = lastElement != null ? lastElement.getNextSibling() : null; 377 Text indentNode = document.createTextNode('\n' + indent); 378 root.insertBefore(indentNode, nextChild); 379 Element element = document.createElement(tagName); 380 if (createEmptyTag) { 381 if (element instanceof ElementImpl) { 382 ElementImpl elementImpl = (ElementImpl) element; 383 elementImpl.setEmptyTag(true); 384 } 385 } 386 element.setAttribute(ATTR_NAME, name); 387 if (!tagName.equals(typeName)) { 388 element.setAttribute(ATTR_TYPE, typeName); 389 } 390 root.insertBefore(element, nextChild); 391 IRegion region = null; 392 393 if (createEmptyTag) { 394 IndexedRegion domRegion = VisualRefactoring.getRegion(element); 395 int endOffset = domRegion.getEndOffset(); 396 region = new Region(endOffset, 0); 397 } else { 398 Node valueNode = document.createTextNode(value); 399 element.appendChild(valueNode); 400 401 IndexedRegion domRegion = VisualRefactoring.getRegion(valueNode); 402 int startOffset = domRegion.getStartOffset(); 403 int length = domRegion.getLength(); 404 region = new Region(startOffset, length); 405 } 406 model.save(); 407 return Pair.of(file, region); 408 } 409 } catch (Exception e) { 410 AdtPlugin.log(e, "Cannot access XML value model"); 411 } finally { 412 if (model != null) { 413 model.endRecording(editRequester); 414 model.releaseFromEdit(); 415 } 416 } 417 } 418 419 return null; 420 } else { 421 // No such file exists: just create it 422 String prolog = "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"; //$NON-NLS-1$ 423 StringBuilder sb = new StringBuilder(prolog); 424 425 String root = TAG_RESOURCES; 426 sb.append('<').append(root).append('>').append('\n'); 427 sb.append(" "); //$NON-NLS-1$ 428 sb.append('<'); 429 sb.append(tagName); 430 sb.append(" name=\""); //$NON-NLS-1$ 431 sb.append(name); 432 sb.append('"'); 433 if (!tagName.equals(typeName)) { 434 sb.append(" type=\""); //$NON-NLS-1$ 435 sb.append(typeName); 436 sb.append('"'); 437 } 438 int start, end; 439 if (createEmptyTag) { 440 sb.append("/>"); //$NON-NLS-1$ 441 start = sb.length(); 442 end = sb.length(); 443 } else { 444 sb.append('>'); 445 start = sb.length(); 446 sb.append(value); 447 end = sb.length(); 448 sb.append('<').append('/'); 449 sb.append(tagName); 450 sb.append('>'); 451 } 452 sb.append('\n').append('<').append('/').append(root).append('>').append('\n'); 453 String result = sb.toString(); 454 // TODO: Pretty print string (wait until that CL is integrated) 455 String error = null; 456 try { 457 byte[] buf = result.getBytes("UTF8"); //$NON-NLS-1$ 458 InputStream stream = new ByteArrayInputStream(buf); 459 IFile file = project.getFile(new Path(projectPath)); 460 file.create(stream, true /*force*/, null /*progress*/); 461 IRegion region = new Region(start, end - start); 462 return Pair.of(file, region); 463 } catch (UnsupportedEncodingException e) { 464 error = e.getMessage(); 465 } catch (CoreException e) { 466 error = e.getMessage(); 467 } 468 469 error = String.format("Failed to generate %1$s: %2$s", name, error); 470 AdtPlugin.displayError("New Android XML File", error); 471 } 472 return null; 473 } 474 475 /** 476 * Returns the theme name to be shown for theme styles, e.g. for "@style/Theme" it 477 * returns "Theme" 478 * 479 * @param style a theme style string 480 * @return the user visible theme name 481 */ 482 public static String styleToTheme(String style) { 483 if (style.startsWith(STYLE_RESOURCE_PREFIX)) { 484 style = style.substring(STYLE_RESOURCE_PREFIX.length()); 485 } else if (style.startsWith(ANDROID_STYLE_RESOURCE_PREFIX)) { 486 style = style.substring(ANDROID_STYLE_RESOURCE_PREFIX.length()); 487 } else if (style.startsWith(PREFIX_RESOURCE_REF)) { 488 // @package:style/foo 489 int index = style.indexOf('/'); 490 if (index != -1) { 491 style = style.substring(index + 1); 492 } 493 } 494 return style; 495 } 496 497 /** 498 * Returns true if the given style represents a project theme 499 * 500 * @param style a theme style string 501 * @return true if the style string represents a project theme, as opposed 502 * to a framework theme 503 */ 504 public static boolean isProjectStyle(String style) { 505 assert style.startsWith(STYLE_RESOURCE_PREFIX) 506 || style.startsWith(ANDROID_STYLE_RESOURCE_PREFIX) : style; 507 508 return style.startsWith(STYLE_RESOURCE_PREFIX); 509 } 510 511 /** 512 * Returns the layout resource name for the given layout file, e.g. for 513 * /res/layout/foo.xml returns foo. 514 * 515 * @param layoutFile the layout file whose name we want to look up 516 * @return the layout name 517 */ 518 public static String getLayoutName(IFile layoutFile) { 519 String layoutName = layoutFile.getName(); 520 int dotIndex = layoutName.indexOf('.'); 521 if (dotIndex != -1) { 522 layoutName = layoutName.substring(0, dotIndex); 523 } 524 return layoutName; 525 } 526 527 /** 528 * Tries to resolve the given resource value to an actual RGB color. For state lists 529 * it will pick the simplest/fallback color. 530 * 531 * @param resources the resource resolver to use to follow color references 532 * @param color the color to resolve 533 * @return the corresponding {@link RGB} color, or null 534 */ 535 public static RGB resolveColor(ResourceResolver resources, ResourceValue color) { 536 color = resources.resolveResValue(color); 537 if (color == null) { 538 return null; 539 } 540 String value = color.getValue(); 541 542 while (value != null) { 543 if (value.startsWith("#")) { //$NON-NLS-1$ 544 try { 545 int rgba = ImageUtils.getColor(value); 546 // Drop alpha channel 547 return ImageUtils.intToRgb(rgba); 548 } catch (NumberFormatException nfe) { 549 // Pass 550 } 551 return null; 552 } 553 if (value.startsWith(PREFIX_RESOURCE_REF)) { 554 boolean isFramework = color.isFramework(); 555 color = resources.findResValue(value, isFramework); 556 if (color != null) { 557 value = color.getValue(); 558 } else { 559 break; 560 } 561 } else { 562 File file = new File(value); 563 if (file.exists() && file.getName().endsWith(DOT_XML)) { 564 // Parse 565 DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); 566 BufferedInputStream bis = null; 567 try { 568 bis = new BufferedInputStream(new FileInputStream(file)); 569 InputSource is = new InputSource(bis); 570 factory.setNamespaceAware(true); 571 factory.setValidating(false); 572 DocumentBuilder builder = factory.newDocumentBuilder(); 573 Document document = builder.parse(is); 574 NodeList items = document.getElementsByTagName(TAG_ITEM); 575 576 value = findColorValue(items); 577 continue; 578 } catch (Exception e) { 579 AdtPlugin.log(e, "Failed parsing color file %1$s", file.getName()); 580 } finally { 581 if (bis != null) { 582 try { 583 bis.close(); 584 } catch (IOException e) { 585 // Nothing useful can be done here 586 } 587 } 588 } 589 } 590 591 return null; 592 } 593 } 594 595 return null; 596 } 597 598 /** 599 * Searches a color XML file for the color definition element that does not 600 * have an associated state and returns its color 601 */ 602 private static String findColorValue(NodeList items) { 603 for (int i = 0, n = items.getLength(); i < n; i++) { 604 // Find non-state color definition 605 Node item = items.item(i); 606 boolean hasState = false; 607 if (item.getNodeType() == Node.ELEMENT_NODE) { 608 Element element = (Element) item; 609 if (element.hasAttributeNS(ANDROID_URI, ATTR_COLOR)) { 610 NamedNodeMap attributes = element.getAttributes(); 611 for (int j = 0, m = attributes.getLength(); j < m; j++) { 612 Attr attribute = (Attr) attributes.item(j); 613 if (attribute.getLocalName().startsWith("state_")) { //$NON-NLS-1$ 614 hasState = true; 615 break; 616 } 617 } 618 619 if (!hasState) { 620 return element.getAttributeNS(ANDROID_URI, ATTR_COLOR); 621 } 622 } 623 } 624 } 625 626 return null; 627 } 628 } 629