Add support for CtsVerifier to exclude certain tests matching any of the
specified features from being shown: "test_excluded_features". Tests
that are unnecessary for ATV devices are excluded on this CL.

This new meta attribute augments test_required_features's lack of the
ability to specify features negatively.  That is, there is at the moment
no way to prevent a test from being shown on a device having a certain
feature.  So, for example, even if Significant Motion Test may not be
useful on an Android TV device, there is no way to exclude the test from
being shown on the Crossfire's test list.

This CL lets you do exactly that by letting users be able to specify a
list of features such that, if any of them matches on the device, the
test will be excluded from being shown.  Note also that
"test_excluded_features" can support multiple features.  So if you do
not want a certain test to not be displayed on a television or a watch,
you can specify these two features separated by a colon.

The test filtering logic now becomes: For a test, if the device has any
of the features in test_excluded_features, then exclude it from being
shown; if not, see if the device has all required features in
test_required_features and, if false, exclude it from being shown.

Code tested under:
- Molly
- Fugu
- Sharp development TV
- Nexus 5
- Nexus 7

Bug: 17570665
Change-Id: Ie1e2cccaf2c7aaf708ec2bea5b99616f912d0e97
diff --git a/apps/CtsVerifier/AndroidManifest.xml b/apps/CtsVerifier/AndroidManifest.xml
index 0183fa2..77339bc 100644
--- a/apps/CtsVerifier/AndroidManifest.xml
+++ b/apps/CtsVerifier/AndroidManifest.xml
@@ -107,6 +107,8 @@
                 <category android:name="android.cts.intent.category.MANUAL_TEST" />
             </intent-filter>
             <meta-data android:name="test_category" android:value="@string/test_category_device_admin" />
+            <meta-data android:name="test_excluded_features"
+                       android:value="android.hardware.type.television:android.software.leanback" />
         </activity>
 
         <receiver android:name=".admin.TestDeviceAdminReceiver"
@@ -443,6 +445,8 @@
                 <category android:name="android.cts.intent.category.MANUAL_TEST" />
             </intent-filter>
             <meta-data android:name="test_category" android:value="@string/test_category_security" />
+            <meta-data android:name="test_excluded_features"
+                       android:value="android.hardware.type.television:android.software.leanback" />
         </activity>
 
         <activity android:name=".streamquality.StreamingVideoActivity"
@@ -883,6 +887,8 @@
                 <category android:name="android.cts.intent.category.MANUAL_TEST" />
             </intent-filter>
             <meta-data android:name="test_category" android:value="@string/test_category_location" />
+            <meta-data android:name="test_excluded_features"
+                       android:value="android.hardware.type.television:android.software.leanback" />
         </activity>
         <activity android:name=".location.LocationModeBatterySavingTestActivity"
                 android:label="@string/location_mode_battery_saving_test">
@@ -899,6 +905,8 @@
                 <category android:name="android.cts.intent.category.MANUAL_TEST" />
             </intent-filter>
             <meta-data android:name="test_category" android:value="@string/test_category_location" />
+            <meta-data android:name="test_excluded_features"
+                       android:value="android.hardware.type.television:android.software.leanback" />
         </activity>
 
         <activity android:name=".camera.formats.CameraFormatsActivity"
@@ -1010,6 +1018,8 @@
                 <category android:name="android.cts.intent.category.MANUAL_TEST" />
             </intent-filter>
             <meta-data android:name="test_category" android:value="@string/test_category_sensors" />
+            <meta-data android:name="test_excluded_features"
+                       android:value="android.hardware.type.television:android.software.leanback" />
         </activity>
 
         <activity android:name=".p2p.P2pTestListActivity"
@@ -1149,6 +1159,8 @@
                 <category android:name="android.cts.intent.category.MANUAL_TEST" />
             </intent-filter>
             <meta-data android:name="test_category" android:value="@string/test_category_sensors" />
+            <meta-data android:name="test_excluded_features"
+                       android:value="android.hardware.type.television:android.software.leanback" />
         </activity>
 
         <activity
@@ -1164,6 +1176,8 @@
             <meta-data
                 android:name="test_category"
                 android:value="@string/test_category_sensors" />
+            <meta-data android:name="test_excluded_features"
+                       android:value="android.hardware.type.television:android.software.leanback" />
         </activity>
 
         <receiver android:name=".widget.WidgetCtsProvider">
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/ManifestTestListAdapter.java b/apps/CtsVerifier/src/com/android/cts/verifier/ManifestTestListAdapter.java
index 1f19cbe..2f42e81 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/ManifestTestListAdapter.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/ManifestTestListAdapter.java
@@ -67,6 +67,14 @@
  *             <meta-data android:name="test_required_features" android:value="android.hardware.sensor.accelerometer" />
  *         </pre>
  *     </li>
+ *     <li>OPTIONAL: Add a meta data attribute to indicate features such that, if any present, the
+ *         test gets excluded from being shown. If the device has any of the excluded features then
+ *         the test will not appear in the test list. Use a colon (:) to specify multiple features
+ *         to exclude for the test. Note that the colon means "or" in this case.
+ *         <pre>
+ *             <meta-data android:name="test_excluded_features" android:value="android.hardware.type.television" />
+ *         </pre>
+ *     </li>
  *
  * </ol>
  */
@@ -78,6 +86,8 @@
 
     private static final String TEST_REQUIRED_FEATURES_META_DATA = "test_required_features";
 
+    private static final String TEST_EXCLUDED_FEATURES_META_DATA = "test_excluded_features";
+
     private Context mContext;
 
     private String mTestParent;
@@ -152,7 +162,9 @@
             String testName = info.activityInfo.name;
             Intent intent = getActivityIntent(info.activityInfo);
             String[] requiredFeatures = getRequiredFeatures(info.activityInfo.metaData);
-            TestListItem item = TestListItem.newTest(title, testName, intent, requiredFeatures);
+            String[] excludedFeatures = getExcludedFeatures(info.activityInfo.metaData);
+            TestListItem item = TestListItem.newTest(title, testName, intent,
+                                                     requiredFeatures, excludedFeatures);
 
             String testCategory = getTestCategory(mContext, info.activityInfo.metaData);
             addTestToCategory(testsByCategory, testCategory, item);
@@ -190,6 +202,19 @@
         }
     }
 
+    static String[] getExcludedFeatures(Bundle metaData) {
+        if (metaData == null) {
+            return null;
+        } else {
+            String value = metaData.getString(TEST_EXCLUDED_FEATURES_META_DATA);
+            if (value == null) {
+                return null;
+            } else {
+                return value.split(":");
+            }
+        }
+    }
+
     static String getTitle(Context context, ActivityInfo activityInfo) {
         if (activityInfo.labelRes != 0) {
             return context.getString(activityInfo.labelRes);
@@ -216,22 +241,39 @@
         tests.add(item);
     }
 
-    List<TestListItem> filterTests(List<TestListItem> tests) {
-        List<TestListItem> filteredTests = new ArrayList<TestListItem>(tests);
-        PackageManager packageManager = mContext.getPackageManager();
-        Iterator<TestListItem> iterator = filteredTests.iterator();
-        while (iterator.hasNext()) {
-            TestListItem item = iterator.next();
-            String[] requiredFeatures = item.requiredFeatures;
-            if (requiredFeatures != null) {
-                for (int i = 0; i < requiredFeatures.length; i++) {
-                    if (!packageManager.hasSystemFeature(requiredFeatures[i])) {
-                        iterator.remove();
-                        break;
-                    }
+    private boolean hasAnyFeature(String[] features) {
+        if (features != null) {
+            PackageManager packageManager = mContext.getPackageManager();
+            for (String feature : features) {
+                if (packageManager.hasSystemFeature(feature)) {
+                    return true;
                 }
             }
         }
+        return false;
+    }
+
+    private boolean hasAllFeatures(String[] features) {
+        if (features != null) {
+            PackageManager packageManager = mContext.getPackageManager();
+            for (String feature : features) {
+                if (!packageManager.hasSystemFeature(feature)) {
+                    return false;
+                }
+            }
+        }
+        return true;
+    }
+
+    List<TestListItem> filterTests(List<TestListItem> tests) {
+        List<TestListItem> filteredTests = new ArrayList<TestListItem>();
+        for (TestListItem test : tests) {
+            String[] excludedFeatures = test.excludedFeatures;
+            String[] requiredFeatures = test.requiredFeatures;
+            if (!hasAnyFeature(excludedFeatures) && hasAllFeatures(requiredFeatures)) {
+                filteredTests.add(test);
+            }
+        }
         return filteredTests;
     }
 }
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/TestListAdapter.java b/apps/CtsVerifier/src/com/android/cts/verifier/TestListAdapter.java
index 2cc79fb..0d9985c 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/TestListAdapter.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/TestListAdapter.java
@@ -83,14 +83,29 @@
         /** Features necessary to run this test. */
         final String[] requiredFeatures;
 
+        /** Features such that, if any present, the test gets excluded from being shown. */
+        final String[] excludedFeatures;
+
+        public static TestListItem newTest(Context context, int titleResId, String testName,
+                Intent intent, String[] requiredFeatures, String[] excludedFeatures) {
+            return newTest(context.getString(titleResId), testName, intent,
+                           requiredFeatures, excludedFeatures);
+        }
+
         public static TestListItem newTest(Context context, int titleResId, String testName,
                 Intent intent, String[] requiredFeatures) {
-            return newTest(context.getString(titleResId), testName, intent, requiredFeatures);
+            return newTest(context.getString(titleResId), testName, intent,
+                           requiredFeatures, null);
+        }
+
+        public static TestListItem newTest(String title, String testName, Intent intent,
+                String[] requiredFeatures, String[] excludedFeatures) {
+            return new TestListItem(title, testName, intent, requiredFeatures, excludedFeatures);
         }
 
         public static TestListItem newTest(String title, String testName, Intent intent,
                 String[] requiredFeatures) {
-            return new TestListItem(title, testName, intent, requiredFeatures);
+            return new TestListItem(title, testName, intent, requiredFeatures, null);
         }
 
         public static TestListItem newCategory(Context context, int titleResId) {
@@ -98,15 +113,16 @@
         }
 
         public static TestListItem newCategory(String title) {
-            return new TestListItem(title, null, null, null);
+            return new TestListItem(title, null, null, null, null);
         }
 
         private TestListItem(String title, String testName, Intent intent,
-                String[] requiredFeatures) {
+                String[] requiredFeatures, String[] excludedFeatures) {
             this.title = title;
             this.testName = testName;
             this.intent = intent;
             this.requiredFeatures = requiredFeatures;
+            this.excludedFeatures = excludedFeatures;
         }
 
         boolean isTest() {
@@ -366,4 +382,4 @@
 
         }
     }
-}
\ No newline at end of file
+}