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