blob: 03b87197ee13e14273603b28e08359e583800152 [file] [log] [blame]
/*
* Copyright 2012-present Facebook, Inc.
*
* Licensed under the Apache License, Version 2.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.apache.org/licenses/LICENSE-2.0
*
* 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.facebook.buck.util;
import com.google.common.base.Charsets;
import com.google.common.base.Throwables;
import com.google.common.collect.Lists;
import com.google.common.io.Files;
import org.w3c.dom.Document;
import org.w3c.dom.NodeList;
import org.xml.sax.InputSource;
import java.io.File;
import java.io.IOException;
import java.io.Reader;
import java.io.StringReader;
import java.util.Iterator;
import java.util.List;
import javax.xml.namespace.NamespaceContext;
import javax.xml.xpath.XPath;
import javax.xml.xpath.XPathConstants;
import javax.xml.xpath.XPathExpression;
import javax.xml.xpath.XPathExpressionException;
import javax.xml.xpath.XPathFactory;
public class DefaultAndroidManifestReader implements AndroidManifestReader{
/**
* XPath expression to retrieve the names of activities with an intent-filter that gets them
* to show up in the launcher.
*/
private static final String XPATH_LAUNCHER_ACTIVITIES =
"/manifest/application/*[self::activity or self::activity-alias]"+
" [intent-filter[action/@android:name='android.intent.action.MAIN' and " +
" category/@android:name='android.intent.category.LAUNCHER']]" +
" /@android:name";
/**
* XPath expression to get the package.
* For a manifest as {@code <manifest package="com.facebook.katana" />}, this results in
* {@code com.facebook.katana}.
*/
private static final String XPATH_PACKAGE = "/manifest/@package";
private final XPathExpression packageExpression;
private final XPathExpression launchableActivitiesExpression;
private final Document doc;
private DefaultAndroidManifestReader(InputSource src) throws IOException {
try {
// Parse the XML.
doc = XmlDomParser.parse(src, true);
// Compile the XPath expressions.
XPath xPath = XPathFactory.newInstance().newXPath();
xPath.setNamespaceContext(androidNamespaceContext);
launchableActivitiesExpression = xPath.compile(XPATH_LAUNCHER_ACTIVITIES);
packageExpression = xPath.compile(XPATH_PACKAGE);
} catch (XPathExpressionException e) {
throw Throwables.propagate(e);
}
}
@Override
public List<String> getLauncherActivities() {
try {
NodeList nodes;
nodes = (NodeList) launchableActivitiesExpression.evaluate(doc, XPathConstants.NODESET);
List<String> activities = Lists.newArrayList();
for (int i = 0; i < nodes.getLength(); i++) {
activities.add(nodes.item(i).getTextContent());
}
return activities;
} catch (XPathExpressionException e) {
throw Throwables.propagate(e);
}
}
@Override
public String getPackage() {
try {
return (String) packageExpression.evaluate(doc, XPathConstants.STRING);
} catch (XPathExpressionException e) {
throw Throwables.propagate(e);
}
}
/**
* This allows querying the AndroidManifest for e.g. attributes like android:name using XPath
*/
private static NamespaceContext androidNamespaceContext = new NamespaceContext() {
@Override
public Iterator<String> getPrefixes(String namespaceURI) {
throw new UnsupportedOperationException();
}
@Override
public String getPrefix(String namespaceURI) {
throw new UnsupportedOperationException();
}
@Override
public String getNamespaceURI(String prefix) {
if (prefix.equals("android")) {
return "http://schemas.android.com/apk/res/android";
} else {
throw new IllegalArgumentException();
}
}
};
/**
* Parses an XML given via its path and returns an {@link AndroidManifestReader} for it.
* @param path path to an AndroidManifest.xml file
* @return an {@code AndroidManifestReader} for {@code path}
* @throws IOException
*/
public static AndroidManifestReader forPath(String path) throws IOException {
File androidManifestXml = new File(path);
Reader reader = Files.newReader(androidManifestXml, Charsets.UTF_8);
return forReader(reader);
}
/**
* Parses an XML given as a string and returns an {@link AndroidManifestReader} for it.
* @param xmlString a string representation of an XML document
* @return an {@code AndroidManifestReader} for the XML document
* @throws IOException
*/
public static AndroidManifestReader forString(String xmlString) throws IOException {
return forReader(new StringReader(xmlString));
}
private static AndroidManifestReader forReader(Reader reader) throws IOException {
return new DefaultAndroidManifestReader(new InputSource(reader));
}
}