CTS coverage tool improvements

* remove default package prefix filter on 'android' package
* enabled multiple package prefix filters
* improved sorting logic:
  * first compare by coverage percentage segments (0, 50%, 80%)
  * then compare by member sizes for classes/packages in
    in descending order

Bug: 19272826
Change-Id: I8416a08d152e2028daf5cb945388be590d3feab8
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 2a62aa0..31e1f8d 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
@@ -121,4 +121,9 @@
             return (float) getNumCoveredMethods() / getTotalMethods() * 100;
         }
     }
+
+    @Override
+    public int getMemberSize() {
+        return getTotalMethods();
+    }
 }
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 c83256c..e0bf73f 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
@@ -72,6 +72,11 @@
         return (float) getNumCoveredMethods() / getTotalMethods() * 100;
     }
 
+    @Override
+    public int getMemberSize() {
+        return getTotalMethods();
+    }
+
     public void removeEmptyAbstractClasses() {
         Iterator<Entry<String, ApiClass>> it = mApiClassMap.entrySet().iterator();
         while (it.hasNext()) {
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 d596cba..05cb4e1 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
@@ -71,8 +71,7 @@
         int format = FORMAT_TXT;
         String dexDeps = "dexDeps";
         String apiXmlPath = "";
-        // By default only care about packages starting with "android"
-        String packageFilter = "android";
+        PackageFilter packageFilter = new PackageFilter();
         String reportTitle = "CTS API Coverage";
 
         for (int i = 0; i < args.length; i++) {
@@ -95,7 +94,7 @@
                 } else if ("-a".equals(args[i])) {
                     apiXmlPath = getExpectedArg(args, ++i);
                 } else if ("-p".equals(args[i])) {
-                    packageFilter = getExpectedArg(args, ++i);
+                    packageFilter.addPrefixToFilter(getExpectedArg(args, ++i));
                 } else if ("-t".equals(args[i])) {
                     reportTitle = getExpectedArg(args, ++i);
                 } else {
@@ -188,7 +187,7 @@
     }
 
     private static void outputCoverageReport(ApiCoverage apiCoverage, List<File> testApks,
-            File outputFile, int format, String packageFilter, String reportTitle)
+            File outputFile, int format, PackageFilter packageFilter, String reportTitle)
                 throws IOException, TransformerException, InterruptedException {
 
         OutputStream out = outputFile != null
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
index 3b369bb..f8d8054 100644
--- a/tools/cts-api-coverage/src/com/android/cts/apicoverage/HasCoverage.java
+++ b/tools/cts-api-coverage/src/com/android/cts/apicoverage/HasCoverage.java
@@ -20,13 +20,33 @@
 
 interface HasCoverage {
     float getCoveragePercentage();
+    int getMemberSize();
     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());
+        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/tools/cts-api-coverage/src/com/android/cts/apicoverage/HtmlReport.java b/tools/cts-api-coverage/src/com/android/cts/apicoverage/HtmlReport.java
index dfe609d..0e6b54a 100644
--- a/tools/cts-api-coverage/src/com/android/cts/apicoverage/HtmlReport.java
+++ b/tools/cts-api-coverage/src/com/android/cts/apicoverage/HtmlReport.java
@@ -37,7 +37,7 @@
 class HtmlReport {
 
     public static void printHtmlReport(final List<File> testApks, final ApiCoverage apiCoverage,
-            final String packageFilter, final String reportTitle, final OutputStream out)
+            final PackageFilter packageFilter, final String reportTitle, final OutputStream out)
                 throws IOException, TransformerException {
         final PipedOutputStream xmlOut = new PipedOutputStream();
         final PipedInputStream xmlIn = new PipedInputStream(xmlOut);
diff --git a/tools/cts-api-coverage/src/com/android/cts/apicoverage/PackageFilter.java b/tools/cts-api-coverage/src/com/android/cts/apicoverage/PackageFilter.java
new file mode 100644
index 0000000..b196d8f
--- /dev/null
+++ b/tools/cts-api-coverage/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/tools/cts-api-coverage/src/com/android/cts/apicoverage/TextReport.java b/tools/cts-api-coverage/src/com/android/cts/apicoverage/TextReport.java
index 23b44c3..e3e2e7c 100644
--- a/tools/cts-api-coverage/src/com/android/cts/apicoverage/TextReport.java
+++ b/tools/cts-api-coverage/src/com/android/cts/apicoverage/TextReport.java
@@ -27,7 +27,8 @@
  */
 class TextReport {
 
-    public static void printTextReport(ApiCoverage api, String packageFilter, OutputStream outputStream) {
+    public static void printTextReport(ApiCoverage api, PackageFilter packageFilter,
+            OutputStream outputStream) {
         PrintStream out = new PrintStream(outputStream);
 
         CoverageComparator comparator = new CoverageComparator();
@@ -35,8 +36,7 @@
         Collections.sort(packages, comparator);
 
         for (ApiPackage apiPackage : packages) {
-            if (apiPackage.getName().startsWith(packageFilter)
-                    && apiPackage.getTotalMethods() > 0) {
+            if (packageFilter.accept(apiPackage.getName()) && apiPackage.getTotalMethods() > 0) {
                 printPackage(apiPackage, out);
             }
         }
@@ -45,7 +45,7 @@
         out.println();
 
         for (ApiPackage apiPackage : packages) {
-            if (apiPackage.getName().startsWith(packageFilter)) {
+            if (packageFilter.accept(apiPackage.getName())) {
                 printPackage(apiPackage, out);
 
                 List<ApiClass> classes = new ArrayList<ApiClass>(apiPackage.getClasses());
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 df10fa4..570b316 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
@@ -32,7 +32,7 @@
 class XmlReport {
 
     public static void printXmlReport(List<File> testApks, ApiCoverage apiCoverage,
-            String packageFilter, String reportTitle, OutputStream outputStream) {
+            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\"?>");
@@ -57,8 +57,7 @@
         int totalMethods = 0;
         int totalCoveredMethods = 0;
         for (ApiPackage pkg : packages) {
-            if (pkg.getName().startsWith(packageFilter)
-                   && pkg.getTotalMethods() > 0) {
+            if (packageFilter.accept(pkg.getName()) && pkg.getTotalMethods() > 0) {
                 int pkgTotal = pkg.getTotalMethods();
                 totalMethods += pkgTotal;
                 int pkgTotalCovered = pkg.getNumCoveredMethods();
diff --git a/tools/cts-api-coverage/src/res/api-coverage.xsl b/tools/cts-api-coverage/src/res/api-coverage.xsl
index 1ac3402..b11a8c4 100644
--- a/tools/cts-api-coverage/src/res/api-coverage.xsl
+++ b/tools/cts-api-coverage/src/res/api-coverage.xsl
@@ -21,6 +21,7 @@
     <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); 
@@ -87,7 +88,7 @@
                 &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)" />)
+                    Source Modules (<xsl:value-of select="count(api-coverage/debug/sources/apk)" />)
                 </div>
                 <div id="sourceApks" style="display: none">
                     <ul>