AI 143120: Added simple XML output.  Shuffled stuff around.

Automated import of CL 143120
diff --git a/tools/dexdeps/README.txt b/tools/dexdeps/README.txt
new file mode 100644
index 0000000..14d65b0
--- /dev/null
+++ b/tools/dexdeps/README.txt
@@ -0,0 +1,28 @@
+dexdeps -- DEX external dependency dump
+
+
+This tool dumps a list of fields and methods that a DEX file uses but does
+not define.  When combined with a list of public APIs, it can be used to
+determine whether an APK is accessing fields and calling methods that it
+shouldn't be.  It may also be useful in determining whether an application
+requires a certain minimum API level to execute.
+
+Basic usage:
+
+  dexdeps [options] <file.{dex,apk,jar}>
+
+For zip archives (including .jar and .apk), dexdeps will look for a
+"classes.dex" entry.
+
+Supported options are:
+
+  --format={brief,xml}
+
+    Specifies the output format.
+
+    "brief" produces one line of output for each field and method.  Field
+    and argument types are shown as descriptor strings.
+
+    "xml" produces a larger output file, readable with an XML browser.  Types
+    are shown in a more human-readable form (e.g. "[I" becomes "int[]").
+
diff --git a/tools/dexdeps/src/com/android/dexdeps/DexData.java b/tools/dexdeps/src/com/android/dexdeps/DexData.java
index 0bfd202..fa79d60 100644
--- a/tools/dexdeps/src/com/android/dexdeps/DexData.java
+++ b/tools/dexdeps/src/com/android/dexdeps/DexData.java
@@ -310,107 +310,34 @@
      */
 
     /**
-     * Converts a single-character primitive type into its human-readable
-     * equivalent.
-     */
-    private String primitiveTypeLabel(char typeChar) {
-        /* primitive type; substitute human-readable name in */
-        switch (typeChar) {
-            case 'B':   return "byte";
-            case 'C':   return "char";
-            case 'D':   return "double";
-            case 'F':   return "float";
-            case 'I':   return "int";
-            case 'J':   return "long";
-            case 'S':   return "short";
-            case 'V':   return "void";
-            case 'Z':   return "boolean";
-            default:
-                /* huh? */
-                System.err.println("Unexpected class char " + typeChar);
-                assert false;
-                return "UNKNOWN";
-        }
-    }
-
-    /**
-     * Converts a descriptor to dotted form.  For example,
-     * "Ljava/lang/String;" becomes "java.lang.String", and "[I" becomes
-     * "int[].
-     */
-    private String descriptorToDot(String descr) {
-        int targetLen = descr.length();
-        int offset = 0;
-        int arrayDepth = 0;
-
-        /* strip leading [s; will be added to end */
-        while (targetLen > 1 && descr.charAt(offset) == '[') {
-            offset++;
-            targetLen--;
-        }
-        arrayDepth = offset;
-
-        if (targetLen == 1) {
-            descr = primitiveTypeLabel(descr.charAt(offset));
-            offset = 0;
-            targetLen = descr.length();
-        } else {
-            /* account for leading 'L' and trailing ';' */
-            if (targetLen >= 2 && descr.charAt(offset) == 'L' &&
-                descr.charAt(offset+targetLen-1) == ';')
-            {
-                targetLen -= 2;     /* two fewer chars to copy */
-                offset++;           /* skip the 'L' */
-            }
-        }
-
-        char[] buf = new char[targetLen + arrayDepth * 2];
-
-        /* copy class name over */
-        int i;
-        for (i = 0; i < targetLen; i++) {
-            char ch = descr.charAt(offset + i);
-            buf[i] = (ch == '/') ? '.' : ch;
-        }
-
-        /* add the appopriate number of brackets for arrays */
-        while (arrayDepth-- > 0) {
-            buf[i++] = '[';
-            buf[i++] = ']';
-        }
-        assert i == buf.length;
-
-        return new String(buf);
-    }
-
-    /**
-     * Returns the dot-form class name, given an index into the type_ids
-     * table.
+     * Returns the class name, given an index into the type_ids table.
      */
     private String classNameFromTypeIndex(int idx) {
-        String descriptor = mStrings[mTypeIds[idx].descriptorIdx];
-        return descriptorToDot(descriptor);
+        return mStrings[mTypeIds[idx].descriptorIdx];
     }
 
     /**
-     * Returns the method prototype descriptor, given an index into the
-     * proto_ids table.
+     * Returns an array of method argument type strings, given an index
+     * into the proto_ids table.
      */
-    private String protoStringFromProtoIndex(int idx) {
-        StringBuilder builder = new StringBuilder();
+    private String[] argArrayFromProtoIndex(int idx) {
         ProtoIdItem protoId = mProtoIds[idx];
+        String[] result = new String[protoId.types.length];
 
-        builder.append("(");
         for (int i = 0; i < protoId.types.length; i++) {
-            String elem = mStrings[mTypeIds[protoId.types[i]].descriptorIdx];
-            builder.append(elem);
+            result[i] = mStrings[mTypeIds[protoId.types[i]].descriptorIdx];
         }
 
-        builder.append(")");
-        String ret = mStrings[mTypeIds[protoId.returnTypeIdx].descriptorIdx];
-        builder.append(ret);
+        return result;
+    }
 
-        return builder.toString();
+    /**
+     * Returns a string representing the method's return type, given an
+     * index into the proto_ids table.
+     */
+    private String returnTypeFromProtoIndex(int idx) {
+        ProtoIdItem protoId = mProtoIds[idx];
+        return mStrings[mTypeIds[protoId.returnTypeIdx].descriptorIdx];
     }
 
     /**
@@ -465,7 +392,8 @@
                 MethodIdItem methodId = mMethodIds[i];
                 methodRefs[count++] =
                     new MethodRef(classNameFromTypeIndex(methodId.classIdx),
-                                 protoStringFromProtoIndex(methodId.protoIdx),
+                                 argArrayFromProtoIndex(methodId.protoIdx),
+                                 returnTypeFromProtoIndex(methodId.protoIdx),
                                  mStrings[methodId.nameIdx]);
             }
         }
@@ -475,6 +403,7 @@
         return methodRefs;
     }
 
+
     /*
      * =======================================================================
      *      Basic I/O functions
diff --git a/tools/dexdeps/src/com/android/dexdeps/FieldRef.java b/tools/dexdeps/src/com/android/dexdeps/FieldRef.java
index 96e0ef0..2726a7a 100644
--- a/tools/dexdeps/src/com/android/dexdeps/FieldRef.java
+++ b/tools/dexdeps/src/com/android/dexdeps/FieldRef.java
@@ -28,14 +28,23 @@
         mFieldName = fieldName;
     }
 
+    /**
+     * Gets the name of the field's declaring class.
+     */
     public String getDeclClassName() {
         return mDeclClass;
     }
 
+    /**
+     * Gets the type name.  Examples: "Ljava/lang/String;", "[I".
+     */
     public String getTypeName() {
         return mFieldType;
     }
 
+    /**
+     * Gets the field name.
+     */
     public String getName() {
         return mFieldName;
     }
diff --git a/tools/dexdeps/src/com/android/dexdeps/Main.java b/tools/dexdeps/src/com/android/dexdeps/Main.java
index 4369845..7eba3aa 100644
--- a/tools/dexdeps/src/com/android/dexdeps/Main.java
+++ b/tools/dexdeps/src/com/android/dexdeps/Main.java
@@ -55,7 +55,8 @@
             usage();
             System.exit(2);
         } catch (IOException ioe) {
-            /* a message was already reported, just bail quietly */
+            if (ioe.getMessage() != null)
+                System.err.println("Failed: " + ioe);
             System.exit(1);
         } catch (DexDataException dde) {
             /* a message was already reported, just bail quietly */
@@ -127,7 +128,7 @@
          * to ensure it doesn't hang around if we fail.
          */
         File tempFile = File.createTempFile("dexdeps", ".dex");
-        System.out.println("+++ using temp " + tempFile);
+        //System.out.println("+++ using temp " + tempFile);
         RandomAccessFile raf = new RandomAccessFile(tempFile, "rw");
         tempFile.delete();
 
@@ -173,7 +174,7 @@
                     System.err.println("Unknown format '" + mOutputFormat +"'");
                     throw new UsageException();
                 }
-                System.out.println("Using format " + mOutputFormat);
+                //System.out.println("+++ using format " + mOutputFormat);
             } else {
                 System.err.println("Unknown option '" + arg + "'");
                 throw new UsageException();
diff --git a/tools/dexdeps/src/com/android/dexdeps/MethodRef.java b/tools/dexdeps/src/com/android/dexdeps/MethodRef.java
index 1ca6852..96522eb 100644
--- a/tools/dexdeps/src/com/android/dexdeps/MethodRef.java
+++ b/tools/dexdeps/src/com/android/dexdeps/MethodRef.java
@@ -17,14 +17,17 @@
 package com.android.dexdeps;
 
 public class MethodRef {
-    private String mDeclClass, mDescriptor, mMethodName;
+    private String mDeclClass, mReturnType, mMethodName;
+    private String[] mArgTypes;
 
     /**
      * Initializes a new field reference.
      */
-    public MethodRef(String declClass, String descriptor, String methodName) {
+    public MethodRef(String declClass, String[] argTypes, String returnType,
+            String methodName) {
         mDeclClass = declClass;
-        mDescriptor = descriptor;
+        mArgTypes = argTypes;
+        mReturnType = returnType;
         mMethodName = methodName;
     }
 
@@ -39,7 +42,7 @@
      * Gets the method's descriptor.
      */
     public String getDescriptor() {
-        return mDescriptor;
+        return descriptorFromProtoArray(mArgTypes, mReturnType);
     }
 
     /**
@@ -50,20 +53,36 @@
     }
 
     /**
-     * Gets the method arguments as an array of type strings.
+     * Gets an array of method argument types.
      */
-    public String[] getArguments() {
-        // TODO
-        return null;
+    public String[] getArgumentTypeNames() {
+        return mArgTypes;
     }
 
     /**
-     * Gets the method's return type.
+     * Gets the method's return type.  Examples: "Ljava/lang/String;", "[I".
      */
-    public String getReturnType() {
-        // TODO
-        return null;
+    public String getReturnTypeName() {
+        return mReturnType;
+    }
+
+    /**
+     * Returns the method descriptor, given the argument and return type
+     * prototype strings.
+     */
+    private static String descriptorFromProtoArray(String[] protos,
+            String returnType) {
+        StringBuilder builder = new StringBuilder();
+
+        builder.append("(");
+        for (int i = 0; i < protos.length; i++) {
+            builder.append(protos[i]);
+        }
+
+        builder.append(")");
+        builder.append(returnType);
+
+        return builder.toString();
     }
 }
 
-
diff --git a/tools/dexdeps/src/com/android/dexdeps/Output.java b/tools/dexdeps/src/com/android/dexdeps/Output.java
index 0c350b6..0039b33 100644
--- a/tools/dexdeps/src/com/android/dexdeps/Output.java
+++ b/tools/dexdeps/src/com/android/dexdeps/Output.java
@@ -21,40 +21,243 @@
  */
 public class Output {
     public static void generate(DexData dexData, String format) {
-        FieldRef[] externFieldRefs;
-        MethodRef[] externMethodRefs;
-
-        externFieldRefs = dexData.getExternalFieldReferences();
-        externMethodRefs = dexData.getExternalMethodReferences();
-
         if (format.equals("brief")) {
-            printFieldRefs(externFieldRefs);
-            printMethodRefs(externMethodRefs);
+            printBrief(dexData);
         } else if (format.equals("xml")) {
-            // ...
+            printXml(dexData);
         } else {
             /* should've been trapped in arg handler */
             throw new RuntimeException("unknown output format");
         }
     }
 
+    /**
+     * Prints the data in a simple human-readable format.
+     */
+    static void printBrief(DexData dexData) {
+        FieldRef[] externFieldRefs = dexData.getExternalFieldReferences();
+        MethodRef[] externMethodRefs = dexData.getExternalMethodReferences();
+
+        printFieldRefs(externFieldRefs);
+        printMethodRefs(externMethodRefs);
+    }
+
+    /**
+     * Prints the list of fields in a simple human-readable format.
+     */
     static void printFieldRefs(FieldRef[] fields) {
         System.out.println("Fields:");
         for (int i = 0; i < fields.length; i++) {
             FieldRef ref = fields[i];
 
-            System.out.println(ref.getDeclClassName() + "." +
+            System.out.println(descriptorToDot(ref.getDeclClassName()) + "." +
                 ref.getName() + " : " + ref.getTypeName());
         }
     }
 
+    /**
+     * Prints the list of methods in a simple human-readable format.
+     */
     static void printMethodRefs(MethodRef[] methods) {
         System.out.println("Methods:");
         for (int i = 0; i < methods.length; i++) {
             MethodRef ref = methods[i];
 
-            System.out.println(ref.getDeclClassName() + "." +
-                ref.getName() + " : " + ref.getDescriptor());
+            System.out.println(descriptorToDot(ref.getDeclClassName()) +
+                "." + ref.getName() + " : " + ref.getDescriptor());
+        }
+    }
+
+
+    /**
+     * Prints the output in XML format.
+     *
+     * We shouldn't need to XML-escape the field/method info.
+     */
+    static void printXml(DexData dexData) {
+        final String IN0 = "";
+        final String IN1 = "  ";
+        final String IN2 = "    ";
+        final String IN3 = "      ";
+        FieldRef[] externFieldRefs = dexData.getExternalFieldReferences();
+        MethodRef[] externMethodRefs = dexData.getExternalMethodReferences();
+        String prevClass = null;
+
+        System.out.println(IN0 + "<external>");
+
+        /* print fields */
+        for (int i = 0; i < externFieldRefs.length; i++) {
+            FieldRef fref = externFieldRefs[i];
+            String declClassName = fref.getDeclClassName();
+
+            if (prevClass != null && !prevClass.equals(declClassName)) {
+                System.out.println(IN1 + "</class>");
+            }
+            if (!declClassName.equals(prevClass)) {
+                String className = classNameOnly(declClassName);
+                String packageName = packageNameOnly(declClassName);
+                System.out.println(IN1 + "<class package=\"" + packageName +
+                    "\" name=\"" + className + "\">");
+                prevClass = declClassName;
+            }
+
+            System.out.println(IN2 + "<field name=\"" + fref.getName() +
+                "\" type=\"" + descriptorToDot(fref.getTypeName()) + "\"/>");
+        }
+
+        /* print methods */
+        for (int i = 0; i < externMethodRefs.length; i++) {
+            MethodRef mref = externMethodRefs[i];
+            String declClassName = mref.getDeclClassName();
+            boolean constructor;
+
+            if (prevClass != null && !prevClass.equals(declClassName)) {
+                System.out.println(IN1 + "</class>");
+            }
+            if (!declClassName.equals(prevClass)) {
+                String className = classNameOnly(declClassName);
+                String packageName = packageNameOnly(declClassName);
+                System.out.println(IN1 + "<class package=\"" + packageName +
+                    "\" name=\"" + className + "\">");
+                prevClass = declClassName;
+            }
+
+            constructor = mref.getName().equals("<init>");
+            if (constructor) {
+                /* use class name instead of method name */
+                System.out.println(IN2 + "<constructor name=\"" +
+                    classNameOnly(declClassName) + "\" return=\"" +
+                    descriptorToDot(mref.getReturnTypeName()) + "\">");
+            } else {
+                System.out.println(IN2 + "<method name=\"" + mref.getName() +
+                    "\" return=\"" + descriptorToDot(mref.getReturnTypeName()) +
+                    "\">");
+            }
+            String[] args = mref.getArgumentTypeNames();
+            for (int j = 0; j < args.length; j++) {
+                System.out.println(IN3 + "<parameter type=\"" +
+                    descriptorToDot(args[j]) + "\"/>");
+            }
+            if (constructor) {
+                System.out.println(IN2 + "</constructor>");
+            } else {
+                System.out.println(IN2 + "</method>");
+            }
+        }
+
+        if (prevClass != null)
+            System.out.println(IN1 + "</class>");
+        System.out.println(IN0 + "</external>");
+    }
+
+
+    /*
+     * =======================================================================
+     *      Utility functions
+     * =======================================================================
+     */
+
+    /**
+     * Converts a single-character primitive type into its human-readable
+     * equivalent.
+     */
+    static String primitiveTypeLabel(char typeChar) {
+        /* primitive type; substitute human-readable name in */
+        switch (typeChar) {
+            case 'B':   return "byte";
+            case 'C':   return "char";
+            case 'D':   return "double";
+            case 'F':   return "float";
+            case 'I':   return "int";
+            case 'J':   return "long";
+            case 'S':   return "short";
+            case 'V':   return "void";
+            case 'Z':   return "boolean";
+            default:
+                /* huh? */
+                System.err.println("Unexpected class char " + typeChar);
+                assert false;
+                return "UNKNOWN";
+        }
+    }
+
+    /**
+     * Converts a type descriptor to human-readable "dotted" form.  For
+     * example, "Ljava/lang/String;" becomes "java.lang.String", and
+     * "[I" becomes "int[].
+     */
+    static String descriptorToDot(String descr) {
+        int targetLen = descr.length();
+        int offset = 0;
+        int arrayDepth = 0;
+
+        /* strip leading [s; will be added to end */
+        while (targetLen > 1 && descr.charAt(offset) == '[') {
+            offset++;
+            targetLen--;
+        }
+        arrayDepth = offset;
+
+        if (targetLen == 1) {
+            descr = primitiveTypeLabel(descr.charAt(offset));
+            offset = 0;
+            targetLen = descr.length();
+        } else {
+            /* account for leading 'L' and trailing ';' */
+            if (targetLen >= 2 && descr.charAt(offset) == 'L' &&
+                descr.charAt(offset+targetLen-1) == ';')
+            {
+                targetLen -= 2;     /* two fewer chars to copy */
+                offset++;           /* skip the 'L' */
+            }
+        }
+
+        char[] buf = new char[targetLen + arrayDepth * 2];
+
+        /* copy class name over */
+        int i;
+        for (i = 0; i < targetLen; i++) {
+            char ch = descr.charAt(offset + i);
+            buf[i] = (ch == '/') ? '.' : ch;
+        }
+
+        /* add the appopriate number of brackets for arrays */
+        while (arrayDepth-- > 0) {
+            buf[i++] = '[';
+            buf[i++] = ']';
+        }
+        assert i == buf.length;
+
+        return new String(buf);
+    }
+
+    /**
+     * Extracts the class name from a type descriptor.
+     */
+    static String classNameOnly(String typeName) {
+        String dotted = descriptorToDot(typeName);
+
+        int start = dotted.lastIndexOf(".");
+        if (start < 0) {
+            return dotted;
+        } else {
+            return dotted.substring(start+1);
+        }
+    }
+
+    /**
+     * Extracts the package name from a type descriptor, and returns it in
+     * dotted form.
+     */
+    static String packageNameOnly(String typeName) {
+        String dotted = descriptorToDot(typeName);
+
+        int end = dotted.lastIndexOf(".");
+        if (end < 0) {
+            /* lives in default package */
+            return "";
+        } else {
+            return dotted.substring(0, end);
         }
     }
 }