improve method coverate analysis of CTS

- do not count interface / abstract class with only abstract methods
- do not count enums
- add total count (do not include deprecated)

Change-Id: Iaa8ce0bf84963d1819292fa8b1aaeada2a761731
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
index 101be7f..2a62aa0 100644
--- a/tools/cts-api-coverage/src/com/android/cts/apicoverage/ApiClass.java
+++ b/tools/cts-api-coverage/src/com/android/cts/apicoverage/ApiClass.java
@@ -28,13 +28,16 @@
 
     private final boolean mDeprecated;
 
+    private final boolean mAbstract;
+
     private final List<ApiConstructor> mApiConstructors = new ArrayList<ApiConstructor>();
 
     private final List<ApiMethod> mApiMethods = new ArrayList<ApiMethod>();
 
-    ApiClass(String name, boolean deprecated) {
+    ApiClass(String name, boolean deprecated, boolean classAbstract) {
         mName = name;
         mDeprecated = deprecated;
+        mAbstract = classAbstract;
     }
 
     @Override
@@ -51,6 +54,10 @@
         return mDeprecated;
     }
 
+    public boolean isAbstract() {
+        return mAbstract;
+    }
+
     public void addConstructor(ApiConstructor constructor) {
         mApiConstructors.add(constructor);
     }
@@ -108,6 +115,10 @@
 
     @Override
     public float getCoveragePercentage() {
-        return (float) getNumCoveredMethods() / getTotalMethods() * 100;
+        if (getTotalMethods() == 0) {
+            return 100;
+        } else {
+            return (float) getNumCoveredMethods() / getTotalMethods() * 100;
+        }
     }
-}
\ 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
index dc40062..adf2ea9 100644
--- a/tools/cts-api-coverage/src/com/android/cts/apicoverage/ApiCoverage.java
+++ b/tools/cts-api-coverage/src/com/android/cts/apicoverage/ApiCoverage.java
@@ -16,6 +16,7 @@
 
 package com.android.cts.apicoverage;
 
+import java.lang.String;
 import java.util.Collection;
 import java.util.Collections;
 import java.util.HashMap;
@@ -37,4 +38,11 @@
     public Collection<ApiPackage> getPackages() {
         return Collections.unmodifiableCollection(mPackages.values());
     }
+
+    public void removeEmptyAbstractClasses() {
+        for (Map.Entry<String, ApiPackage> entry : mPackages.entrySet()) {
+            ApiPackage pkg = entry.getValue();
+            pkg.removeEmptyAbstractClasses();
+        }
+    }
 }
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
index ddc6fb4..c83256c 100644
--- a/tools/cts-api-coverage/src/com/android/cts/apicoverage/ApiPackage.java
+++ b/tools/cts-api-coverage/src/com/android/cts/apicoverage/ApiPackage.java
@@ -19,7 +19,9 @@
 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 {
@@ -69,4 +71,16 @@
     public float getCoveragePercentage() {
         return (float) getNumCoveredMethods() / getTotalMethods() * 100;
     }
-}
\ No newline at end of file
+
+    public void removeEmptyAbstractClasses() {
+        Iterator<Entry<String, ApiClass>> it = mApiClassMap.entrySet().iterator();
+        while (it.hasNext()) {
+            Map.Entry<String, ApiClass> entry = it.next();
+            ApiClass cls = entry.getValue();
+            if (cls.isAbstract() && (cls.getTotalMethods() == 0)) {
+                // this is essentially interface
+                it.remove();
+            }
+        }
+    }
+}
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
index 2923ba2..d6abf9a 100644
--- a/tools/cts-api-coverage/src/com/android/cts/apicoverage/CtsApiCoverage.java
+++ b/tools/cts-api-coverage/src/com/android/cts/apicoverage/CtsApiCoverage.java
@@ -109,6 +109,7 @@
          */
 
         ApiCoverage apiCoverage = getEmptyApiCoverage(apiXmlPath);
+        apiCoverage.removeEmptyAbstractClasses();
         for (File testApk : testApks) {
             addApiCoverage(apiCoverage, testApk, dexDeps);
         }
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
index f3abd86..b9f9e9c 100644
--- a/tools/cts-api-coverage/src/com/android/cts/apicoverage/CurrentXmlHandler.java
+++ b/tools/cts-api-coverage/src/com/android/cts/apicoverage/CurrentXmlHandler.java
@@ -32,12 +32,17 @@
 
     private String mCurrentClassName;
 
+    private boolean mIgnoreCurrentClass;
+
     private String mCurrentMethodName;
 
     private String mCurrentMethodReturnType;
 
+    private boolean mCurrentMethodIsAbstract;
+
     private boolean mDeprecated;
 
+
     private List<String> mCurrentParameterTypes = new ArrayList<String>();
 
     private ApiCoverage mApiCoverage = new ApiCoverage();
@@ -56,15 +61,20 @@
             ApiPackage apiPackage = new ApiPackage(mCurrentPackageName);
             mApiCoverage.addPackage(apiPackage);
 
-        } else if ("class".equalsIgnoreCase(localName)
-                || "interface".equalsIgnoreCase(localName)) {
+        } else if ("class".equalsIgnoreCase(localName)) {
+            if (isEnum(attributes)) {
+                mIgnoreCurrentClass = true;
+                return;
+            }
+            mIgnoreCurrentClass = false;
             mCurrentClassName = getValue(attributes, "name");
             mDeprecated = isDeprecated(attributes);
-
-            ApiClass apiClass = new ApiClass(mCurrentClassName, mDeprecated);
+            ApiClass apiClass = new ApiClass(mCurrentClassName, mDeprecated, isAbstract(attributes));
             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();
@@ -72,6 +82,7 @@
             mDeprecated = isDeprecated(attributes);
             mCurrentMethodName = getValue(attributes, "name");
             mCurrentMethodReturnType = getValue(attributes, "return");
+            mCurrentMethodIsAbstract = isAbstract(attributes);
             mCurrentParameterTypes.clear();
         } else if ("parameter".equalsIgnoreCase(localName)) {
             mCurrentParameterTypes.add(getValue(attributes, "type"));
@@ -81,6 +92,10 @@
     @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...
@@ -92,6 +107,9 @@
             ApiClass apiClass = apiPackage.getClass(mCurrentClassName);
             apiClass.addConstructor(apiConstructor);
         }  else if ("method".equalsIgnoreCase(localName)) {
+            if (mCurrentMethodIsAbstract) { // do not add abstract method
+                return;
+            }
             ApiMethod apiMethod = new ApiMethod(mCurrentMethodName, mCurrentParameterTypes,
                     mCurrentMethodReturnType, mDeprecated);
             ApiPackage apiPackage = mApiCoverage.getPackage(mCurrentPackageName);
@@ -110,4 +128,12 @@
     private boolean isDeprecated(Attributes attributes) {
         return "deprecated".equals(attributes.getValue("deprecated"));
     }
+
+    private boolean isAbstract(Attributes attributes) {
+        return "true".equals(attributes.getValue("abstract"));
+    }
+
+    private boolean isEnum(Attributes attributes) {
+        return "java.lang.Enum".equals(attributes.getValue("extends"));
+    }
 }
\ No newline at end of file
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
index 94ccbb4..e76343e 100644
--- a/tools/cts-api-coverage/src/com/android/cts/apicoverage/XmlReport.java
+++ b/tools/cts-api-coverage/src/com/android/cts/apicoverage/XmlReport.java
@@ -54,12 +54,18 @@
         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 (pkg.getName().startsWith("android")
                     && pkg.getTotalMethods() > 0) {
+                int pkgTotal = pkg.getTotalMethods();
+                totalMethods += pkgTotal;
+                int pkgTotalCovered = pkg.getNumCoveredMethods();
+                totalCoveredMethods += pkgTotalCovered;
                 out.println("<package name=\"" + pkg.getName()
-                        + "\" numCovered=\"" + pkg.getNumCoveredMethods()
-                        + "\" numTotal=\"" + pkg.getTotalMethods()
+                        + "\" numCovered=\"" + pkgTotalCovered
+                        + "\" numTotal=\"" + pkgTotal
                         + "\" coveragePercentage=\""
                             + Math.round(pkg.getCoveragePercentage())
                         + "\">");
@@ -81,7 +87,12 @@
                             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 + "\" />");
                             }
@@ -94,7 +105,12 @@
                                     + "\" returnType=\"" + method.getReturnType()
                                     + "\" deprecated=\"" + method.isDeprecated()
                                     + "\" covered=\"" + method.isCovered() + "\">");
-
+                            if (method.isDeprecated()) {
+                                if (method.isCovered()) {
+                                    totalCoveredMethods -= 1;
+                                }
+                                totalMethods -= 1;
+                            }
                             for (String parameterType : method.getParameterTypes()) {
                                 out.println("<parameter type=\"" + parameterType + "\" />");
                             }
@@ -109,6 +125,10 @@
         }
 
         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/tools/cts-api-coverage/src/res/api-coverage.xsl b/tools/cts-api-coverage/src/res/api-coverage.xsl
index 95994e2..9cd4aeb 100644
--- a/tools/cts-api-coverage/src/res/api-coverage.xsl
+++ b/tools/cts-api-coverage/src/res/api-coverage.xsl
@@ -82,6 +82,10 @@
                 <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 APKs (<xsl:value-of select="count(api-coverage/debug/sources/apk)" />)
                 </div>