package jadx.core.xmlgen; import java.io.InputStream; import java.util.HashMap; import java.util.LinkedHashMap; import java.util.Map; import javax.xml.parsers.DocumentBuilder; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.w3c.dom.Document; import org.w3c.dom.NamedNodeMap; import org.w3c.dom.Node; import org.w3c.dom.NodeList; import jadx.core.utils.exceptions.JadxRuntimeException; public class ManifestAttributes { private static final Logger LOG = LoggerFactory.getLogger(ManifestAttributes.class); private static final String ATTR_XML = "/android/attrs.xml"; private static final String MANIFEST_ATTR_XML = "/android/attrs_manifest.xml"; private enum MAttrType { ENUM, FLAG } private static class MAttr { private final MAttrType type; private final Map<Long, String> values = new LinkedHashMap<>(); public MAttr(MAttrType type) { this.type = type; } public MAttrType getType() { return type; } public Map<Long, String> getValues() { return values; } @Override public String toString() { return "[" + type + ", " + values + ']'; } } private final Map<String, MAttr> attrMap = new HashMap<>(); private static ManifestAttributes instance; public static ManifestAttributes getInstance() { if (instance == null) { try { instance = new ManifestAttributes(); } catch (Exception e) { LOG.error("Failed to create ManifestAttributes", e); } } return instance; } private ManifestAttributes() { parseAll(); } private void parseAll() { parse(loadXML(ATTR_XML)); parse(loadXML(MANIFEST_ATTR_XML)); LOG.debug("Loaded android attributes count: {}", attrMap.size()); } private Document loadXML(String xml) { Document doc; try (InputStream xmlStream = ManifestAttributes.class.getResourceAsStream(xml)) { if (xmlStream == null) { throw new JadxRuntimeException(xml + " not found in classpath"); } DocumentBuilder dBuilder = XmlSecurity.getSecureDbf().newDocumentBuilder(); doc = dBuilder.parse(xmlStream); } catch (Exception e) { throw new JadxRuntimeException("Xml load error, file: " + xml, e); } return doc; } private void parse(Document doc) { NodeList nodeList = doc.getChildNodes(); for (int count = 0; count < nodeList.getLength(); count++) { Node node = nodeList.item(count); if (node.getNodeType() == Node.ELEMENT_NODE && node.hasChildNodes()) { parseAttrList(node.getChildNodes()); } } } private void parseAttrList(NodeList nodeList) { for (int count = 0; count < nodeList.getLength(); count++) { Node tempNode = nodeList.item(count); if (tempNode.getNodeType() == Node.ELEMENT_NODE && tempNode.hasAttributes() && tempNode.hasChildNodes()) { String name = null; NamedNodeMap nodeMap = tempNode.getAttributes(); for (int i = 0; i < nodeMap.getLength(); i++) { Node node = nodeMap.item(i); if (node.getNodeName().equals("name")) { name = node.getNodeValue(); break; } } if (name != null && tempNode.getNodeName().equals("attr")) { parseValues(name, tempNode.getChildNodes()); } else { parseAttrList(tempNode.getChildNodes()); } } } } private void parseValues(String name, NodeList nodeList) { MAttr attr = null; for (int count = 0; count < nodeList.getLength(); count++) { Node tempNode = nodeList.item(count); if (tempNode.getNodeType() == Node.ELEMENT_NODE && tempNode.hasAttributes()) { if (attr == null) { if (tempNode.getNodeName().equals("enum")) { attr = new MAttr(MAttrType.ENUM); } else if (tempNode.getNodeName().equals("flag")) { attr = new MAttr(MAttrType.FLAG); } if (attr == null) { return; } attrMap.put(name, attr); } NamedNodeMap attributes = tempNode.getAttributes(); Node nameNode = attributes.getNamedItem("name"); if (nameNode != null) { Node valueNode = attributes.getNamedItem("value"); if (valueNode != null) { try { long key; String nodeValue = valueNode.getNodeValue(); if (nodeValue.startsWith("0x")) { nodeValue = nodeValue.substring(2); key = Long.parseLong(nodeValue, 16); } else { key = Long.parseLong(nodeValue); } attr.getValues().put(key, nameNode.getNodeValue()); } catch (NumberFormatException e) { LOG.debug("Failed parse manifest number", e); } } } } } } public String decode(String attrName, long value) { MAttr attr = attrMap.get(attrName); if (attr == null) { return null; } if (attr.getType() == MAttrType.ENUM) { return attr.getValues().get(value); } else if (attr.getType() == MAttrType.FLAG) { StringBuilder sb = new StringBuilder(); for (Map.Entry<Long, String> entry : attr.getValues().entrySet()) { if (value == entry.getKey()) { sb = new StringBuilder(entry.getValue() + '|'); break; } else if ((value & entry.getKey()) == entry.getKey()) { sb.append(entry.getValue()).append('|'); } } if (sb.length() != 0) { return sb.deleteCharAt(sb.length() - 1).toString(); } } return null; } }