| /* |
| * Copyright (C) 2011 The Android Open Source Project |
| * |
| * Licensed under the Eclipse Public License, Version 1.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.eclipse.org/org/documents/epl-v10.php |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| package com.android.ide.eclipse.adt.internal.resources; |
| |
| import static com.android.SdkConstants.ANDROID_PREFIX; |
| import static com.android.SdkConstants.ANDROID_STYLE_RESOURCE_PREFIX; |
| import static com.android.SdkConstants.ANDROID_URI; |
| import static com.android.SdkConstants.ATTR_COLOR; |
| import static com.android.SdkConstants.ATTR_NAME; |
| import static com.android.SdkConstants.ATTR_TYPE; |
| import static com.android.SdkConstants.DOT_XML; |
| import static com.android.SdkConstants.EXT_XML; |
| import static com.android.SdkConstants.FD_RESOURCES; |
| import static com.android.SdkConstants.FD_RES_VALUES; |
| import static com.android.SdkConstants.PREFIX_RESOURCE_REF; |
| import static com.android.SdkConstants.STYLE_RESOURCE_PREFIX; |
| import static com.android.SdkConstants.TAG_ITEM; |
| import static com.android.SdkConstants.TAG_RESOURCES; |
| import static com.android.ide.eclipse.adt.AdtConstants.WS_SEP; |
| |
| import com.android.ide.common.rendering.api.ResourceValue; |
| import com.android.ide.common.resources.ResourceDeltaKind; |
| import com.android.ide.common.resources.ResourceResolver; |
| import com.android.ide.common.resources.ResourceUrl; |
| import com.android.ide.common.resources.configuration.CountryCodeQualifier; |
| import com.android.ide.common.resources.configuration.DensityQualifier; |
| import com.android.ide.common.resources.configuration.FolderConfiguration; |
| import com.android.ide.common.resources.configuration.KeyboardStateQualifier; |
| import com.android.ide.common.resources.configuration.LayoutDirectionQualifier; |
| import com.android.ide.common.resources.configuration.LocaleQualifier; |
| import com.android.ide.common.resources.configuration.NavigationMethodQualifier; |
| import com.android.ide.common.resources.configuration.NavigationStateQualifier; |
| import com.android.ide.common.resources.configuration.NetworkCodeQualifier; |
| import com.android.ide.common.resources.configuration.NightModeQualifier; |
| import com.android.ide.common.resources.configuration.ResourceQualifier; |
| import com.android.ide.common.resources.configuration.ScreenDimensionQualifier; |
| import com.android.ide.common.resources.configuration.ScreenHeightQualifier; |
| import com.android.ide.common.resources.configuration.ScreenOrientationQualifier; |
| import com.android.ide.common.resources.configuration.ScreenRatioQualifier; |
| import com.android.ide.common.resources.configuration.ScreenSizeQualifier; |
| import com.android.ide.common.resources.configuration.ScreenWidthQualifier; |
| import com.android.ide.common.resources.configuration.SmallestScreenWidthQualifier; |
| import com.android.ide.common.resources.configuration.TextInputMethodQualifier; |
| import com.android.ide.common.resources.configuration.TouchScreenQualifier; |
| import com.android.ide.common.resources.configuration.UiModeQualifier; |
| import com.android.ide.common.resources.configuration.VersionQualifier; |
| import com.android.ide.eclipse.adt.AdtPlugin; |
| import com.android.ide.eclipse.adt.internal.editors.AndroidXmlEditor; |
| import com.android.ide.eclipse.adt.internal.editors.Hyperlinks; |
| import com.android.ide.eclipse.adt.internal.editors.IconFactory; |
| import com.android.ide.eclipse.adt.internal.editors.layout.gle2.ImageUtils; |
| import com.android.ide.eclipse.adt.internal.editors.layout.refactoring.VisualRefactoring; |
| import com.android.ide.eclipse.adt.internal.wizards.newxmlfile.NewXmlFileWizard; |
| import com.android.resources.FolderTypeRelationship; |
| import com.android.resources.ResourceFolderType; |
| import com.android.resources.ResourceType; |
| import com.android.utils.Pair; |
| |
| import org.eclipse.core.resources.IFile; |
| import org.eclipse.core.resources.IProject; |
| import org.eclipse.core.resources.IResource; |
| import org.eclipse.core.resources.IResourceDelta; |
| import org.eclipse.core.runtime.CoreException; |
| import org.eclipse.core.runtime.IPath; |
| import org.eclipse.core.runtime.Path; |
| import org.eclipse.jface.text.IRegion; |
| import org.eclipse.jface.text.Region; |
| import org.eclipse.swt.graphics.Image; |
| import org.eclipse.swt.graphics.RGB; |
| import org.eclipse.wst.sse.core.StructuredModelManager; |
| import org.eclipse.wst.sse.core.internal.provisional.IModelManager; |
| import org.eclipse.wst.sse.core.internal.provisional.IStructuredModel; |
| import org.eclipse.wst.sse.core.internal.provisional.IndexedRegion; |
| import org.eclipse.wst.sse.core.internal.provisional.text.IStructuredDocument; |
| import org.eclipse.wst.xml.core.internal.document.ElementImpl; |
| import org.eclipse.wst.xml.core.internal.provisional.document.IDOMModel; |
| import org.w3c.dom.Attr; |
| import org.w3c.dom.Document; |
| import org.w3c.dom.Element; |
| import org.w3c.dom.NamedNodeMap; |
| import org.w3c.dom.Node; |
| import org.w3c.dom.NodeList; |
| import org.w3c.dom.Text; |
| import org.xml.sax.InputSource; |
| |
| import java.io.BufferedInputStream; |
| import java.io.ByteArrayInputStream; |
| import java.io.File; |
| import java.io.FileInputStream; |
| import java.io.IOException; |
| import java.io.InputStream; |
| import java.io.UnsupportedEncodingException; |
| import java.util.HashMap; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Set; |
| |
| import javax.xml.parsers.DocumentBuilder; |
| import javax.xml.parsers.DocumentBuilderFactory; |
| |
| /** |
| * Helper class to deal with SWT specifics for the resources. |
| */ |
| @SuppressWarnings("restriction") // XML model |
| public class ResourceHelper { |
| |
| private final static Map<Class<?>, Image> sIconMap = new HashMap<Class<?>, Image>( |
| FolderConfiguration.getQualifierCount()); |
| |
| static { |
| try { |
| IconFactory factory = IconFactory.getInstance(); |
| sIconMap.put(CountryCodeQualifier.class, factory.getIcon("mcc")); //$NON-NLS-1$ |
| sIconMap.put(NetworkCodeQualifier.class, factory.getIcon("mnc")); //$NON-NLS-1$ |
| sIconMap.put(LocaleQualifier.class, factory.getIcon("language")); //$NON-NLS-1$ |
| sIconMap.put(LayoutDirectionQualifier.class, factory.getIcon("bidi")); //$NON-NLS-1$ |
| sIconMap.put(ScreenSizeQualifier.class, factory.getIcon("size")); //$NON-NLS-1$ |
| sIconMap.put(ScreenRatioQualifier.class, factory.getIcon("ratio")); //$NON-NLS-1$ |
| sIconMap.put(ScreenOrientationQualifier.class, factory.getIcon("orientation")); //$NON-NLS-1$ |
| sIconMap.put(UiModeQualifier.class, factory.getIcon("dockmode")); //$NON-NLS-1$ |
| sIconMap.put(NightModeQualifier.class, factory.getIcon("nightmode")); //$NON-NLS-1$ |
| sIconMap.put(DensityQualifier.class, factory.getIcon("dpi")); //$NON-NLS-1$ |
| sIconMap.put(TouchScreenQualifier.class, factory.getIcon("touch")); //$NON-NLS-1$ |
| sIconMap.put(KeyboardStateQualifier.class, factory.getIcon("keyboard")); //$NON-NLS-1$ |
| sIconMap.put(TextInputMethodQualifier.class, factory.getIcon("text_input")); //$NON-NLS-1$ |
| sIconMap.put(NavigationStateQualifier.class, factory.getIcon("navpad")); //$NON-NLS-1$ |
| sIconMap.put(NavigationMethodQualifier.class, factory.getIcon("navpad")); //$NON-NLS-1$ |
| sIconMap.put(ScreenDimensionQualifier.class, factory.getIcon("dimension")); //$NON-NLS-1$ |
| sIconMap.put(VersionQualifier.class, factory.getIcon("version")); //$NON-NLS-1$ |
| sIconMap.put(ScreenWidthQualifier.class, factory.getIcon("width")); //$NON-NLS-1$ |
| sIconMap.put(ScreenHeightQualifier.class, factory.getIcon("height")); //$NON-NLS-1$ |
| sIconMap.put(SmallestScreenWidthQualifier.class,factory.getIcon("swidth")); //$NON-NLS-1$ |
| } catch (Throwable t) { |
| AdtPlugin.log(t , null); |
| } |
| } |
| |
| /** |
| * Returns the icon for the qualifier. |
| */ |
| public static Image getIcon(Class<? extends ResourceQualifier> theClass) { |
| return sIconMap.get(theClass); |
| } |
| |
| /** |
| * Returns a {@link ResourceDeltaKind} from an {@link IResourceDelta} value. |
| * @param kind a {@link IResourceDelta} integer constant. |
| * @return a matching {@link ResourceDeltaKind} or null. |
| * |
| * @see IResourceDelta#ADDED |
| * @see IResourceDelta#REMOVED |
| * @see IResourceDelta#CHANGED |
| */ |
| public static ResourceDeltaKind getResourceDeltaKind(int kind) { |
| switch (kind) { |
| case IResourceDelta.ADDED: |
| return ResourceDeltaKind.ADDED; |
| case IResourceDelta.REMOVED: |
| return ResourceDeltaKind.REMOVED; |
| case IResourceDelta.CHANGED: |
| return ResourceDeltaKind.CHANGED; |
| } |
| |
| return null; |
| } |
| |
| /** |
| * Is this a resource that can be defined in any file within the "values" folder? |
| * <p> |
| * Some resource types can be defined <b>both</b> as a separate XML file as well |
| * as defined within a value XML file. This method will return true for these types |
| * as well. In other words, a ResourceType can return true for both |
| * {@link #isValueBasedResourceType} and {@link #isFileBasedResourceType}. |
| * |
| * @param type the resource type to check |
| * @return true if the given resource type can be represented as a value under the |
| * values/ folder |
| */ |
| public static boolean isValueBasedResourceType(ResourceType type) { |
| List<ResourceFolderType> folderTypes = FolderTypeRelationship.getRelatedFolders(type); |
| for (ResourceFolderType folderType : folderTypes) { |
| if (folderType == ResourceFolderType.VALUES) { |
| return true; |
| } |
| } |
| |
| return false; |
| } |
| |
| /** |
| * Is this a resource that is defined in a file named by the resource plus the XML |
| * extension? |
| * <p> |
| * Some resource types can be defined <b>both</b> as a separate XML file as well as |
| * defined within a value XML file along with other properties. This method will |
| * return true for these resource types as well. In other words, a ResourceType can |
| * return true for both {@link #isValueBasedResourceType} and |
| * {@link #isFileBasedResourceType}. |
| * |
| * @param type the resource type to check |
| * @return true if the given resource type is stored in a file named by the resource |
| */ |
| public static boolean isFileBasedResourceType(ResourceType type) { |
| List<ResourceFolderType> folderTypes = FolderTypeRelationship.getRelatedFolders(type); |
| for (ResourceFolderType folderType : folderTypes) { |
| if (folderType != ResourceFolderType.VALUES) { |
| |
| if (type == ResourceType.ID) { |
| // The folder types for ID is not only VALUES but also |
| // LAYOUT and MENU. However, unlike resources, they are only defined |
| // inline there so for the purposes of isFileBasedResourceType |
| // (where the intent is to figure out files that are uniquely identified |
| // by a resource's name) this method should return false anyway. |
| return false; |
| } |
| |
| return true; |
| } |
| } |
| |
| return false; |
| } |
| |
| /** |
| * Returns true if this class can create the given resource |
| * |
| * @param resource the resource to be created |
| * @return true if the {@link #createResource} method can create this resource |
| */ |
| public static boolean canCreateResource(String resource) { |
| // Cannot create framework resources |
| if (resource.startsWith(ANDROID_PREFIX)) { |
| return false; |
| } |
| |
| ResourceUrl parsed = ResourceUrl.parse(resource); |
| if (parsed != null) { |
| if (parsed.framework) { |
| return false; |
| } |
| ResourceType type = parsed.type; |
| String name = parsed.name; |
| |
| // Make sure the name is valid |
| ResourceNameValidator validator = |
| ResourceNameValidator.create(false, (Set<String>) null /* existing */, type); |
| if (validator.isValid(name) != null) { |
| return false; |
| } |
| |
| return canCreateResourceType(type); |
| } |
| |
| return false; |
| } |
| |
| /** |
| * Returns true if this class can create resources of the given resource |
| * type |
| * |
| * @param type the type of resource to be created |
| * @return true if the {@link #createResource} method can create resources |
| * of this type (provided the name parameter is also valid) |
| */ |
| public static boolean canCreateResourceType(ResourceType type) { |
| // We can create all value types |
| if (isValueBasedResourceType(type)) { |
| return true; |
| } |
| |
| // We can create -some- file-based types - those supported by the New XML wizard: |
| for (ResourceFolderType folderType : FolderTypeRelationship.getRelatedFolders(type)) { |
| if (NewXmlFileWizard.canCreateXmlFile(folderType)) { |
| return true; |
| } |
| } |
| |
| return false; |
| } |
| |
| /** Creates a file-based resource, like a layout. Used by {@link #createResource} */ |
| private static Pair<IFile,IRegion> createFileResource(IProject project, ResourceType type, |
| String name) { |
| |
| ResourceFolderType folderType = null; |
| for (ResourceFolderType f : FolderTypeRelationship.getRelatedFolders(type)) { |
| if (NewXmlFileWizard.canCreateXmlFile(f)) { |
| folderType = f; |
| break; |
| } |
| } |
| if (folderType == null) { |
| return null; |
| } |
| |
| // Find "dimens.xml" file in res/values/ (or corresponding name for other |
| // value types) |
| IPath projectPath = new Path(FD_RESOURCES + WS_SEP + folderType.getName() + WS_SEP |
| + name + '.' + EXT_XML); |
| IFile file = project.getFile(projectPath); |
| return NewXmlFileWizard.createXmlFile(project, file, folderType); |
| } |
| |
| /** |
| * Creates a resource of a given type, name and (if applicable) value |
| * |
| * @param project the project to contain the resource |
| * @param type the type of resource |
| * @param name the name of the resource |
| * @param value the value of the resource, if it is a value-type resource |
| * @return a pair of the file containing the resource and a region where the value |
| * appears |
| */ |
| public static Pair<IFile,IRegion> createResource(IProject project, ResourceType type, |
| String name, String value) { |
| if (!isValueBasedResourceType(type)) { |
| return createFileResource(project, type, name); |
| } |
| |
| // Find "dimens.xml" file in res/values/ (or corresponding name for other |
| // value types) |
| String typeName = type.getName(); |
| String fileName = typeName + 's'; |
| String projectPath = FD_RESOURCES + WS_SEP + FD_RES_VALUES + WS_SEP |
| + fileName + '.' + EXT_XML; |
| Object editRequester = project; |
| IResource member = project.findMember(projectPath); |
| String tagName = Hyperlinks.getTagName(type); |
| boolean createEmptyTag = type == ResourceType.ID; |
| if (member != null) { |
| if (member instanceof IFile) { |
| IFile file = (IFile) member; |
| // File exists: Must add item to the XML |
| IModelManager manager = StructuredModelManager.getModelManager(); |
| IStructuredModel model = null; |
| try { |
| model = manager.getExistingModelForEdit(file); |
| if (model == null) { |
| model = manager.getModelForEdit(file); |
| } |
| if (model instanceof IDOMModel) { |
| model.beginRecording(editRequester, String.format("Add %1$s", |
| type.getDisplayName())); |
| IDOMModel domModel = (IDOMModel) model; |
| Document document = domModel.getDocument(); |
| Element root = document.getDocumentElement(); |
| IStructuredDocument structuredDocument = model.getStructuredDocument(); |
| Node lastElement = null; |
| NodeList childNodes = root.getChildNodes(); |
| String indent = null; |
| for (int i = childNodes.getLength() - 1; i >= 0; i--) { |
| Node node = childNodes.item(i); |
| if (node.getNodeType() == Node.ELEMENT_NODE) { |
| lastElement = node; |
| indent = AndroidXmlEditor.getIndent(structuredDocument, node); |
| break; |
| } |
| } |
| if (indent == null || indent.length() == 0) { |
| indent = " "; //$NON-NLS-1$ |
| } |
| Node nextChild = lastElement != null ? lastElement.getNextSibling() : null; |
| Text indentNode = document.createTextNode('\n' + indent); |
| root.insertBefore(indentNode, nextChild); |
| Element element = document.createElement(tagName); |
| if (createEmptyTag) { |
| if (element instanceof ElementImpl) { |
| ElementImpl elementImpl = (ElementImpl) element; |
| elementImpl.setEmptyTag(true); |
| } |
| } |
| element.setAttribute(ATTR_NAME, name); |
| if (!tagName.equals(typeName)) { |
| element.setAttribute(ATTR_TYPE, typeName); |
| } |
| root.insertBefore(element, nextChild); |
| IRegion region = null; |
| |
| if (createEmptyTag) { |
| IndexedRegion domRegion = VisualRefactoring.getRegion(element); |
| int endOffset = domRegion.getEndOffset(); |
| region = new Region(endOffset, 0); |
| } else { |
| Node valueNode = document.createTextNode(value); |
| element.appendChild(valueNode); |
| |
| IndexedRegion domRegion = VisualRefactoring.getRegion(valueNode); |
| int startOffset = domRegion.getStartOffset(); |
| int length = domRegion.getLength(); |
| region = new Region(startOffset, length); |
| } |
| model.save(); |
| return Pair.of(file, region); |
| } |
| } catch (Exception e) { |
| AdtPlugin.log(e, "Cannot access XML value model"); |
| } finally { |
| if (model != null) { |
| model.endRecording(editRequester); |
| model.releaseFromEdit(); |
| } |
| } |
| } |
| |
| return null; |
| } else { |
| // No such file exists: just create it |
| String prolog = "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"; //$NON-NLS-1$ |
| StringBuilder sb = new StringBuilder(prolog); |
| |
| String root = TAG_RESOURCES; |
| sb.append('<').append(root).append('>').append('\n'); |
| sb.append(" "); //$NON-NLS-1$ |
| sb.append('<'); |
| sb.append(tagName); |
| sb.append(" name=\""); //$NON-NLS-1$ |
| sb.append(name); |
| sb.append('"'); |
| if (!tagName.equals(typeName)) { |
| sb.append(" type=\""); //$NON-NLS-1$ |
| sb.append(typeName); |
| sb.append('"'); |
| } |
| int start, end; |
| if (createEmptyTag) { |
| sb.append("/>"); //$NON-NLS-1$ |
| start = sb.length(); |
| end = sb.length(); |
| } else { |
| sb.append('>'); |
| start = sb.length(); |
| sb.append(value); |
| end = sb.length(); |
| sb.append('<').append('/'); |
| sb.append(tagName); |
| sb.append('>'); |
| } |
| sb.append('\n').append('<').append('/').append(root).append('>').append('\n'); |
| String result = sb.toString(); |
| // TODO: Pretty print string (wait until that CL is integrated) |
| String error = null; |
| try { |
| byte[] buf = result.getBytes("UTF8"); //$NON-NLS-1$ |
| InputStream stream = new ByteArrayInputStream(buf); |
| IFile file = project.getFile(new Path(projectPath)); |
| file.create(stream, true /*force*/, null /*progress*/); |
| IRegion region = new Region(start, end - start); |
| return Pair.of(file, region); |
| } catch (UnsupportedEncodingException e) { |
| error = e.getMessage(); |
| } catch (CoreException e) { |
| error = e.getMessage(); |
| } |
| |
| error = String.format("Failed to generate %1$s: %2$s", name, error); |
| AdtPlugin.displayError("New Android XML File", error); |
| } |
| return null; |
| } |
| |
| /** |
| * Returns the theme name to be shown for theme styles, e.g. for "@style/Theme" it |
| * returns "Theme" |
| * |
| * @param style a theme style string |
| * @return the user visible theme name |
| */ |
| public static String styleToTheme(String style) { |
| if (style.startsWith(STYLE_RESOURCE_PREFIX)) { |
| style = style.substring(STYLE_RESOURCE_PREFIX.length()); |
| } else if (style.startsWith(ANDROID_STYLE_RESOURCE_PREFIX)) { |
| style = style.substring(ANDROID_STYLE_RESOURCE_PREFIX.length()); |
| } else if (style.startsWith(PREFIX_RESOURCE_REF)) { |
| // @package:style/foo |
| int index = style.indexOf('/'); |
| if (index != -1) { |
| style = style.substring(index + 1); |
| } |
| } |
| return style; |
| } |
| |
| /** |
| * Returns true if the given style represents a project theme |
| * |
| * @param style a theme style string |
| * @return true if the style string represents a project theme, as opposed |
| * to a framework theme |
| */ |
| public static boolean isProjectStyle(String style) { |
| assert style.startsWith(STYLE_RESOURCE_PREFIX) |
| || style.startsWith(ANDROID_STYLE_RESOURCE_PREFIX) : style; |
| |
| return style.startsWith(STYLE_RESOURCE_PREFIX); |
| } |
| |
| /** |
| * Returns the layout resource name for the given layout file, e.g. for |
| * /res/layout/foo.xml returns foo. |
| * |
| * @param layoutFile the layout file whose name we want to look up |
| * @return the layout name |
| */ |
| public static String getLayoutName(IFile layoutFile) { |
| String layoutName = layoutFile.getName(); |
| int dotIndex = layoutName.indexOf('.'); |
| if (dotIndex != -1) { |
| layoutName = layoutName.substring(0, dotIndex); |
| } |
| return layoutName; |
| } |
| |
| /** |
| * Tries to resolve the given resource value to an actual RGB color. For state lists |
| * it will pick the simplest/fallback color. |
| * |
| * @param resources the resource resolver to use to follow color references |
| * @param color the color to resolve |
| * @return the corresponding {@link RGB} color, or null |
| */ |
| public static RGB resolveColor(ResourceResolver resources, ResourceValue color) { |
| color = resources.resolveResValue(color); |
| if (color == null) { |
| return null; |
| } |
| String value = color.getValue(); |
| |
| while (value != null) { |
| if (value.startsWith("#")) { //$NON-NLS-1$ |
| try { |
| int rgba = ImageUtils.getColor(value); |
| // Drop alpha channel |
| return ImageUtils.intToRgb(rgba); |
| } catch (NumberFormatException nfe) { |
| // Pass |
| } |
| return null; |
| } |
| if (value.startsWith(PREFIX_RESOURCE_REF)) { |
| boolean isFramework = color.isFramework(); |
| color = resources.findResValue(value, isFramework); |
| if (color != null) { |
| value = color.getValue(); |
| } else { |
| break; |
| } |
| } else { |
| File file = new File(value); |
| if (file.exists() && file.getName().endsWith(DOT_XML)) { |
| // Parse |
| DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); |
| BufferedInputStream bis = null; |
| try { |
| bis = new BufferedInputStream(new FileInputStream(file)); |
| InputSource is = new InputSource(bis); |
| factory.setNamespaceAware(true); |
| factory.setValidating(false); |
| DocumentBuilder builder = factory.newDocumentBuilder(); |
| Document document = builder.parse(is); |
| NodeList items = document.getElementsByTagName(TAG_ITEM); |
| |
| value = findColorValue(items); |
| continue; |
| } catch (Exception e) { |
| AdtPlugin.log(e, "Failed parsing color file %1$s", file.getName()); |
| } finally { |
| if (bis != null) { |
| try { |
| bis.close(); |
| } catch (IOException e) { |
| // Nothing useful can be done here |
| } |
| } |
| } |
| } |
| |
| return null; |
| } |
| } |
| |
| return null; |
| } |
| |
| /** |
| * Searches a color XML file for the color definition element that does not |
| * have an associated state and returns its color |
| */ |
| private static String findColorValue(NodeList items) { |
| for (int i = 0, n = items.getLength(); i < n; i++) { |
| // Find non-state color definition |
| Node item = items.item(i); |
| boolean hasState = false; |
| if (item.getNodeType() == Node.ELEMENT_NODE) { |
| Element element = (Element) item; |
| if (element.hasAttributeNS(ANDROID_URI, ATTR_COLOR)) { |
| NamedNodeMap attributes = element.getAttributes(); |
| for (int j = 0, m = attributes.getLength(); j < m; j++) { |
| Attr attribute = (Attr) attributes.item(j); |
| if (attribute.getLocalName().startsWith("state_")) { //$NON-NLS-1$ |
| hasState = true; |
| break; |
| } |
| } |
| |
| if (!hasState) { |
| return element.getAttributeNS(ANDROID_URI, ATTR_COLOR); |
| } |
| } |
| } |
| } |
| |
| return null; |
| } |
| } |