summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--Android.mk43
-rw-r--r--etc/cmsdk-api-coverage46
-rw-r--r--src/Android.mk29
-rw-r--r--src/MANIFEST.mf2
-rw-r--r--src/com/android/cts/apicoverage/ApiClass.java243
-rw-r--r--src/com/android/cts/apicoverage/ApiConstructor.java64
-rw-r--r--src/com/android/cts/apicoverage/ApiCoverage.java49
-rw-r--r--src/com/android/cts/apicoverage/ApiMethod.java99
-rw-r--r--src/com/android/cts/apicoverage/ApiPackage.java103
-rw-r--r--src/com/android/cts/apicoverage/CtsApiCoverage.java240
-rw-r--r--src/com/android/cts/apicoverage/CurrentXmlHandler.java154
-rw-r--r--src/com/android/cts/apicoverage/DexDepsXmlHandler.java90
-rw-r--r--src/com/android/cts/apicoverage/HasCoverage.java52
-rw-r--r--src/com/android/cts/apicoverage/HtmlReport.java69
-rw-r--r--src/com/android/cts/apicoverage/PackageFilter.java57
-rw-r--r--src/com/android/cts/apicoverage/TextReport.java129
-rw-r--r--src/com/android/cts/apicoverage/XmlReport.java137
-rw-r--r--src/res/api-coverage.xsl190
18 files changed, 1796 insertions, 0 deletions
diff --git a/Android.mk b/Android.mk
new file mode 100644
index 0000000..9f257d1
--- /dev/null
+++ b/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 := cmsdk-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/etc/cmsdk-api-coverage b/etc/cmsdk-api-coverage
new file mode 100644
index 0000000..fe7278a
--- /dev/null
+++ b/etc/cmsdk-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/src/Android.mk b/src/Android.mk
new file mode 100644
index 0000000..75d4afa
--- /dev/null
+++ b/src/Android.mk
@@ -0,0 +1,29 @@
+# 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)
+
+
+# cmsdk-api-coverage java library
+# ============================================================
+include $(CLEAR_VARS)
+
+LOCAL_SRC_FILES := $(call all-subdir-java-files)
+LOCAL_JAVA_RESOURCE_DIRS := res
+LOCAL_JAR_MANIFEST := MANIFEST.mf
+
+LOCAL_MODULE := cmsdk-api-coverage
+LOCAL_MODULE_TAGS := optional
+
+include $(BUILD_HOST_JAVA_LIBRARY)
diff --git a/src/MANIFEST.mf b/src/MANIFEST.mf
new file mode 100644
index 0000000..b6aa831
--- /dev/null
+++ b/src/MANIFEST.mf
@@ -0,0 +1,2 @@
+Manifest-Version: 1.0
+Main-Class: com.android.cts.apicoverage.CtsApiCoverage
diff --git a/src/com/android/cts/apicoverage/ApiClass.java b/src/com/android/cts/apicoverage/ApiClass.java
new file mode 100644
index 0000000..73cea67
--- /dev/null
+++ b/src/com/android/cts/apicoverage/ApiClass.java
@@ -0,0 +1,243 @@
+/*
+ * 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 static final String VOID = "void";
+
+ private final String mName;
+
+ private final boolean mDeprecated;
+
+ private final boolean mAbstract;
+
+ private final List<ApiConstructor> mApiConstructors = new ArrayList<ApiConstructor>();
+
+ private final List<ApiMethod> mApiMethods = new ArrayList<ApiMethod>();
+
+ private final String mSuperClassName;
+
+ private ApiClass mSuperClass;
+
+ /**
+ * @param name The name of the class
+ * @param deprecated true iff the class is marked as deprecated
+ * @param classAbstract true iff the class is abstract
+ * @param superClassName The fully qualified name of the super class
+ */
+ ApiClass(
+ String name,
+ boolean deprecated,
+ boolean classAbstract,
+ String superClassName) {
+ mName = name;
+ mDeprecated = deprecated;
+ mAbstract = classAbstract;
+ mSuperClassName = superClassName;
+ }
+
+ @Override
+ public int compareTo(ApiClass another) {
+ return mName.compareTo(another.mName);
+ }
+
+ @Override
+ public String getName() {
+ return mName;
+ }
+
+ public boolean isDeprecated() {
+ return mDeprecated;
+ }
+
+ public String getSuperClassName() {
+ return mSuperClassName;
+ }
+
+ public boolean isAbstract() {
+ return mAbstract;
+ }
+
+ public void setSuperClass(ApiClass superClass) { mSuperClass = superClass; }
+
+ public void addConstructor(ApiConstructor constructor) {
+ mApiConstructors.add(constructor);
+ }
+
+
+ public Collection<ApiConstructor> getConstructors() {
+ return Collections.unmodifiableList(mApiConstructors);
+ }
+
+ public void addMethod(ApiMethod method) {
+ mApiMethods.add(method);
+ }
+
+ /** Look for a matching constructor and mark it as covered */
+ public void markConstructorCovered(List<String> parameterTypes) {
+ if (mSuperClass != null) {
+ // Mark matching constructors in the superclass
+ mSuperClass.markConstructorCovered(parameterTypes);
+ }
+ ApiConstructor apiConstructor = getConstructor(parameterTypes);
+ if (apiConstructor != null) {
+ apiConstructor.setCovered(true);
+ }
+
+ }
+
+ /** Look for a matching method and if found and mark it as covered */
+ public void markMethodCovered(String name, List<String> parameterTypes, String returnType) {
+ if (mSuperClass != null) {
+ // Mark matching methods in the super class
+ mSuperClass.markMethodCovered(name, parameterTypes, returnType);
+ }
+ ApiMethod apiMethod = getMethod(name, parameterTypes, returnType);
+ if (apiMethod != null) {
+ apiMethod.setCovered(true);
+ }
+ }
+
+ 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();
+ }
+
+ @Override
+ public float getCoveragePercentage() {
+ if (getTotalMethods() == 0) {
+ return 100;
+ } else {
+ return (float) getNumCoveredMethods() / getTotalMethods() * 100;
+ }
+ }
+
+ @Override
+ public int getMemberSize() {
+ return getTotalMethods();
+ }
+
+ private ApiMethod getMethod(String name, List<String> parameterTypes, String returnType) {
+ for (ApiMethod method : mApiMethods) {
+ boolean methodNameMatch = name.equals(method.getName());
+ boolean parameterTypeMatch =
+ compareParameterTypes(method.getParameterTypes(), parameterTypes);
+ boolean returnTypeMatch = compareType(method.getReturnType(), returnType);
+ if (methodNameMatch && parameterTypeMatch && returnTypeMatch) {
+ return method;
+ }
+ }
+ return null;
+ }
+
+ /**
+ * The method compares two lists of parameters. If the {@code apiParameterTypeList} contains
+ * generic types, test parameter types are ignored.
+ *
+ * @param apiParameterTypeList The list of parameter types from the API
+ * @param testParameterTypeList The list of parameter types used in a test
+ * @return true iff the list of types are the same.
+ */
+ private static boolean compareParameterTypes(
+ List<String> apiParameterTypeList, List<String> testParameterTypeList) {
+ if (apiParameterTypeList.equals(testParameterTypeList)) {
+ return true;
+ }
+ if (apiParameterTypeList.size() != testParameterTypeList.size()) {
+ return false;
+ }
+
+ for (int i = 0; i < apiParameterTypeList.size(); i++) {
+ String apiParameterType = apiParameterTypeList.get(i);
+ String testParameterType = testParameterTypeList.get(i);
+ if (!compareType(apiParameterType, testParameterType)) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ /**
+ * Compare class types.
+ * @param apiType The type as reported by the api
+ * @param testType The type as found used in a test
+ * @return true iff the strings are equal,
+ * or the apiType is generic and the test type is not void
+ */
+ private static boolean compareType(String apiType, String testType) {
+ return apiType.equals(testType) ||
+ isGenericType(apiType) && !testType.equals(VOID) ||
+ isGenericArrayType(apiType) && isArrayType(testType) ;
+ }
+
+ /**
+ * @return true iff the given parameterType is a generic type.
+ */
+ private static boolean isGenericType(String type) {
+ return type.length() == 1 &&
+ type.charAt(0) >= 'A' &&
+ type.charAt(0) <= 'Z';
+ }
+
+ /**
+ * @return true iff {@code type} ends with an [].
+ */
+ private static boolean isArrayType(String type) {
+ return type.endsWith("[]");
+ }
+
+ /**
+ * @return true iff the given parameterType is an array of generic type.
+ */
+ private static boolean isGenericArrayType(String type) {
+ return type.length() == 3 && isGenericType(type.substring(0, 1)) && isArrayType(type);
+ }
+
+ private ApiConstructor getConstructor(List<String> parameterTypes) {
+ for (ApiConstructor constructor : mApiConstructors) {
+ if (compareParameterTypes(constructor.getParameterTypes(), parameterTypes)) {
+ return constructor;
+ }
+ }
+ return null;
+ }
+}
diff --git a/src/com/android/cts/apicoverage/ApiConstructor.java b/src/com/android/cts/apicoverage/ApiConstructor.java
new file mode 100644
index 0000000..8d721e6
--- /dev/null
+++ b/src/com/android/cts/apicoverage/ApiConstructor.java
@@ -0,0 +1,64 @@
+/*
+ * 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 final boolean mDeprecated;
+
+ private boolean mIsCovered;
+
+ ApiConstructor(String name, List<String> parameterTypes, boolean deprecated) {
+ mName = name;
+ mParameterTypes = new ArrayList<String>(parameterTypes);
+ mDeprecated = deprecated;
+ }
+
+ @Override
+ 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 isDeprecated() {
+ return mDeprecated;
+ }
+
+ public boolean isCovered() {
+ return mIsCovered;
+ }
+
+ public void setCovered(boolean covered) {
+ mIsCovered = covered;
+ }
+} \ No newline at end of file
diff --git a/src/com/android/cts/apicoverage/ApiCoverage.java b/src/com/android/cts/apicoverage/ApiCoverage.java
new file mode 100644
index 0000000..953aab3
--- /dev/null
+++ b/src/com/android/cts/apicoverage/ApiCoverage.java
@@ -0,0 +1,49 @@
+/*
+ * 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.lang.String;
+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());
+ }
+
+ /** Iterate through all packages and update all classes to include its superclass */
+ public void resolveSuperClasses() {
+ for (Map.Entry<String, ApiPackage> entry : mPackages.entrySet()) {
+ ApiPackage pkg = entry.getValue();
+ pkg.resolveSuperClasses(mPackages);
+ }
+ }
+}
diff --git a/src/com/android/cts/apicoverage/ApiMethod.java b/src/com/android/cts/apicoverage/ApiMethod.java
new file mode 100644
index 0000000..582c2b6
--- /dev/null
+++ b/src/com/android/cts/apicoverage/ApiMethod.java
@@ -0,0 +1,99 @@
+/*
+ * 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 final boolean mDeprecated;
+
+ private final String mVisibility;
+
+ private final boolean mStaticMethod;
+
+ private final boolean mFinalMethod;
+
+ private final boolean mAbstractMethod;
+
+ private boolean mIsCovered;
+
+ ApiMethod(
+ String name,
+ List<String> parameterTypes,
+ String returnType,
+ boolean deprecated,
+ String visibility,
+ boolean staticMethod,
+ boolean finalMethod,
+ boolean abstractMethod) {
+ mName = name;
+ mParameterTypes = new ArrayList<String>(parameterTypes);
+ mReturnType = returnType;
+ mDeprecated = deprecated;
+ mVisibility = visibility;
+ mStaticMethod = staticMethod;
+ mFinalMethod = finalMethod;
+ mAbstractMethod = abstractMethod;
+ }
+
+ @Override
+ 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 isDeprecated() {
+ return mDeprecated;
+ }
+
+ public boolean isCovered() {
+ return mIsCovered;
+ }
+
+ public String getVisibility() { return mVisibility; }
+
+ public boolean isAbstractMethod() { return mAbstractMethod; }
+
+ public boolean isStaticMethod() { return mStaticMethod; }
+
+ public boolean isFinalMethod() { return mFinalMethod; }
+
+ public void setCovered(boolean covered) {
+ mIsCovered = covered;
+ }
+} \ No newline at end of file
diff --git a/src/com/android/cts/apicoverage/ApiPackage.java b/src/com/android/cts/apicoverage/ApiPackage.java
new file mode 100644
index 0000000..7be7e3c
--- /dev/null
+++ b/src/com/android/cts/apicoverage/ApiPackage.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.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.Map.Entry;
+
+/** 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) {
+ mName = name;
+ }
+
+ @Override
+ 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;
+ }
+
+ @Override
+ public float getCoveragePercentage() {
+ return (float) getNumCoveredMethods() / getTotalMethods() * 100;
+ }
+
+ @Override
+ public int getMemberSize() {
+ return getTotalMethods();
+ }
+
+ /** Iterate through all classes and add superclass. */
+ public void resolveSuperClasses(Map<String, ApiPackage> packageMap) {
+ Iterator<Entry<String, ApiClass>> it = mApiClassMap.entrySet().iterator();
+ while (it.hasNext()) {
+ Map.Entry<String, ApiClass> entry = it.next();
+ ApiClass apiClass = entry.getValue();
+ if (apiClass.getSuperClassName() != null) {
+ String superClassName = apiClass.getSuperClassName();
+ // Split the fully qualified class name into package and class name.
+ String packageName = superClassName.substring(0, superClassName.lastIndexOf('.'));
+ String className = superClassName.substring(
+ superClassName.lastIndexOf('.') + 1, superClassName.length());
+ if (packageMap.containsKey(packageName)) {
+ ApiPackage apiPackage = packageMap.get(packageName);
+ ApiClass superClass = apiPackage.getClass(className);
+ if (superClass != null) {
+ // Add the super class
+ apiClass.setSuperClass(superClass);
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/src/com/android/cts/apicoverage/CtsApiCoverage.java b/src/com/android/cts/apicoverage/CtsApiCoverage.java
new file mode 100644
index 0000000..9647da7
--- /dev/null
+++ b/src/com/android/cts/apicoverage/CtsApiCoverage.java
@@ -0,0 +1,240 @@
+/*
+ * 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.FilenameFilter;
+import java.io.FileOutputStream;
+import java.io.FileReader;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.util.Arrays;
+import java.util.ArrayList;
+import java.util.List;
+
+import javax.xml.transform.TransformerException;
+
+/**
+ * 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 FilenameFilter SUPPORTED_FILE_NAME_FILTER = new FilenameFilter() {
+ public boolean accept(File dir, String name) {
+ String fileName = name.toLowerCase();
+ return fileName.endsWith(".apk") || fileName.endsWith(".jar");
+ }
+ };
+
+ private static final int FORMAT_TXT = 0;
+
+ private static final int FORMAT_XML = 1;
+
+ private static final int FORMAT_HTML = 2;
+
+ private static void printUsage() {
+ System.out.println("Usage: cmsdk-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|html] format of output");
+ System.out.println(" -d PATH path to dexdeps or expected to be in $PATH");
+ System.out.println(" -a PATH path to the API XML file");
+ System.out.println(" -p PACKAGENAMEPREFIX report coverage only for package that start with");
+ System.out.println(" -t TITLE report title");
+ System.out.println(" -cm CYANOGENMOD include cyanogenmod classes");
+ 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;
+ String dexDeps = "dexDeps";
+ String apiXmlPath = "";
+ PackageFilter packageFilter = new PackageFilter();
+ String reportTitle = "CMSDK API Coverage";
+ boolean includeCMClasses = false;
+
+ for (int i = 0; i < args.length; i++) {
+ if (args[i].startsWith("-")) {
+ if ("-o".equals(args[i])) {
+ outputFile = new File(getExpectedArg(args, ++i));
+ } else if ("-f".equals(args[i])) {
+ String formatSpec = getExpectedArg(args, ++i);
+ if ("xml".equalsIgnoreCase(formatSpec)) {
+ format = FORMAT_XML;
+ } else if ("txt".equalsIgnoreCase(formatSpec)) {
+ format = FORMAT_TXT;
+ } else if ("html".equalsIgnoreCase(formatSpec)) {
+ format = FORMAT_HTML;
+ } else {
+ printUsage();
+ }
+ } else if ("-d".equals(args[i])) {
+ dexDeps = getExpectedArg(args, ++i);
+ } else if ("-a".equals(args[i])) {
+ apiXmlPath = getExpectedArg(args, ++i);
+ } else if ("-p".equals(args[i])) {
+ packageFilter.addPrefixToFilter(getExpectedArg(args, ++i));
+ } else if ("-t".equals(args[i])) {
+ reportTitle = getExpectedArg(args, ++i);
+ } else if ("-cm".equals(args[i])) {
+ includeCMClasses = true;
+ } else {
+ printUsage();
+ }
+ } else {
+ File file = new File(args[i]);
+ if (file.isDirectory()) {
+ testApks.addAll(Arrays.asList(file.listFiles(SUPPORTED_FILE_NAME_FILTER)));
+ } else {
+ testApks.add(file);
+ }
+ }
+ }
+
+ /*
+ * 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(apiXmlPath);
+ // Add superclass information into api coverage.
+ apiCoverage.resolveSuperClasses();
+ for (File testApk : testApks) {
+ addApiCoverage(apiCoverage, testApk, dexDeps, includeCMClasses);
+ }
+ outputCoverageReport(apiCoverage, testApks, outputFile, format, packageFilter, reportTitle);
+ }
+
+ /** Get the argument or print out the usage and exit. */
+ private static String getExpectedArg(String[] args, int index) {
+ if (index < args.length) {
+ return args[index];
+ } else {
+ printUsage();
+ return null; // Never will happen because printUsage will call exit(1)
+ }
+ }
+
+ /**
+ * Creates an object representing the API that will be used later to collect coverage
+ * statistics as we iterate over the test APKs.
+ *
+ * @param apiXmlPath to the API XML file
+ * @return an {@link ApiCoverage} object representing the API in current.xml without any
+ * coverage statistics yet
+ */
+ private static ApiCoverage getEmptyApiCoverage(String apiXmlPath)
+ throws SAXException, IOException {
+ XMLReader xmlReader = XMLReaderFactory.createXMLReader();
+ CurrentXmlHandler currentXmlHandler = new CurrentXmlHandler();
+ xmlReader.setContentHandler(currentXmlHandler);
+
+ File currentXml = new File(apiXmlPath);
+ 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, String dexdeps,
+ boolean includeCmClasses) throws SAXException, IOException {
+ XMLReader xmlReader = XMLReaderFactory.createXMLReader();
+ DexDepsXmlHandler dexDepsXmlHandler = new DexDepsXmlHandler(apiCoverage);
+ xmlReader.setContentHandler(dexDepsXmlHandler);
+
+ String apkPath = testApk.getPath();
+ Process process;
+ if (includeCmClasses) {
+ process = new ProcessBuilder(dexdeps, "--format=xml", "--include-cm-classes",
+ apkPath).start();
+ } else {
+ process = new ProcessBuilder(dexdeps, "--format=xml", apkPath).start();
+ }
+ try {
+ xmlReader.parse(new InputSource(process.getInputStream()));
+ } catch (SAXException e) {
+ // Catch this exception, but continue. SAXException is acceptable in cases
+ // where the apk does not contain a classes.dex and therefore parsing won't work.
+ System.err.println("warning: dexdeps failed for: " + apkPath);
+ }
+ }
+
+ private static void outputCoverageReport(ApiCoverage apiCoverage, List<File> testApks,
+ File outputFile, int format, PackageFilter packageFilter, String reportTitle)
+ throws IOException, TransformerException, InterruptedException {
+
+ OutputStream out = outputFile != null
+ ? new FileOutputStream(outputFile)
+ : System.out;
+
+ try {
+ switch (format) {
+ case FORMAT_TXT:
+ TextReport.printTextReport(apiCoverage, packageFilter, out);
+ break;
+
+ case FORMAT_XML:
+ XmlReport.printXmlReport(testApks, apiCoverage, packageFilter, reportTitle, out);
+ break;
+
+ case FORMAT_HTML:
+ HtmlReport.printHtmlReport(testApks, apiCoverage, packageFilter, reportTitle, out);
+ break;
+ }
+ } finally {
+ out.close();
+ }
+ }
+}
diff --git a/src/com/android/cts/apicoverage/CurrentXmlHandler.java b/src/com/android/cts/apicoverage/CurrentXmlHandler.java
new file mode 100644
index 0000000..de9f5d5
--- /dev/null
+++ b/src/com/android/cts/apicoverage/CurrentXmlHandler.java
@@ -0,0 +1,154 @@
+/*
+ * 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 boolean mIgnoreCurrentClass;
+
+ private String mCurrentMethodName;
+
+ private String mCurrentMethodReturnType;
+
+ private boolean mCurrentMethodIsAbstract;
+
+ private String mCurrentMethodVisibility;
+
+ private boolean mCurrentMethodStaticMethod;
+
+ private boolean mCurrentMethodFinalMethod;
+
+ private boolean mDeprecated;
+
+
+ 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 = getValue(attributes, "name");
+
+ ApiPackage apiPackage = new ApiPackage(mCurrentPackageName);
+ mApiCoverage.addPackage(apiPackage);
+
+ } else if ("class".equalsIgnoreCase(localName)) {
+ if (isEnum(attributes)) {
+ mIgnoreCurrentClass = true;
+ return;
+ }
+ mIgnoreCurrentClass = false;
+ mCurrentClassName = getValue(attributes, "name");
+ mDeprecated = isDeprecated(attributes);
+ String superClass = attributes.getValue("extends");
+ ApiClass apiClass = new ApiClass(
+ mCurrentClassName, mDeprecated, is(attributes, "abstract"), superClass);
+ ApiPackage apiPackage = mApiCoverage.getPackage(mCurrentPackageName);
+ apiPackage.addClass(apiClass);
+ } else if ("interface".equalsIgnoreCase(localName)) {
+ // don't add interface
+ mIgnoreCurrentClass = true;
+ } else if ("constructor".equalsIgnoreCase(localName)) {
+ mDeprecated = isDeprecated(attributes);
+ mCurrentParameterTypes.clear();
+ } else if ("method".equalsIgnoreCase(localName)) {
+ mDeprecated = isDeprecated(attributes);
+ mCurrentMethodName = getValue(attributes, "name");
+ mCurrentMethodReturnType = getValue(attributes, "return");
+ mCurrentMethodIsAbstract = is(attributes, "abstract");
+ mCurrentMethodVisibility = getValue(attributes, "visibility");
+ mCurrentMethodStaticMethod = is(attributes, "static");
+ mCurrentMethodFinalMethod = is(attributes, "final");
+ mCurrentParameterTypes.clear();
+ } else if ("parameter".equalsIgnoreCase(localName)) {
+ mCurrentParameterTypes.add(getValue(attributes, "type"));
+ }
+ }
+
+ @Override
+ public void endElement(String uri, String localName, String name) throws SAXException {
+ super.endElement(uri, localName, name);
+ if (mIgnoreCurrentClass) {
+ // do not add anything for interface
+ return;
+ }
+ if ("constructor".equalsIgnoreCase(localName)) {
+ if (mCurrentParameterTypes.isEmpty()) {
+ // Don't add empty default constructors...
+ return;
+ }
+ ApiConstructor apiConstructor = new ApiConstructor(mCurrentClassName,
+ mCurrentParameterTypes, mDeprecated);
+ 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,
+ mDeprecated,
+ mCurrentMethodVisibility,
+ mCurrentMethodStaticMethod,
+ mCurrentMethodFinalMethod,
+ mCurrentMethodIsAbstract);
+ 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("$", ".");
+ }
+
+ private boolean isDeprecated(Attributes attributes) {
+ return "deprecated".equals(attributes.getValue("deprecated"));
+ }
+
+ private static boolean is(Attributes attributes, String valueName) {
+ return "true".equals(attributes.getValue(valueName));
+ }
+
+ private boolean isEnum(Attributes attributes) {
+ return "java.lang.Enum".equals(attributes.getValue("extends"));
+ }
+} \ No newline at end of file
diff --git a/src/com/android/cts/apicoverage/DexDepsXmlHandler.java b/src/com/android/cts/apicoverage/DexDepsXmlHandler.java
new file mode 100644
index 0000000..3df532e
--- /dev/null
+++ b/src/com/android/cts/apicoverage/DexDepsXmlHandler.java
@@ -0,0 +1,90 @@
+/*
+ * 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) {
+ apiClass.markConstructorCovered(mCurrentParameterTypes);
+ }
+ }
+ } else if ("method".equalsIgnoreCase(localName)) {
+ ApiPackage apiPackage = mPackageMap.getPackage(mCurrentPackageName);
+ if (apiPackage != null) {
+ ApiClass apiClass = apiPackage.getClass(mCurrentClassName);
+ if (apiClass != null) {
+ apiClass.markMethodCovered(
+ mCurrentMethodName, mCurrentParameterTypes, mCurrentMethodReturnType);
+ }
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/src/com/android/cts/apicoverage/HasCoverage.java b/src/com/android/cts/apicoverage/HasCoverage.java
new file mode 100644
index 0000000..f8d8054
--- /dev/null
+++ b/src/com/android/cts/apicoverage/HasCoverage.java
@@ -0,0 +1,52 @@
+/*
+ * 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();
+ int getMemberSize();
+ String getName();
+}
+
+class CoverageComparator implements Comparator<HasCoverage> {
+ public int compare(HasCoverage entity, HasCoverage otherEntity) {
+ int lhsPct = Math.round(entity.getCoveragePercentage());
+ int rhsPct = Math.round(otherEntity.getCoveragePercentage());
+ int diff = Integer.compare(getCoveragePercentageSegment(lhsPct),
+ getCoveragePercentageSegment(rhsPct));
+ return diff != 0 ? diff :
+ Integer.compare(otherEntity.getMemberSize(), entity.getMemberSize());
+ }
+
+ /**
+ * Distill coverage percentage down to 3 major segments
+ * @param coveragePercentage
+ * @return
+ */
+ private int getCoveragePercentageSegment(int coveragePercentage) {
+ // note that this segmentation logic is duplicated in api-coverage.xsl
+ if (coveragePercentage <= 50) {
+ return 0;
+ } else if (coveragePercentage <= 80) {
+ return 1;
+ } else {
+ return 2;
+ }
+ }
+}
diff --git a/src/com/android/cts/apicoverage/HtmlReport.java b/src/com/android/cts/apicoverage/HtmlReport.java
new file mode 100644
index 0000000..0e6b54a
--- /dev/null
+++ b/src/com/android/cts/apicoverage/HtmlReport.java
@@ -0,0 +1,69 @@
+/*
+ * 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.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.PipedInputStream;
+import java.io.PipedOutputStream;
+import java.util.List;
+
+import javax.xml.transform.Transformer;
+import javax.xml.transform.TransformerException;
+import javax.xml.transform.TransformerFactory;
+import javax.xml.transform.stream.StreamResult;
+import javax.xml.transform.stream.StreamSource;
+
+/**
+ * Class that outputs an HTML report of the {@link ApiCoverage} collected. It is the XML report
+ * transformed into HTML.
+ */
+class HtmlReport {
+
+ public static void printHtmlReport(final List<File> testApks, final ApiCoverage apiCoverage,
+ final PackageFilter packageFilter, final String reportTitle, final OutputStream out)
+ throws IOException, TransformerException {
+ final PipedOutputStream xmlOut = new PipedOutputStream();
+ final PipedInputStream xmlIn = new PipedInputStream(xmlOut);
+
+ Thread t = new Thread(new Runnable() {
+ @Override
+ public void run() {
+ XmlReport.printXmlReport(testApks, apiCoverage, packageFilter, reportTitle, xmlOut);
+
+ // Close the output stream to avoid "Write dead end" errors.
+ try {
+ xmlOut.close();
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ }
+ });
+ t.start();
+
+ InputStream xsl = CtsApiCoverage.class.getResourceAsStream("/api-coverage.xsl");
+ StreamSource xslSource = new StreamSource(xsl);
+ TransformerFactory factory = TransformerFactory.newInstance();
+ Transformer transformer = factory.newTransformer(xslSource);
+
+ StreamSource xmlSource = new StreamSource(xmlIn);
+ StreamResult result = new StreamResult(out);
+ transformer.transform(xmlSource, result);
+ }
+}
diff --git a/src/com/android/cts/apicoverage/PackageFilter.java b/src/com/android/cts/apicoverage/PackageFilter.java
new file mode 100644
index 0000000..b196d8f
--- /dev/null
+++ b/src/com/android/cts/apicoverage/PackageFilter.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright (C) 2015 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.List;
+
+/**
+ * Util class to support package filtering logic
+ * <p>
+ * A list of package prefixes can be added to the filter, and {{@link #accept(String)} method will
+ * decide if the provided package name matches any of the prefixes.
+ */
+public class PackageFilter {
+
+ private List<String> mFilters = new ArrayList<>();
+
+ /**
+ * Check if a particular package name matches any of the package prefixes configured in filter.
+ * If no filters are configured, any package names will be accepted
+ * @param packageName
+ * @return
+ */
+ public boolean accept(String packageName) {
+ if (mFilters.isEmpty()) {
+ return true;
+ }
+ for (String filter : mFilters) {
+ if (packageName.startsWith(filter)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ public void addPrefixToFilter(String prefix) {
+ mFilters.add(prefix);
+ }
+
+ public void clearFilter() {
+ mFilters.clear();
+ }
+}
diff --git a/src/com/android/cts/apicoverage/TextReport.java b/src/com/android/cts/apicoverage/TextReport.java
new file mode 100644
index 0000000..3adc020
--- /dev/null
+++ b/src/com/android/cts/apicoverage/TextReport.java
@@ -0,0 +1,129 @@
+/*
+ * 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, PackageFilter packageFilter,
+ 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 (packageFilter.accept(apiPackage.getName()) && apiPackage.getTotalMethods() > 0) {
+ printPackage(apiPackage, out);
+ }
+ }
+
+ out.println();
+ out.println();
+
+ for (ApiPackage apiPackage : packages) {
+ if (packageFilter.accept(apiPackage.getName())) {
+ 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.getVisibility()).append(" ");
+ if (method.isAbstractMethod()) {
+ builder.append("abstract ");
+ }
+ if (method.isStaticMethod()) {
+ builder.append("static ");
+ }
+ if (method.isFinalMethod()) {
+ builder.append("final ");
+ }
+ builder.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/src/com/android/cts/apicoverage/XmlReport.java b/src/com/android/cts/apicoverage/XmlReport.java
new file mode 100644
index 0000000..4310d20
--- /dev/null
+++ b/src/com/android/cts/apicoverage/XmlReport.java
@@ -0,0 +1,137 @@
+/*
+ * 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,
+ PackageFilter packageFilter, String reportTitle, 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 + "\" title=\"" + reportTitle +"\">");
+
+ 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);
+ int totalMethods = 0;
+ int totalCoveredMethods = 0;
+ for (ApiPackage pkg : packages) {
+ if (packageFilter.accept(pkg.getName()) && pkg.getTotalMethods() > 0) {
+ int pkgTotal = pkg.getTotalMethods();
+ totalMethods += pkgTotal;
+ int pkgTotalCovered = pkg.getNumCoveredMethods();
+ totalCoveredMethods += pkgTotalCovered;
+ out.println("<package name=\"" + pkg.getName()
+ + "\" numCovered=\"" + pkgTotalCovered
+ + "\" numTotal=\"" + pkgTotal
+ + "\" 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()
+ + "\" deprecated=\"" + apiClass.isDeprecated()
+ + "\" coveragePercentage=\""
+ + Math.round(apiClass.getCoveragePercentage())
+ + "\">");
+
+ for (ApiConstructor constructor : apiClass.getConstructors()) {
+ out.println("<constructor name=\"" + constructor.getName()
+ + "\" deprecated=\"" + constructor.isDeprecated()
+ + "\" covered=\"" + constructor.isCovered() + "\">");
+ if (constructor.isDeprecated()) {
+ if (constructor.isCovered()) {
+ totalCoveredMethods -= 1;
+ }
+ totalMethods -= 1;
+ }
+ 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()
+ + "\" deprecated=\"" + method.isDeprecated()
+ + "\" static=\"" + method.isStaticMethod()
+ + "\" final=\"" + method.isFinalMethod()
+ + "\" visibility=\"" + method.getVisibility()
+ + "\" abstract=\"" + method.isAbstractMethod()
+ + "\" covered=\"" + method.isCovered() + "\">");
+ if (method.isDeprecated()) {
+ if (method.isCovered()) {
+ totalCoveredMethods -= 1;
+ }
+ totalMethods -= 1;
+ }
+ for (String parameterType : method.getParameterTypes()) {
+ out.println("<parameter type=\"" + parameterType + "\" />");
+ }
+
+ out.println("</method>");
+ }
+ out.println("</class>");
+ }
+ }
+ out.println("</package>");
+ }
+ }
+
+ out.println("</api>");
+ out.println("<total numCovered=\"" + totalCoveredMethods + "\" "
+ + "numTotal=\"" + totalMethods + "\" "
+ + "coveragePercentage=\""
+ + Math.round((float)totalCoveredMethods / totalMethods * 100.0f) + "\" />");
+ out.println("</api-coverage>");
+ }
+}
diff --git a/src/res/api-coverage.xsl b/src/res/api-coverage.xsl
new file mode 100644
index 0000000..1a56eb0
--- /dev/null
+++ b/src/res/api-coverage.xsl
@@ -0,0 +1,190 @@
+<?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>
+ <title><xsl:value-of select="api-coverage/@title" /></title>
+ <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">
+ 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: #FF6666;
+ }
+
+ .yellow {
+ background-color: #FFFF66;
+ }
+
+ .green {
+ background-color: #66FF66;
+ }
+
+ .deprecated {
+ text-decoration: line-through;
+ }
+ </style>
+ </head>
+ <body>
+ <h1><xsl:value-of select="api-coverage/@title" /></h1>
+ <div class="info">
+ Generated: <xsl:value-of select="api-coverage/@generatedTime" />
+ </div>
+ <div class="total">
+ Total:&nbsp;<xsl:value-of select="api-coverage/total/@coveragePercentage" />%
+ &nbsp;(<xsl:value-of select="api-coverage/total/@numCovered" />/<xsl:value-of select="api-coverage/total/@numTotal" />)
+ </div>
+ <div class="apks" onclick="toggleVisibility('sourceApks')">
+ Source Modules (<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:with-param name="toggleId" select="@name" />
+ </xsl:call-template>
+ <div class="packageDetails" id="{@name}" style="display: none">
+ <ul>
+ <xsl:for-each select="class">
+ <xsl:variable name="packageClassId" select="concat(../@name, '.', @name)"/>
+ <xsl:call-template name="packageOrClassListItem">
+ <xsl:with-param name="bulletClass" select="'class'" />
+ <xsl:with-param name="toggleId" select="$packageClassId" />
+ </xsl:call-template>
+ <div class="classDetails" id="{$packageClassId}" 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:param name="toggleId"/>
+
+ <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>
+
+ <xsl:variable name="deprecatedClass">
+ <xsl:choose>
+ <xsl:when test="@deprecated = 'true'">deprecated</xsl:when>
+ <xsl:otherwise></xsl:otherwise>
+ </xsl:choose>
+ </xsl:variable>
+
+ <li class="{$bulletClass}" onclick="toggleVisibility('{$toggleId}')">
+ <span class="{$colorClass} {$deprecatedClass}">
+ <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">
+
+ <xsl:variable name="deprecatedClass">
+ <xsl:choose>
+ <xsl:when test="@deprecated = 'true'">deprecated</xsl:when>
+ <xsl:otherwise></xsl:otherwise>
+ </xsl:choose>
+ </xsl:variable>
+
+ <span class="method {$deprecatedClass}">
+ <xsl:choose>
+ <xsl:when test="@covered = 'true'">[X]</xsl:when>
+ <xsl:otherwise>[ ]</xsl:otherwise>
+ </xsl:choose>
+ <xsl:if test="@visibility != ''">&nbsp;<xsl:value-of select="@visibility" /></xsl:if>
+ <xsl:if test="@abstract = 'true'">&nbsp;abstract</xsl:if>
+ <xsl:if test="@static = 'true'">&nbsp;static</xsl:if>
+ <xsl:if test="@final = 'true'">&nbsp;final</xsl:if>
+ <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>
+