package com.ensoftcorp.open;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.StringReader;
import java.net.URL;
import java.net.URLConnection;
import java.util.HashSet;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Comment;
import org.jsoup.parser.Parser;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NodeList;
import org.xml.sax.InputSource;
import com.ensoftcorp.open.android.essentials.permissions.Permission;
/**
* Generates code to add undocumented Permission objects
*
* @author Ben Holland
*/
public class GenerateUndocumentedPermissions {
// pulls from latest raw version on master of Android github mirror
public static String ANDROID_SOURCE_MANIFEST = "https://raw.githubusercontent.com/android/platform_frameworks_base/master/core/res/AndroidManifest.xml";
public static void main(String[] args) throws Exception {
// parse manifest xml file using XML parser
String xml = getUrlContent(ANDROID_SOURCE_MANIFEST);
DocumentBuilderFactory dbFactory = DocumentBuilderFactory.newInstance();
DocumentBuilder dBuilder = dbFactory.newDocumentBuilder();
InputSource stringStream = new InputSource(new StringReader(xml));
Document androidManifest = dBuilder.parse(stringStream);
androidManifest.getDocumentElement().normalize();
// parse the file again using jsoup as a way to get the xml comment preceeding the xml elements which contains the permissions description
org.jsoup.nodes.Document jsoupDoc = Jsoup.parse(xml, "", Parser.xmlParser());
// for each permission entry record the permission
HashSet<String> qualifiedPermissionNames = new HashSet<String>();
// iterate over permissions and collect attributes
NodeList permissions = androidManifest.getElementsByTagName("permission");
for (int i = 0; i < permissions.getLength(); i++) {
Element permission = (Element) permissions.item(i);
String permissionName = permission.getAttribute("android:name");
qualifiedPermissionNames.add(permissionName);
}
for(String qualifiedPermissionName : qualifiedPermissionNames){
if(isUndocumentedPermission(qualifiedPermissionName)){
org.jsoup.nodes.Element permissionElement = jsoupDoc.select("permission[android:name=" + qualifiedPermissionName + "]").first();
org.jsoup.nodes.Comment comment = getPreceedingComment(permissionElement);
String commentString = (comment == null) ? "" : comment.getData().replaceAll("\\s+", " ").replace("@SystemApi ", "").replace("@hide ", "").replace("@link ", "").replace("<p> ", "").replace("</p> ", "").trim();
String description = commentString.equals("") ? "No description provided." : ("This permission is undocumented, its description has been scraped from the Android source.\n" + commentString);
System.out.println("public static final Permission " + getSimplePermissionName(qualifiedPermissionName) + " = new Permission(\"" + qualifiedPermissionName + "\", "
+ "-1, "
+ "-1, "
+ "\"" + description.replace("\n", "\\n").replace("\"", "\\\"") + "\", "
+ "\"Permission was found in " + ANDROID_SOURCE_MANIFEST + ", but not in " + GenerateDocumentedPermissionGroups.DOCUMENTED_PERMISSIONS_REFERENCE + ".\");");
}
}
System.out.println("-----------------");
for(String qualifiedPermissionName : qualifiedPermissionNames){
if(isUndocumentedPermission(qualifiedPermissionName)){
System.out.println("allUndocumentedPermissions.add(" + getSimplePermissionName(qualifiedPermissionName) + ");");
}
}
}
/**
* Gets a string containing the contents of the webpage at the given url
* @param url
* @return
* @throws Exception
*/
private static String getUrlContent(String url) throws Exception {
URL website = new URL(url);
URLConnection connection = website.openConnection();
BufferedReader in = new BufferedReader(new InputStreamReader(connection.getInputStream()));
StringBuilder response = new StringBuilder();
String inputLine;
while ((inputLine = in.readLine()) != null){
response.append(inputLine);
}
in.close();
return response.toString();
}
/**
* Searches for the preceeding sibling level comment before the given xml permission element
* @param permissionElement
* @return
*/
private static org.jsoup.nodes.Comment getPreceedingComment(org.jsoup.nodes.Element permissionElement){
org.jsoup.nodes.Node node = permissionElement;
while(true){
node = node.previousSibling();
if(node instanceof Comment){
return (org.jsoup.nodes.Comment) node;
} else if(node instanceof org.jsoup.nodes.TextNode){
// important, there is a trailing whitespace character after the comment that is considered as a node
continue;
} else if(node instanceof org.jsoup.nodes.Element){
return null;
}
}
}
/**
* Returns true if the permission is not already contained in the Permission.allDocumentedPermissions set
* @param qualifiedPermissionName
* @return
*/
private static boolean isUndocumentedPermission(String qualifiedPermissionName){
for(Permission permission : Permission.getAllDocumentedPermissions()){
if(permission.getQualifiedName().equals(qualifiedPermissionName)){
return false;
}
}
return true;
}
/**
* Helper method for getting a permissions simple name from a qualified permission name string
* @param qualifiedPermissionName
* @return
*/
private static String getSimplePermissionName(String qualifiedPermissionName){
int pos = qualifiedPermissionName.lastIndexOf('.');
return qualifiedPermissionName.substring(pos + 1);
}
}