CTS API Surface Coverage Tool

Adds a few make rules to generate coverage reports that use
dexdeps to see what methods from the API are directly
called from the tests rather than using the usual approach
of using EMMA.

    make cts-test-coverage

This rule will generate coverage of the API from the test
cases in CtsTestCaseList's CTS_COVERAGE_TEST_CASE_LIST list.

    make cts-verifier-coverage

This rule will generate coverage by the CtsVerifier.

Change-Id: I28c8d825cff5b1c6b293b786e28a472059a7259a
diff --git a/Android.mk b/Android.mk
index 29e7cff..e3f9ba8 100644
--- a/Android.mk
+++ b/Android.mk
@@ -14,4 +14,6 @@
 # limitations under the License.
 #
 
+include cts/CtsTestCoverage.mk
+
 include $(call all-subdir-makefiles)
diff --git a/CtsTestCaseList.mk b/CtsTestCaseList.mk
index 46db63c..16b81c9 100644
--- a/CtsTestCaseList.mk
+++ b/CtsTestCaseList.mk
@@ -27,16 +27,14 @@
 	CtsTargetInstrumentationApp \
 	CtsUsePermissionDiffCert
 
-CTS_TEST_CASE_LIST := \
-	TestDeviceSetup \
-	CtsTestStubs \
+# These test cases will be analyzed by the CTS API coverage tools. 
+CTS_COVERAGE_TEST_CASE_LIST := \
 	CtsAccessibilityServiceTestCases \
 	CtsAccountManagerTestCases \
 	CtsAppTestCases \
 	CtsBluetoothTestCases \
 	CtsContentTestCases \
 	CtsDatabaseTestCases \
-	CtsDelegatingAccessibilityService \
 	CtsDpiTestCases \
 	CtsDpiTestCases2 \
 	CtsExampleTestCases \
@@ -53,19 +51,25 @@
 	CtsSaxTestCases \
 	CtsSpeechTestCases \
 	CtsTelephonyTestCases \
+	CtsTestStubs \
 	CtsTextTestCases \
 	CtsUtilTestCases \
 	CtsViewTestCases \
 	CtsWebkitTestCases \
 	CtsWidgetTestCases \
 	CtsNetTestCases \
-	SignatureTest \
 	CtsPerformanceTestCases \
 	CtsPerformance2TestCases \
 	CtsPerformance3TestCases \
 	CtsPerformance4TestCases \
-	CtsPerformance5TestCases \
+	CtsPerformance5TestCases
+
+CTS_TEST_CASE_LIST := \
+	TestDeviceSetup \
+	CtsDelegatingAccessibilityService \
+	SignatureTest \
 	ApiDemos \
 	ApiDemosReferenceTest \
 	$(CTS_APPS_LIST) \
+	$(CTS_COVERAGE_TEST_CASE_LIST) \
 	$(CTS_SECURITY_APPS_LIST)
diff --git a/CtsTestCoverage.mk b/CtsTestCoverage.mk
new file mode 100644
index 0000000..44a7b1a
--- /dev/null
+++ b/CtsTestCoverage.mk
@@ -0,0 +1,53 @@
+#
+# Copyright (C) 2010 The Android Open Source Project
+#
+# 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.
+#
+
+# Makefile for producing CTS coverage reports.
+# Run "make cts-test-coverage" in the $ANDROID_BUILD_TOP directory.
+
+include cts/CtsTestCaseList.mk
+
+CTS_API_COVERAGE_DEPENDENCIES := cts-api-coverage dexdeps $(ACP)
+
+.PHONY: cts-test-coverage
+cts-test-coverage: $(CTS_COVERAGE_TEST_CASE_LIST) $(CTS_API_COVERAGE_DEPENDENCIES)
+	$(call generate-coverage-report,"CTS Tests API Coverage Report",\
+			$(CTS_COVERAGE_TEST_CASE_LIST),xml,$(HOST_OUT)/cts/test-coverage,api-coverage.xml)
+
+.PHONY: cts-verifier-coverage
+cts-verifier-coverage: CtsVerifier $(CTS_API_COVERAGE_DEPENDENCIES)
+	$(call generate-coverage-report,"CTS Verifier API Coverage Report",\
+			CtsVerifier,xml,$(HOST_OUT)/cts/verifier-coverage,api-coverage.xml)
+
+# Arguments;
+#  1 - Name of the report printed out on the screen
+#  2 - Name of APK packages that will be scanned to generate the report
+#  3 - Format of the report
+#  4 - Output directory to put the report
+#  5 - Output file name of the report
+define generate-coverage-report
+	$(hide) rm -rf $(4)
+	$(hide) mkdir -p $(4)
+	$(hide) $(ACP) cts/tools/cts-api-coverage/res/* $(4)
+
+	$(foreach testcase,$(2),$(eval $(call add-testcase-apk,$(testcase))))
+	$(hide) cts-api-coverage -f $(3) -o $(4)/$(5) $(TEST_APKS)
+
+	@echo $(1): file://$(ANDROID_BUILD_TOP)/$(4)/$(5)
+endef
+
+define add-testcase-apk
+	TEST_APKS += $(call intermediates-dir-for,APPS,$(1))/package.apk
+endef
diff --git a/development/ide/eclipse/.classpath b/development/ide/eclipse/.classpath
index 59c1521..f3b4137 100644
--- a/development/ide/eclipse/.classpath
+++ b/development/ide/eclipse/.classpath
@@ -46,6 +46,7 @@
     <classpathentry kind="src" path="cts/tests/tests/webkit/src"/>
     <classpathentry kind="src" path="cts/tests/tests/widget/src"/>
     <classpathentry kind="src" path="cts/tools/annotation-helper/src"/>
+    <classpathentry kind="src" path="cts/tools/cts-api-coverage/src"/>
     <classpathentry kind="src" path="cts/tools/cts-reference-app-lib/src"/>
     <classpathentry kind="src" path="cts/tools/dasm/src"/>
     <classpathentry kind="src" path="cts/tools/device-setup/TestDeviceSetup/src"/>
diff --git a/tools/cts-api-coverage/Android.mk b/tools/cts-api-coverage/Android.mk
new file mode 100644
index 0000000..2382d61
--- /dev/null
+++ b/tools/cts-api-coverage/Android.mk
@@ -0,0 +1,43 @@
+# Copyright (C) 2010 The Android Open Source Project
+#
+# 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.
+
+LOCAL_PATH := $(call my-dir)
+
+# We use copy-file-to-new-target so that the installed
+# script file's timestamp is at least as new as the
+# .jar file it wraps.
+
+# the hat script
+# ============================================================
+include $(CLEAR_VARS)
+LOCAL_IS_HOST_MODULE := true
+LOCAL_MODULE_CLASS := EXECUTABLES
+LOCAL_MODULE := cts-api-coverage
+LOCAL_MODULE_TAGS := optional
+
+include $(BUILD_SYSTEM)/base_rules.mk
+
+$(LOCAL_BUILT_MODULE): $(HOST_OUT_JAVA_LIBRARIES)/$(LOCAL_MODULE)$(COMMON_JAVA_PACKAGE_SUFFIX)
+$(LOCAL_BUILT_MODULE): $(LOCAL_PATH)/etc/$(LOCAL_MODULE) | $(ACP)
+	@echo "Copy: $(PRIVATE_MODULE) ($@)"
+	$(copy-file-to-new-target)
+	$(hide) chmod 755 $@
+
+# the other stuff
+# ============================================================
+subdirs := $(addprefix $(LOCAL_PATH)/,$(addsuffix /Android.mk, \
+		src \
+	))
+
+include $(subdirs)
diff --git a/tools/cts-api-coverage/etc/cts-api-coverage b/tools/cts-api-coverage/etc/cts-api-coverage
new file mode 100644
index 0000000..fe7278a
--- /dev/null
+++ b/tools/cts-api-coverage/etc/cts-api-coverage
@@ -0,0 +1,46 @@
+#!/bin/bash
+#
+# Copyright (C) 2010, The Android Open Source Project
+#
+# 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.
+
+# Set up prog to be the path of this script, including following symlinks,
+# and set up progdir to be the fully-qualified pathname of its directory.
+prog="$0"
+while [ -h "${prog}" ]; do
+    newProg=`/bin/ls -ld "${prog}"`
+    newProg=`expr "${newProg}" : ".* -> \(.*\)$"`
+    if expr "x${newProg}" : 'x/' >/dev/null; then
+        prog="${newProg}"
+    else
+        progdir=`dirname "${prog}"`
+        prog="${progdir}/${newProg}"
+    fi
+done
+oldwd=`pwd`
+progdir=`dirname "${prog}"`
+cd "${progdir}"
+progdir=`pwd`
+prog="${progdir}"/`basename "${prog}"`
+cd "${oldwd}"
+
+libdir=`dirname $progdir`/framework
+
+javaOpts=""
+while expr "x$1" : 'x-J' >/dev/null; do
+    opt=`expr "$1" : '-J\(.*\)'`
+    javaOpts="${javaOpts} -${opt}"
+    shift
+done
+
+exec java $javaOpts -jar $libdir/cts-api-coverage.jar "$@"
diff --git a/tools/cts-api-coverage/res/api-coverage.css b/tools/cts-api-coverage/res/api-coverage.css
new file mode 100644
index 0000000..b956e16
--- /dev/null
+++ b/tools/cts-api-coverage/res/api-coverage.css
@@ -0,0 +1,54 @@
+/* Copyright (C) 2010 The Android Open Source Project
+
+    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.
+*/
+
+body {
+  background-color: #CCCCCC;
+  font-family: sans-serif;
+  margin: 10px;
+}
+
+.info {
+  margin-bottom: 10px;
+}
+
+.apks, .package, .class {
+  cursor: pointer;
+  text-decoration: underline;
+}
+
+.packageDetails {
+  padding-left: 20px;
+}
+
+.classDetails {
+  padding-left: 40px;
+}
+
+.method {
+  font-family: courier;
+  white-space: nowrap;
+}
+
+.red {
+  background-color: #FF0000;
+}
+
+.yellow {
+  background-color: #FFFF00;
+}
+
+.green {
+  background-color: #00FF00;
+}
diff --git a/tools/cts-api-coverage/res/api-coverage.xsl b/tools/cts-api-coverage/res/api-coverage.xsl
new file mode 100644
index 0000000..5d45014
--- /dev/null
+++ b/tools/cts-api-coverage/res/api-coverage.xsl
@@ -0,0 +1,120 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * 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.
+ -->
+
+<!DOCTYPE xsl:stylesheet [ <!ENTITY nbsp "&#160;"> ]>
+<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
+    <xsl:output method="html" version="1.0" encoding="UTF-8" indent="yes" />
+    <xsl:template match="/">
+        <html>
+            <head>
+                <script type="text/javascript">
+                    function toggleVisibility(id) {
+                        element = document.getElementById(id); 
+                        if (element.style.display == "none") {
+                            element.style.display = ""; 
+                        } else { 
+                            element.style.display = "none";
+                        } 
+                    }
+                </script>
+                <style type="text/css">
+                    @import "api-coverage.css";
+                </style>
+            </head>
+            <body>
+                <h1>CTS API Coverage</h1>
+                <div class="info">
+                    Generated: <xsl:value-of select="api-coverage/@generatedTime" />
+                </div>
+                <div class="apks" onclick="toggleVisibility('sourceApks')">
+                    Source APKs (<xsl:value-of select="count(api-coverage/debug/sources/apk)" />)
+                </div>
+                <div id="sourceApks" style="display: none">
+                    <ul>
+                        <xsl:for-each select="api-coverage/debug/sources/apk">
+                            <li><xsl:value-of select="@path" /></li>
+                        </xsl:for-each>
+                    </ul>
+                </div>
+                <ul>
+                    <xsl:for-each select="api-coverage/api/package">
+                        <xsl:call-template name="packageOrClassListItem">
+                            <xsl:with-param name="bulletClass" select="'package'" />
+                        </xsl:call-template>
+                        <div class="packageDetails" id="{@name}" style="display: none">
+                            <ul>
+                                <xsl:for-each select="class">
+                                    <xsl:call-template name="packageOrClassListItem">
+                                        <xsl:with-param name="bulletClass" select="'class'" />
+                                    </xsl:call-template>
+                                    <div class="classDetails" id="{@name}" style="display: none">
+                                        <xsl:for-each select="constructor">
+                                            <xsl:call-template name="methodListItem" />
+                                        </xsl:for-each>
+                                        <xsl:for-each select="method">
+                                            <xsl:call-template name="methodListItem" />
+                                        </xsl:for-each>
+                                    </div>
+                                </xsl:for-each>
+                            </ul>
+                        </div>
+                    </xsl:for-each>
+                </ul>
+            </body>
+        </html>
+    </xsl:template>
+    
+    <xsl:template name="packageOrClassListItem">
+        <xsl:param name="bulletClass" />
+        
+        <xsl:variable name="colorClass">
+            <xsl:choose>
+                <xsl:when test="@coveragePercentage &lt;= 50">red</xsl:when>
+                <xsl:when test="@coveragePercentage &lt;= 80">yellow</xsl:when>
+                <xsl:otherwise>green</xsl:otherwise>
+            </xsl:choose>
+        </xsl:variable>
+        
+        <li class="{$bulletClass}" onclick="toggleVisibility('{@name}')">
+            <span class="{$colorClass}">
+                <b><xsl:value-of select="@name" /></b>
+                &nbsp;<xsl:value-of select="@coveragePercentage" />%
+                &nbsp;(<xsl:value-of select="@numCovered" />/<xsl:value-of select="@numTotal" />)
+            </span>
+        </li>   
+    </xsl:template>
+  
+  <xsl:template name="methodListItem">
+    <span class="method">
+      <xsl:choose>
+        <xsl:when test="@covered = 'true'">[X]</xsl:when>
+        <xsl:otherwise>[ ]</xsl:otherwise>
+      </xsl:choose>
+      <xsl:if test="@returnType != ''">&nbsp;<xsl:value-of select="@returnType" /></xsl:if>
+      <b>&nbsp;<xsl:value-of select="@name" /></b><xsl:call-template name="formatParameters" />
+    </span>
+    <br />
+  </xsl:template>
+  
+  <xsl:template name="formatParameters">(<xsl:for-each select="parameter">
+      <xsl:value-of select="@type" />
+      <xsl:if test="not(position() = last())">,&nbsp;</xsl:if>
+    </xsl:for-each>)
+  </xsl:template>
+  
+</xsl:stylesheet>
+
diff --git a/tools/cts-api-coverage/src/Android.mk b/tools/cts-api-coverage/src/Android.mk
new file mode 100644
index 0000000..67ce8b6
--- /dev/null
+++ b/tools/cts-api-coverage/src/Android.mk
@@ -0,0 +1,28 @@
+# Copyright (C) 2010 The Android Open Source Project
+#
+# 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.
+
+LOCAL_PATH := $(call my-dir)
+
+
+# cts-api-coverage java library
+# ============================================================
+include $(CLEAR_VARS)
+
+LOCAL_SRC_FILES := $(call all-subdir-java-files)
+LOCAL_JAR_MANIFEST := MANIFEST.mf
+
+LOCAL_MODULE := cts-api-coverage
+LOCAL_MODULE_TAGS := optional
+
+include $(BUILD_HOST_JAVA_LIBRARY)
diff --git a/tools/cts-api-coverage/src/MANIFEST.mf b/tools/cts-api-coverage/src/MANIFEST.mf
new file mode 100644
index 0000000..b6aa831
--- /dev/null
+++ b/tools/cts-api-coverage/src/MANIFEST.mf
@@ -0,0 +1,2 @@
+Manifest-Version: 1.0
+Main-Class: com.android.cts.apicoverage.CtsApiCoverage
diff --git a/tools/cts-api-coverage/src/com/android/cts/apicoverage/ApiClass.java b/tools/cts-api-coverage/src/com/android/cts/apicoverage/ApiClass.java
new file mode 100644
index 0000000..de2e3eb
--- /dev/null
+++ b/tools/cts-api-coverage/src/com/android/cts/apicoverage/ApiClass.java
@@ -0,0 +1,103 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * 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.android.cts.apicoverage;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+
+/** Representation of a class in the API with constructors and methods. */
+class ApiClass implements Comparable<ApiClass>, HasCoverage {
+
+    private final String mName;
+
+    private final List<ApiConstructor> mApiConstructors = new ArrayList<ApiConstructor>();
+
+    private final List<ApiMethod> mApiMethods = new ArrayList<ApiMethod>();
+
+    ApiClass(String name) {
+        this.mName = name;
+    }
+
+    public int compareTo(ApiClass another) {
+        return mName.compareTo(another.mName);
+    }
+
+    public String getName() {
+        return mName;
+    }
+
+    public void addConstructor(ApiConstructor constructor) {
+        mApiConstructors.add(constructor);
+    }
+
+    public ApiConstructor getConstructor(List<String> parameterTypes) {
+        for (ApiConstructor constructor : mApiConstructors) {
+            if (parameterTypes.equals(constructor.getParameterTypes())) {
+                return constructor;
+            }
+        }
+        return null;
+    }
+
+    public Collection<ApiConstructor> getConstructors() {
+        return Collections.unmodifiableList(mApiConstructors);
+    }
+
+    public void addMethod(ApiMethod method) {
+        mApiMethods.add(method);
+    }
+
+    public ApiMethod getMethod(String name, List<String> parameterTypes, String returnType) {
+        for (ApiMethod method : mApiMethods) {
+            if (name.equals(method.getName())
+                    && parameterTypes.equals(method.getParameterTypes())
+                    && returnType.equals(method.getReturnType())) {
+                return method;
+            }
+        }
+        return null;
+    }
+
+    public Collection<ApiMethod> getMethods() {
+        return Collections.unmodifiableList(mApiMethods);
+    }
+
+    public int getNumCoveredMethods() {
+        int numCovered = 0;
+        for (ApiConstructor constructor : mApiConstructors) {
+            if (constructor.isCovered()) {
+                numCovered++;
+            }
+        }
+        for (ApiMethod method : mApiMethods) {
+            if (method.isCovered()) {
+                numCovered++;
+            }
+        }
+        return numCovered;
+    }
+
+    public int getTotalMethods() {
+        return mApiConstructors.size() + mApiMethods.size();
+    }
+
+    public float getCoveragePercentage() {
+        return (float) getNumCoveredMethods() / getTotalMethods() * 100;
+    }
+}
\ No newline at end of file
diff --git a/tools/cts-api-coverage/src/com/android/cts/apicoverage/ApiConstructor.java b/tools/cts-api-coverage/src/com/android/cts/apicoverage/ApiConstructor.java
new file mode 100644
index 0000000..b38bd34
--- /dev/null
+++ b/tools/cts-api-coverage/src/com/android/cts/apicoverage/ApiConstructor.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * 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.android.cts.apicoverage;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+/** Representation of a constructor in the API with parameters (arguments). */
+class ApiConstructor implements Comparable<ApiConstructor> {
+
+    private final String mName;
+
+    private final List<String> mParameterTypes;
+
+    private boolean mIsCovered;
+
+    ApiConstructor(String name, List<String> parameterTypes) {
+        this.mName = name;
+        this.mParameterTypes = new ArrayList<String>(parameterTypes);
+    }
+
+    public int compareTo(ApiConstructor another) {
+        return mParameterTypes.size() - another.mParameterTypes.size();
+    }
+
+    public String getName() {
+        return mName;
+    }
+
+    public List<String> getParameterTypes() {
+        return Collections.unmodifiableList(mParameterTypes);
+    }
+
+    public boolean isCovered() {
+        return mIsCovered;
+    }
+
+    public void setCovered(boolean covered) {
+        mIsCovered = covered;
+    }
+}
\ No newline at end of file
diff --git a/tools/cts-api-coverage/src/com/android/cts/apicoverage/ApiCoverage.java b/tools/cts-api-coverage/src/com/android/cts/apicoverage/ApiCoverage.java
new file mode 100644
index 0000000..dc40062
--- /dev/null
+++ b/tools/cts-api-coverage/src/com/android/cts/apicoverage/ApiCoverage.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * 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.android.cts.apicoverage;
+
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+
+/** Representation of the entire API containing packages. */
+class ApiCoverage {
+
+    private final Map<String, ApiPackage> mPackages = new HashMap<String, ApiPackage>();
+
+    public void addPackage(ApiPackage pkg) {
+        mPackages.put(pkg.getName(), pkg);
+    }
+
+    public ApiPackage getPackage(String name) {
+        return mPackages.get(name);
+    }
+
+    public Collection<ApiPackage> getPackages() {
+        return Collections.unmodifiableCollection(mPackages.values());
+    }
+}
diff --git a/tools/cts-api-coverage/src/com/android/cts/apicoverage/ApiMethod.java b/tools/cts-api-coverage/src/com/android/cts/apicoverage/ApiMethod.java
new file mode 100644
index 0000000..eb67b8e
--- /dev/null
+++ b/tools/cts-api-coverage/src/com/android/cts/apicoverage/ApiMethod.java
@@ -0,0 +1,63 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * 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.android.cts.apicoverage;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+/** Representation of a method in the API with parameters (arguments) and a return value. */
+class ApiMethod implements Comparable<ApiMethod> {
+
+    private final String mName;
+
+    private final List<String> mParameterTypes;
+
+    private final String mReturnType;
+
+    private boolean mIsCovered;
+
+    ApiMethod(String name, List<String> parameterTypes, String returnType) {
+        this.mName = name;
+        this.mParameterTypes = new ArrayList<String>(parameterTypes);
+        this.mReturnType = returnType;
+    }
+
+    public int compareTo(ApiMethod another) {
+        return mName.compareTo(another.mName);
+    }
+
+    public String getName() {
+        return mName;
+    }
+
+    public List<String> getParameterTypes() {
+        return Collections.unmodifiableList(mParameterTypes);
+    }
+
+    public String getReturnType() {
+        return mReturnType;
+    }
+
+    public boolean isCovered() {
+        return mIsCovered;
+    }
+
+    public void setCovered(boolean covered) {
+        mIsCovered = covered;
+    }
+}
\ No newline at end of file
diff --git a/tools/cts-api-coverage/src/com/android/cts/apicoverage/ApiPackage.java b/tools/cts-api-coverage/src/com/android/cts/apicoverage/ApiPackage.java
new file mode 100644
index 0000000..f0ca889
--- /dev/null
+++ b/tools/cts-api-coverage/src/com/android/cts/apicoverage/ApiPackage.java
@@ -0,0 +1,70 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * 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.android.cts.apicoverage;
+
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+
+/** Representation of a package in the API containing classes. */
+class ApiPackage implements HasCoverage {
+
+    private final String mName;
+
+    private final Map<String, ApiClass> mApiClassMap = new HashMap<String, ApiClass>();
+
+    ApiPackage(String name) {
+        this.mName = name;
+    }
+
+    public String getName() {
+        return mName;
+    }
+
+    public void addClass(ApiClass apiClass) {
+        mApiClassMap.put(apiClass.getName(), apiClass);
+    }
+
+    public ApiClass getClass(String name) {
+        return mApiClassMap.get(name);
+    }
+
+    public Collection<ApiClass> getClasses() {
+        return Collections.unmodifiableCollection(mApiClassMap.values());
+    }
+
+    public int getNumCoveredMethods() {
+        int covered = 0;
+        for (ApiClass apiClass : mApiClassMap.values()) {
+            covered += apiClass.getNumCoveredMethods();
+        }
+        return covered;
+    }
+
+    public int getTotalMethods() {
+        int total = 0;
+        for (ApiClass apiClass : mApiClassMap.values()) {
+            total += apiClass.getTotalMethods();
+        }
+        return total;
+    }
+
+    public float getCoveragePercentage() {
+        return (float) getNumCoveredMethods() / getTotalMethods() * 100;
+    }
+}
\ No newline at end of file
diff --git a/tools/cts-api-coverage/src/com/android/cts/apicoverage/CtsApiCoverage.java b/tools/cts-api-coverage/src/com/android/cts/apicoverage/CtsApiCoverage.java
new file mode 100644
index 0000000..112ba39
--- /dev/null
+++ b/tools/cts-api-coverage/src/com/android/cts/apicoverage/CtsApiCoverage.java
@@ -0,0 +1,176 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * 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.android.cts.apicoverage;
+
+import org.xml.sax.InputSource;
+import org.xml.sax.SAXException;
+import org.xml.sax.XMLReader;
+import org.xml.sax.helpers.XMLReaderFactory;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.FileReader;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Tool that generates a report of what Android framework methods are being called from a given
+ * set of APKS. See the {@link #printUsage()} method for more details.
+ */
+public class CtsApiCoverage {
+
+    private static final int FORMAT_TXT = 0;
+
+    private static final int FORMAT_XML = 1;
+
+    private static void printUsage() {
+        System.out.println("Usage: cts-api-coverage [OPTION]... [APK]...");
+        System.out.println();
+        System.out.println("Generates a report about what Android framework methods are called ");
+        System.out.println("from the given APKs.");
+        System.out.println();
+        System.out.println("Use the Makefiles rules in CtsTestCoverage.mk to generate the report ");
+        System.out.println("rather than executing this directly. If you still want to run this ");
+        System.out.println("directly, then this must be used from the $ANDROID_BUILD_TOP ");
+        System.out.println("directory and dexdeps must be built via \"make dexdeps\".");
+        System.out.println();
+        System.out.println("Options:");
+        System.out.println("  -o FILE         output file or standard out if not given");
+        System.out.println("  -f [txt|xml]    format of output either text or xml");
+        System.out.println();
+        System.exit(1);
+    }
+
+    public static void main(String[] args) throws Exception {
+        List<File> testApks = new ArrayList<File>();
+        File outputFile = null;
+        int format = FORMAT_TXT;
+
+        for (int i = 0; i < args.length; i++) {
+            if (args[i].startsWith("-")) {
+                if ("-o".equals(args[i])) {
+                    if (i + 1 < args.length) {
+                        outputFile = new File(args[++i]);;
+                    } else {
+                        printUsage();
+                    }
+                } else if ("-f".equals(args[i])) {
+                    if (i + 1 < args.length) {
+                        String formatArg = args[++i];
+                        if ("xml".equalsIgnoreCase(formatArg)) {
+                            format = FORMAT_XML;
+                        } else if ("txt".equalsIgnoreCase(formatArg)) {
+                            format = FORMAT_TXT;
+                        } else {
+                            printUsage();
+                        }
+                    } else {
+                        printUsage();
+                    }
+                } else {
+                    printUsage();
+                }
+            } else {
+                testApks.add(new File(args[i]));
+            }
+        }
+
+        /*
+         * 1. Create an ApiCoverage object that is a tree of Java objects representing the API
+         *    in current.xml. The object will have no information about the coverage for each
+         *    constructor or method yet.
+         *
+         * 2. For each provided APK, scan it using dexdeps, parse the output of dexdeps, and
+         *    call methods on the ApiCoverage object to cumulatively add coverage stats.
+         *
+         * 3. Output a report based on the coverage stats in the ApiCoverage object.
+         */
+
+        ApiCoverage apiCoverage = getEmptyApiCoverage();
+        for (File testApk : testApks) {
+            addApiCoverage(apiCoverage, testApk);
+        }
+        outputCoverageReport(apiCoverage, testApks, outputFile, format);
+    }
+
+    /**
+     * Creates an object representing the API that will be used later to collect coverage
+     * statistics as we iterate over the test APKs.
+     *
+     * @return an {@link ApiCoverage} object representing the API in current.xml without any
+     *     coverage statistics yet
+     */
+    private static ApiCoverage getEmptyApiCoverage()
+            throws SAXException, IOException {
+        XMLReader xmlReader = XMLReaderFactory.createXMLReader();
+        CurrentXmlHandler currentXmlHandler = new CurrentXmlHandler();
+        xmlReader.setContentHandler(currentXmlHandler);
+
+        File currentXml = new File("frameworks/base/api/current.xml");
+        FileReader fileReader = null;
+        try {
+            fileReader = new FileReader(currentXml);
+            xmlReader.parse(new InputSource(fileReader));
+        } finally {
+            if (fileReader != null) {
+                fileReader.close();
+            }
+        }
+
+        return currentXmlHandler.getApi();
+    }
+
+    /**
+     * Adds coverage information gleamed from running dexdeps on the APK to the
+     * {@link ApiCoverage} object.
+     *
+     * @param apiCoverage object to which the coverage statistics will be added to
+     * @param testApk containing the tests that will be scanned by dexdeps
+     */
+    private static void addApiCoverage(ApiCoverage apiCoverage, File testApk)
+            throws SAXException, IOException {
+        XMLReader xmlReader = XMLReaderFactory.createXMLReader();
+        DexDepsXmlHandler dexDepsXmlHandler = new DexDepsXmlHandler(apiCoverage);
+        xmlReader.setContentHandler(dexDepsXmlHandler);
+
+        Process process = new ProcessBuilder("dexdeps", "--format=xml", testApk.getPath()).start();
+        xmlReader.parse(new InputSource(process.getInputStream()));
+    }
+
+    private static void outputCoverageReport(ApiCoverage apiCoverage, List<File> testApks,
+            File outputFile, int format) throws IOException {
+        OutputStream out = outputFile != null
+                ? new FileOutputStream(outputFile)
+                : System.out;
+
+        try {
+            switch (format) {
+                case FORMAT_TXT:
+                    TextReport.printTextReport(apiCoverage, out);
+                    break;
+
+                case FORMAT_XML:
+                    XmlReport.printXmlReport(testApks, apiCoverage, out);
+                    break;
+            }
+        } finally {
+            out.close();
+        }
+    }
+}
diff --git a/tools/cts-api-coverage/src/com/android/cts/apicoverage/CurrentXmlHandler.java b/tools/cts-api-coverage/src/com/android/cts/apicoverage/CurrentXmlHandler.java
new file mode 100644
index 0000000..41ee0dc
--- /dev/null
+++ b/tools/cts-api-coverage/src/com/android/cts/apicoverage/CurrentXmlHandler.java
@@ -0,0 +1,100 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * 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.android.cts.apicoverage;
+
+import org.xml.sax.Attributes;
+import org.xml.sax.SAXException;
+import org.xml.sax.helpers.DefaultHandler;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * {@link DefaultHandler} that builds an empty {@link ApiCoverage} object from scanning current.xml.
+ */
+class CurrentXmlHandler extends DefaultHandler {
+
+    private String mCurrentPackageName;
+
+    private String mCurrentClassName;
+
+    private String mCurrentMethodName;
+
+    private String mCurrentMethodReturnType;
+
+    private List<String> mCurrentParameterTypes = new ArrayList<String>();
+
+    private ApiCoverage mApiCoverage = new ApiCoverage();
+
+    public ApiCoverage getApi() {
+        return mApiCoverage;
+    }
+
+    @Override
+    public void startElement(String uri, String localName, String name, Attributes attributes)
+            throws SAXException {
+        super.startElement(uri, localName, name, attributes);
+        if ("package".equalsIgnoreCase(localName)) {
+            mCurrentPackageName = CurrentXmlHandler.getValue(attributes, "name");
+
+            ApiPackage apiPackage = new ApiPackage(mCurrentPackageName);
+            mApiCoverage.addPackage(apiPackage);
+
+        } else if ("class".equalsIgnoreCase(localName)
+                || "interface".equalsIgnoreCase(localName)) {
+            mCurrentClassName = CurrentXmlHandler.getValue(attributes, "name");
+
+            ApiClass apiClass = new ApiClass(mCurrentClassName);
+            ApiPackage apiPackage = mApiCoverage.getPackage(mCurrentPackageName);
+            apiPackage.addClass(apiClass);
+
+        } else if ("constructor".equalsIgnoreCase(localName)) {
+            mCurrentParameterTypes.clear();
+        }  else if ("method".equalsIgnoreCase(localName)) {
+            mCurrentMethodName = CurrentXmlHandler.getValue(attributes, "name");
+            mCurrentMethodReturnType = CurrentXmlHandler.getValue(attributes, "return");
+            mCurrentParameterTypes.clear();
+        } else if ("parameter".equalsIgnoreCase(localName)) {
+            mCurrentParameterTypes.add(CurrentXmlHandler.getValue(attributes, "type"));
+        }
+    }
+
+    @Override
+    public void endElement(String uri, String localName, String name) throws SAXException {
+        super.endElement(uri, localName, name);
+        if ("constructor".equalsIgnoreCase(localName)) {
+            ApiConstructor apiConstructor = new ApiConstructor(mCurrentClassName,
+                    mCurrentParameterTypes);
+            ApiPackage apiPackage = mApiCoverage.getPackage(mCurrentPackageName);
+            ApiClass apiClass = apiPackage.getClass(mCurrentClassName);
+            apiClass.addConstructor(apiConstructor);
+        }  else if ("method".equalsIgnoreCase(localName)) {
+            ApiMethod apiMethod = new ApiMethod(mCurrentMethodName, mCurrentParameterTypes,
+                    mCurrentMethodReturnType);
+            ApiPackage apiPackage = mApiCoverage.getPackage(mCurrentPackageName);
+            ApiClass apiClass = apiPackage.getClass(mCurrentClassName);
+            apiClass.addMethod(apiMethod);
+        }
+    }
+
+    static String getValue(Attributes attributes, String key) {
+        // Strip away generics <...> and make inner classes always use a "." rather than "$".
+        return attributes.getValue(key)
+                .replaceAll("<.+>", "")
+                .replace("$", ".");
+    }
+}
\ No newline at end of file
diff --git a/tools/cts-api-coverage/src/com/android/cts/apicoverage/DexDepsXmlHandler.java b/tools/cts-api-coverage/src/com/android/cts/apicoverage/DexDepsXmlHandler.java
new file mode 100644
index 0000000..0a90bdd
--- /dev/null
+++ b/tools/cts-api-coverage/src/com/android/cts/apicoverage/DexDepsXmlHandler.java
@@ -0,0 +1,96 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * 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.android.cts.apicoverage;
+
+import org.xml.sax.Attributes;
+import org.xml.sax.SAXException;
+import org.xml.sax.helpers.DefaultHandler;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * {@link DefaultHander} that parses the output of dexdeps and adds the coverage information to
+ * an {@link ApiCoverage} object.
+ */
+class DexDepsXmlHandler extends DefaultHandler {
+
+    private final ApiCoverage mPackageMap;
+
+    private String mCurrentPackageName;
+
+    private String mCurrentClassName;
+
+    private String mCurrentMethodName;
+
+    private String mCurrentMethodReturnType;
+
+    private List<String> mCurrentParameterTypes = new ArrayList<String>();
+
+    DexDepsXmlHandler(ApiCoverage packageMap) {
+        this.mPackageMap = packageMap;
+    }
+
+    @Override
+    public void startElement(String uri, String localName, String name, Attributes attributes)
+            throws SAXException {
+        super.startElement(uri, localName, name, attributes);
+        if ("package".equalsIgnoreCase(localName)) {
+            mCurrentPackageName = CurrentXmlHandler.getValue(attributes, "name");
+        } else if ("class".equalsIgnoreCase(localName)
+                || "interface".equalsIgnoreCase(localName)) {
+            mCurrentClassName = CurrentXmlHandler.getValue(attributes, "name");
+        } else if ("constructor".equalsIgnoreCase(localName)) {
+            mCurrentParameterTypes.clear();
+        }  else if ("method".equalsIgnoreCase(localName)) {
+            mCurrentMethodName = CurrentXmlHandler.getValue(attributes, "name");
+            mCurrentMethodReturnType = CurrentXmlHandler.getValue(attributes, "return");
+            mCurrentParameterTypes.clear();
+        } else if ("parameter".equalsIgnoreCase(localName)) {
+            mCurrentParameterTypes.add(CurrentXmlHandler.getValue(attributes, "type"));
+        }
+    }
+
+    @Override
+    public void endElement(String uri, String localName, String name) throws SAXException {
+        super.endElement(uri, localName, name);
+        if ("constructor".equalsIgnoreCase(localName)) {
+            ApiPackage apiPackage = mPackageMap.getPackage(mCurrentPackageName);
+            if (apiPackage != null) {
+                ApiClass apiClass = apiPackage.getClass(mCurrentClassName);
+                if (apiClass != null) {
+                    ApiConstructor apiConstructor = apiClass.getConstructor(mCurrentParameterTypes);
+                    if (apiConstructor != null) {
+                        apiConstructor.setCovered(true);
+                    }
+                }
+            }
+        }  else if ("method".equalsIgnoreCase(localName)) {
+            ApiPackage apiPackage = mPackageMap.getPackage(mCurrentPackageName);
+            if (apiPackage != null) {
+                ApiClass apiClass = apiPackage.getClass(mCurrentClassName);
+                if (apiClass != null) {
+                    ApiMethod apiMethod = apiClass.getMethod(mCurrentMethodName,
+                            mCurrentParameterTypes, mCurrentMethodReturnType);
+                    if (apiMethod != null) {
+                        apiMethod.setCovered(true);
+                    }
+                }
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/tools/cts-api-coverage/src/com/android/cts/apicoverage/HasCoverage.java b/tools/cts-api-coverage/src/com/android/cts/apicoverage/HasCoverage.java
new file mode 100644
index 0000000..3b369bb
--- /dev/null
+++ b/tools/cts-api-coverage/src/com/android/cts/apicoverage/HasCoverage.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * 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.android.cts.apicoverage;
+
+import java.util.Comparator;
+
+interface HasCoverage {
+    float getCoveragePercentage();
+    String getName();
+}
+
+class CoverageComparator implements Comparator<HasCoverage> {
+    public int compare(HasCoverage entity, HasCoverage otherEntity) {
+        int diff = Math.round(entity.getCoveragePercentage())
+                - Math.round(otherEntity.getCoveragePercentage());
+        return diff != 0 ? diff : entity.getName().compareTo(otherEntity.getName());
+    }
+}
diff --git a/tools/cts-api-coverage/src/com/android/cts/apicoverage/TextReport.java b/tools/cts-api-coverage/src/com/android/cts/apicoverage/TextReport.java
new file mode 100644
index 0000000..ebcefe5
--- /dev/null
+++ b/tools/cts-api-coverage/src/com/android/cts/apicoverage/TextReport.java
@@ -0,0 +1,118 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * 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.android.cts.apicoverage;
+
+import java.io.OutputStream;
+import java.io.PrintStream;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * Class that outputs a text report of {@link ApiCoverage}.
+ */
+class TextReport {
+
+    public static void printTextReport(ApiCoverage api, OutputStream outputStream) {
+        PrintStream out = new PrintStream(outputStream);
+
+        CoverageComparator comparator = new CoverageComparator();
+        List<ApiPackage> packages = new ArrayList<ApiPackage>(api.getPackages());
+        Collections.sort(packages, comparator);
+
+        for (ApiPackage apiPackage : packages) {
+            if (apiPackage.getName().startsWith("android")
+                    && apiPackage.getTotalMethods() > 0) {
+                printPackage(apiPackage, out);
+            }
+        }
+
+        out.println();
+        out.println();
+
+        for (ApiPackage apiPackage : packages) {
+            if (apiPackage.getName().startsWith("android")) {
+                printPackage(apiPackage, out);
+
+                List<ApiClass> classes = new ArrayList<ApiClass>(apiPackage.getClasses());
+                Collections.sort(classes, comparator);
+                for (ApiClass apiClass : classes) {
+                    if (apiClass.getTotalMethods() > 0) {
+                        printClass(apiClass, out);
+
+                        List<ApiConstructor> constructors =
+                                new ArrayList<ApiConstructor>(apiClass.getConstructors());
+                        Collections.sort(constructors);
+                        for (ApiConstructor constructor : constructors) {
+                            printConstructor(constructor, out);
+                        }
+
+                        List<ApiMethod> methods = new ArrayList<ApiMethod>(apiClass.getMethods());
+                        Collections.sort(methods);
+                        for (ApiMethod method : methods) {
+                            printMethod(method, out);
+                        }
+                    }
+                }
+            }
+        }
+    }
+
+    private static void printPackage(ApiPackage apiPackage, PrintStream out) {
+        out.println(apiPackage.getName() + " "
+                + Math.round(apiPackage.getCoveragePercentage()) + "% ("
+                + apiPackage.getNumCoveredMethods() + "/" + apiPackage.getTotalMethods() + ")");
+    }
+
+    private static void printClass(ApiClass apiClass, PrintStream out) {
+        out.println("  " + apiClass.getName() + " "
+                + Math.round(apiClass.getCoveragePercentage()) + "% ("
+                + apiClass.getNumCoveredMethods() + "/" + apiClass.getTotalMethods() + ") ");
+    }
+
+    private static void printConstructor(ApiConstructor constructor, PrintStream out) {
+        StringBuilder builder = new StringBuilder("    [")
+                .append(constructor.isCovered() ? "X" : " ")
+                .append("] ").append(constructor.getName()).append("(");
+
+        List<String> parameterTypes = constructor.getParameterTypes();
+        int numParameterTypes = parameterTypes.size();
+        for (int i = 0; i < numParameterTypes; i++) {
+            builder.append(parameterTypes.get(i));
+            if (i + 1 < numParameterTypes) {
+                builder.append(", ");
+            }
+        }
+        out.println(builder.append(")"));
+    }
+
+    private static void printMethod(ApiMethod method, PrintStream out) {
+        StringBuilder builder = new StringBuilder("    [")
+                .append(method.isCovered() ? "X" : " ")
+                .append("] ").append(method.getReturnType()).append(" ")
+                .append(method.getName()).append("(");
+        List<String> parameterTypes = method.getParameterTypes();
+        int numParameterTypes = parameterTypes.size();
+        for (int i = 0; i < numParameterTypes; i++) {
+            builder.append(parameterTypes.get(i));
+            if (i + 1 < numParameterTypes) {
+                builder.append(", ");
+            }
+        }
+        out.println(builder.append(")"));
+    }
+}
diff --git a/tools/cts-api-coverage/src/com/android/cts/apicoverage/XmlReport.java b/tools/cts-api-coverage/src/com/android/cts/apicoverage/XmlReport.java
new file mode 100644
index 0000000..68acf06
--- /dev/null
+++ b/tools/cts-api-coverage/src/com/android/cts/apicoverage/XmlReport.java
@@ -0,0 +1,111 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * 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.android.cts.apicoverage;
+
+import java.io.File;
+import java.io.OutputStream;
+import java.io.PrintStream;
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Date;
+import java.util.List;
+
+/**
+ * Class that outputs an XML report of the {@link ApiCoverage} collected. It can be viewed in
+ * a browser when used with the api-coverage.css and api-coverage.xsl files.
+ */
+class XmlReport {
+
+    public static void printXmlReport(List<File> testApks, ApiCoverage apiCoverage,
+            OutputStream outputStream) {
+        PrintStream out = new PrintStream(outputStream);
+        out.println("<?xml version=\"1.0\" encoding=\"UTF-8\"?>");
+        out.println("<?xml-stylesheet type=\"text/xsl\"  href=\"api-coverage.xsl\"?>");
+
+        SimpleDateFormat format = new SimpleDateFormat("EEE, MMM d, yyyy h:mm a z");
+        String date = format.format(new Date(System.currentTimeMillis()));
+        out.println("<api-coverage generatedTime=\"" + date + "\">");
+
+        out.println("<debug>");
+        out.println("<sources>");
+        for (File testApk : testApks) {
+            out.println("<apk path=\"" + testApk.getPath() + "\" />");
+        }
+        out.println("</sources>");
+        out.println("</debug>");
+
+        out.println("<api>");
+
+        CoverageComparator comparator = new CoverageComparator();
+        List<ApiPackage> packages = new ArrayList<ApiPackage>(apiCoverage.getPackages());
+        Collections.sort(packages, comparator);
+        for (ApiPackage pkg : packages) {
+            if (pkg.getName().startsWith("android")
+                    && pkg.getTotalMethods() > 0) {
+                out.println("<package name=\"" + pkg.getName()
+                        + "\" numCovered=\"" + pkg.getNumCoveredMethods()
+                        + "\" numTotal=\"" + pkg.getTotalMethods()
+                        + "\" coveragePercentage=\""
+                            + Math.round(pkg.getCoveragePercentage())
+                        + "\">");
+
+                List<ApiClass> classes = new ArrayList<ApiClass>(pkg.getClasses());
+                Collections.sort(classes, comparator);
+
+                for (ApiClass apiClass : classes) {
+                    if (apiClass.getTotalMethods() > 0) {
+                        out.println("<class name=\"" + apiClass.getName()
+                                + "\" numCovered=\"" + apiClass.getNumCoveredMethods()
+                                + "\" numTotal=\"" + apiClass.getTotalMethods()
+                                + "\" coveragePercentage=\""
+                                    + Math.round(apiClass.getCoveragePercentage())
+                                + "\">");
+
+                        for (ApiConstructor constructor : apiClass.getConstructors()) {
+                            out.println("<constructor name=\"" + constructor.getName()
+                                    + "\" covered=\"" + constructor.isCovered() + "\">");
+
+                            for (String parameterType : constructor.getParameterTypes()) {
+                                out.println("<parameter type=\"" + parameterType + "\" />");
+                            }
+
+                            out.println("</constructor>");
+                        }
+
+                        for (ApiMethod method : apiClass.getMethods()) {
+                            out.println("<method name=\"" + method.getName()
+                                    + "\" returnType=\"" + method.getReturnType()
+                                    + "\" covered=\"" + method.isCovered() + "\">");
+
+                            for (String parameterType : method.getParameterTypes()) {
+                                out.println("<parameter type=\"" + parameterType + "\" />");
+                            }
+
+                            out.println("</method>");
+                        }
+                        out.println("</class>");
+                    }
+                }
+                out.println("</package>");
+            }
+        }
+
+        out.println("</api>");
+        out.println("</api-coverage>");
+    }
+}