Merge "Fix MultiAppStartupTest" into froyo
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/tests/tests/hardware/src/android/hardware/cts/SensorManagerTest.java b/tests/tests/hardware/src/android/hardware/cts/SensorManagerTest.java
deleted file mode 100644
index 136d077..0000000
--- a/tests/tests/hardware/src/android/hardware/cts/SensorManagerTest.java
+++ /dev/null
@@ -1,307 +0,0 @@
-/*
- * Copyright (C) 2008 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 android.hardware.cts;
-
-import dalvik.annotation.BrokenTest;
-import dalvik.annotation.TestLevel;
-import dalvik.annotation.TestTargetClass;
-import dalvik.annotation.TestTargetNew;
-import dalvik.annotation.TestTargets;
-import dalvik.annotation.ToBeFixed;
-
-import android.content.Context;
-import android.hardware.Sensor;
-import android.hardware.SensorEvent;
-import android.hardware.SensorEventListener;
-import android.hardware.SensorListener;
-import android.hardware.SensorManager;
-import android.test.AndroidTestCase;
-import android.util.Log;
-
-import java.util.List;
-
-@SuppressWarnings("deprecation")
-@TestTargetClass(SensorManager.class)
-public class SensorManagerTest extends AndroidTestCase {
-
-    private static final String TAG = "SensorManagerTest";
-    private static final float[] GRAVITY = { 0, 0, SensorManager.GRAVITY_EARTH };
-    private static final double TWO_PI = 2 * Math.PI;
-    private static final int MATRIX = 9;
-    private static final int VECTOR = 3;
-    private static final int MAX_COUNT = 10;
-    private static final long TIME_OUT = 10000;
-    private static Object mSync;
-    private static int mCounter;
-    private static boolean mHasNotified;
-    private static float[] mR;
-    private static float[] mI;
-    private static float[] mGeomagnetic;
-    private static SensorManager mSensorManager;
-
-    private static int[] mSensorListenerOnChangedSensor;
-    private static float[][] mSensorListenerOnChangedValus;
-    private static SensorEvent[] mSensorEvents;
-
-    @SuppressWarnings("serial")
-    private static class SensorTestTimeOutException extends Exception {
-        public SensorTestTimeOutException() {
-            super("Time out while waiting for sensor events. "
-                    + "Please move the device to generate events.");
-        }
-    }
-
-    private static void waitForSensorEvent() throws InterruptedException,
-            SensorTestTimeOutException {
-        mCounter = 0;
-        mHasNotified = false;
-        synchronized (mSync) {
-            if (!mHasNotified) {
-                mSync.wait(TIME_OUT);
-            }
-            if (!mHasNotified) {
-                throw new SensorTestTimeOutException();
-            }
-        }
-    }
-
-    private static void addSyncCount() {
-        synchronized (mSync) {
-            if (++mCounter >= MAX_COUNT) {
-                mCounter = MAX_COUNT -1;
-                mHasNotified = true;
-                mSync.notify();
-            }
-        }
-    }
-
-    @Override
-    protected void setUp() throws Exception {
-        super.setUp();
-        mSensorManager = (SensorManager) getContext().getSystemService(Context.SENSOR_SERVICE);
-        mR = new float[MATRIX];
-        mI = new float[MATRIX];
-        mGeomagnetic = new float[VECTOR];
-        mSync = new Object();
-        mSensorListenerOnChangedSensor = new int[MAX_COUNT];
-        mSensorListenerOnChangedValus = new float[MAX_COUNT][];
-        mSensorEvents = new SensorEvent[MAX_COUNT];
-    }
-
-    @TestTargets({
-        @TestTargetNew(
-            level = TestLevel.COMPLETE,
-            method = "getSensors",
-            args = {}
-        ),
-        @TestTargetNew(
-            level = TestLevel.COMPLETE,
-            method = "registerListener",
-            args = {SensorListener.class, int.class}
-        ),
-        @TestTargetNew(
-            level = TestLevel.COMPLETE,
-            method = "registerListener",
-            args = {SensorListener.class, int.class, int.class}
-        ),
-        @TestTargetNew(
-            level = TestLevel.COMPLETE,
-            method = "unregisterListener",
-            args = {SensorListener.class}
-        ),
-        @TestTargetNew(
-            level = TestLevel.COMPLETE,
-            method = "unregisterListener",
-            args = {SensorListener.class, int.class}
-        )
-    })
-    @ToBeFixed(bug="1852042", explanation="Error values in onSensorChanged() from system")
-    @BrokenTest("Relies on earthquakes or user to generate sensor events")
-    public void testSensorManagerOldAPIs() throws InterruptedException, SensorTestTimeOutException {
-        assertEquals(SensorManager.SENSOR_ACCELEROMETER
-                | SensorManager.SENSOR_MAGNETIC_FIELD
-                | SensorManager.SENSOR_ORIENTATION
-                | SensorManager.SENSOR_ORIENTATION_RAW,
-                mSensorManager.getSensors());
-
-        final SensorListener sensorListener = new SensorListener() {
-
-            public void onAccuracyChanged(int sensor, int accuracy) {
-            }
-
-            public void onSensorChanged(int sensor, float[] values) {
-                mSensorListenerOnChangedSensor[mCounter] = sensor;
-                mSensorListenerOnChangedValus[mCounter] = values;
-                if(sensor == SensorManager.SENSOR_ACCELEROMETER) {
-                    Log.d(TAG, "Deprecated Accelerometer value X="
-                            + values[0] + " Y=" + values[1] + " Z=" + values[2]);
-                } else {
-                    Log.d(TAG, "Deprecated Orientation value X="
-                            + values[0] + " Y=" + values[1] + " Z=" + values[2]);
-                }
-                addSyncCount();
-            }
-        };
-        mSensorManager.registerListener(sensorListener,
-                SensorManager.SENSOR_ACCELEROMETER
-                | SensorManager.SENSOR_ORIENTATION
-                | SensorManager.SENSOR_ORIENTATION_RAW,
-                SensorManager.SENSOR_DELAY_NORMAL);
-        waitForSensorEvent();
-
-        mSensorManager.unregisterListener(sensorListener, SensorManager.SENSOR_ACCELEROMETER);
-        // Following should unregister sensorListener for all sensor type but it didn't.
-        // So we unregisterListener by the other unregister method.
-        // mSensorManager.unregisterListener(sensorListener);
-        mSensorManager.unregisterListener(sensorListener, SensorManager.SENSOR_ORIENTATION |
-                SensorManager.SENSOR_ORIENTATION_RAW);
-        try {
-            waitForSensorEvent();
-            fail("SensorListener have been unregistered, shouldn't get any more sensor events.");
-        } catch (SensorTestTimeOutException e) {
-            // expected
-        }
-    }
-
-    @TestTargets({
-        @TestTargetNew(
-            level = TestLevel.COMPLETE,
-            method = "getDefaultSensor",
-            args = {int.class}
-        ),
-        @TestTargetNew(
-            level = TestLevel.COMPLETE,
-            method = "getInclination",
-            args = {float[].class}
-        ),
-        @TestTargetNew(
-            level = TestLevel.COMPLETE,
-            method = "getOrientation",
-            args = {float[].class, float[].class}
-        ),
-        @TestTargetNew(
-            level = TestLevel.COMPLETE,
-            method = "getRotationMatrix",
-            args = {float[].class, float[].class, float[].class, float[].class}
-        ),
-        @TestTargetNew(
-            level = TestLevel.COMPLETE,
-            method = "getSensorList",
-            args = {int.class}
-        ),
-        @TestTargetNew(
-            level = TestLevel.COMPLETE,
-            method = "registerListener",
-            args = {SensorEventListener.class, Sensor.class, int.class}
-        ),
-        @TestTargetNew(
-            level = TestLevel.COMPLETE,
-            method = "registerListener",
-            args = {SensorEventListener.class, Sensor.class, int.class, android.os.Handler.class}
-        ),
-        @TestTargetNew(
-            level = TestLevel.COMPLETE,
-            method = "remapCoordinateSystem",
-            args = {float[].class, int.class, int.class, float[].class}
-        ),
-        @TestTargetNew(
-            level = TestLevel.COMPLETE,
-            method = "unregisterListener",
-            args = {SensorEventListener.class}
-        ),
-        @TestTargetNew(
-            level = TestLevel.COMPLETE,
-            method = "unregisterListener",
-            args = {SensorEventListener.class, Sensor.class}
-        )
-    })
-    @ToBeFixed(bug="1852042", explanation="Error values in onSensorChanged() from system")
-    @BrokenTest("Relies on earthquakes or user to generate sensor events")
-    public void testSensorManager() throws InterruptedException, SensorTestTimeOutException {
-        final List<Sensor> sensorList = mSensorManager.getSensorList(Sensor.TYPE_ALL);
-        assertNotNull(sensorList);
-        Sensor sensor = mSensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);
-        assertEquals(Sensor.TYPE_ACCELEROMETER, sensor.getType());
-        final SensorEventListener sensorAccListener = new SensorEventListener() {
-            public void onAccuracyChanged(Sensor sensor, int accuracy) {
-            }
-
-            public void onSensorChanged(SensorEvent event) {
-                assertEquals(Sensor.TYPE_ACCELEROMETER, event.sensor.getType());
-                Log.d(TAG, "Accelerometer value X="
-                        + event.values[0] + " Y=" + event.values[1] + " Z=" + event.values[2]);
-                addSyncCount();
-            }
-        };
-
-        mSensorManager.registerListener(sensorAccListener,
-                sensor, SensorManager.SENSOR_DELAY_NORMAL);
-        waitForSensorEvent();
-
-        mSensorManager.unregisterListener(sensorAccListener);
-        try {
-            waitForSensorEvent();
-            fail("SensorListener have been unregistered, shouldn't get any more sensor events.");
-        } catch (SensorTestTimeOutException e) {
-            // expected
-        }
-
-        sensor = mSensorManager.getDefaultSensor(Sensor.TYPE_MAGNETIC_FIELD);
-        assertEquals(Sensor.TYPE_MAGNETIC_FIELD, sensor.getType());
-        final SensorEventListener sensorMagListener = new SensorEventListener() {
-            public void onAccuracyChanged(Sensor sensor, int accuracy) {
-            }
-
-            public void onSensorChanged(SensorEvent event) {
-                mSensorEvents[mCounter] = event;
-                Log.d(TAG, "Magnetic field value X="
-                        + event.values[0] + " Y=" + event.values[1] + " Z=" + event.values[2]);
-                addSyncCount();
-            }
-        };
-
-        mSensorManager.registerListener(sensorMagListener, sensor,
-                SensorManager.SENSOR_DELAY_FASTEST, null);
-        waitForSensorEvent();
-        mSensorManager.unregisterListener(sensorMagListener, sensor);
-
-        assertSensorEvents();
-
-        assertTrue(SensorManager.remapCoordinateSystem(mR, SensorManager.AXIS_Y,
-                SensorManager.AXIS_MINUS_X, mR));
-        final float[] outR = new float[VECTOR];
-        assertFalse(SensorManager.remapCoordinateSystem(mR, SensorManager.AXIS_Y,
-                SensorManager.AXIS_MINUS_X, outR));
-    }
-
-    private static void assertSensorEvents() {
-        for (int i = 0; i < MAX_COUNT; i++) {
-            assertEquals(Sensor.TYPE_MAGNETIC_FIELD, mSensorEvents[i].sensor.getType());
-            mGeomagnetic[0] = mSensorEvents[i].values[0];
-            mGeomagnetic[1] = mSensorEvents[i].values[1];
-            mGeomagnetic[2] = mSensorEvents[i].values[2];
-
-            assertTrue(SensorManager.getRotationMatrix(mR, mI, GRAVITY, mGeomagnetic));
-            float inclination = SensorManager.getInclination(mI);
-            assertTrue(Math.abs(inclination) <= TWO_PI);
-            float[] values = new float[3];
-            float[] orientation = SensorManager.getOrientation(mR, values);
-            assertTrue(Math.abs(orientation[0]) <= TWO_PI);
-            assertTrue(Math.abs(orientation[1]) <= TWO_PI);
-            assertTrue(Math.abs(orientation[2]) <= TWO_PI);
-        }
-    }
-}
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>");
+    }
+}