ArrayTestListAdapter Class

I wanted to do what StreamingVideoActivity was doing with
TestListAdapter where it customized what rows were being
returned by overriding getRows.

So I moved code in TestListAdapter that scanned the
AndroidManifest to its own class called ManifestTestListAdapter.

I then extracted ArrayTestListAdapter from StreamingVideoActivity,
so you can call add(TestListItem) to build up a list of tests.

Change-Id: If3d2bacc52e19f326467a36ab55aa52c49f765cc
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/AbstractTestListActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/AbstractTestListActivity.java
index bc1931b..9b53bfd 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/AbstractTestListActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/AbstractTestListActivity.java
@@ -29,8 +29,8 @@
 
     protected TestListAdapter mAdapter;
 
-    protected void prepareTestListAdapter(String parent) {
-        mAdapter = new TestListAdapter(this, parent);
+    protected void setTestListAdapter(TestListAdapter adapter) {
+        mAdapter = adapter;
         setListAdapter(mAdapter);
         mAdapter.loadTestResults();
     }
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/ArrayTestListAdapter.java b/apps/CtsVerifier/src/com/android/cts/verifier/ArrayTestListAdapter.java
new file mode 100644
index 0000000..29440f0
--- /dev/null
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/ArrayTestListAdapter.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2011 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.verifier;
+
+import android.content.Context;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * {@link TestListAdapter} that works like {@link android.widget.ArrayAdapter}
+ * where items can be added by calling {@link #add(TestListItem)} repeatedly.
+ */
+public class ArrayTestListAdapter extends TestListAdapter {
+
+    private final List<TestListItem> mRows = new ArrayList<TestListItem>();
+
+    public ArrayTestListAdapter(Context context) {
+        super(context);
+    }
+
+    public void add(TestListItem item) {
+        mRows.add(item);
+        notifyDataSetChanged();
+    }
+
+    @Override
+    protected List<TestListItem> getRows() {
+        return mRows;
+    }
+}
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/ManifestTestListAdapter.java b/apps/CtsVerifier/src/com/android/cts/verifier/ManifestTestListAdapter.java
new file mode 100644
index 0000000..fdea120
--- /dev/null
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/ManifestTestListAdapter.java
@@ -0,0 +1,192 @@
+/*
+ * Copyright (C) 2011 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.verifier;
+
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.ActivityInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.os.Bundle;
+import android.widget.ListView;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * {@link TestListAdapter} that populates the {@link TestListActivity}'s {@link ListView} by
+ * reading data from the CTS Verifier's AndroidManifest.xml.
+ * <p>
+ * Making a new test activity to appear in the list requires the following steps:
+ *
+ * <ol>
+ *     <li>REQUIRED: Add an activity to the AndroidManifest.xml with an intent filter with a
+ *         main action and the MANUAL_TEST category.
+ *         <pre>
+ *             <intent-filter>
+ *                <action android:name="android.intent.action.MAIN" />
+ *                <category android:name="android.cts.intent.category.MANUAL_TEST" />
+ *             </intent-filter>
+ *         </pre>
+ *     </li>
+ *     <li>OPTIONAL: Add a meta data attribute to indicate what category of tests the activity
+ *         should belong to. If you don't add this attribute, your test will show up in the
+ *         "Other" tests category.
+ *         <pre>
+ *             <meta-data android:name="test_category" android:value="@string/test_category_security" />
+ *         </pre>
+ *     </li>
+ *     <li>OPTIONAL: Add a meta data attribute to indicate whether this test has a parent test.
+ *         <pre>
+ *             <meta-data android:name="test_parent" android:value="com.android.cts.verifier.bluetooth.BluetoothTestActivity" />
+ *         </pre>
+ *     </li>
+ * </ol>
+ */
+public class ManifestTestListAdapter extends TestListAdapter {
+
+    private static final String TEST_CATEGORY_META_DATA = "test_category";
+
+    private static final String TEST_PARENT_META_DATA = "test_parent";
+
+    private Context mContext;
+
+    private String mTestParent;
+
+    public ManifestTestListAdapter(Context context, String testParent) {
+        super(context);
+        mContext = context;
+        mTestParent = testParent;
+    }
+
+    @Override
+    protected List<TestListItem> getRows() {
+
+        /*
+         * 1. Get all the tests belonging to the test parent.
+         * 2. Get all the tests keyed by their category.
+         * 3. Flatten the tests and categories into one giant list for the list view.
+         */
+
+        List<ResolveInfo> infos = getResolveInfosForParent();
+        Map<String, List<TestListItem>> testsByCategory = getTestsByCategory(infos);
+
+        List<String> testCategories = new ArrayList<String>(testsByCategory.keySet());
+        Collections.sort(testCategories);
+
+        List<TestListItem> allRows = new ArrayList<TestListItem>();
+        for (String testCategory : testCategories) {
+            allRows.add(TestListItem.newCategory(testCategory));
+
+            List<TestListItem> tests = testsByCategory.get(testCategory);
+            Collections.sort(tests, new Comparator<TestListItem>() {
+                @Override
+                public int compare(TestListItem item, TestListItem otherItem) {
+                    return item.title.compareTo(otherItem.title);
+                }
+            });
+            allRows.addAll(tests);
+        }
+        return allRows;
+    }
+
+    List<ResolveInfo> getResolveInfosForParent() {
+        Intent mainIntent = new Intent(Intent.ACTION_MAIN);
+        mainIntent.addCategory(CATEGORY_MANUAL_TEST);
+
+        PackageManager packageManager = mContext.getPackageManager();
+        List<ResolveInfo> list = packageManager.queryIntentActivities(mainIntent,
+                PackageManager.GET_ACTIVITIES | PackageManager.GET_META_DATA);
+        int size = list.size();
+
+        List<ResolveInfo> matchingList = new ArrayList<ResolveInfo>();
+        for (int i = 0; i < size; i++) {
+            ResolveInfo info = list.get(i);
+            String parent = getTestParent(mContext, info.activityInfo.metaData);
+            if ((mTestParent == null && parent == null)
+                    || (mTestParent != null && mTestParent.equals(parent))) {
+                matchingList.add(info);
+            }
+        }
+        return matchingList;
+    }
+
+    Map<String, List<TestListItem>> getTestsByCategory(List<ResolveInfo> list) {
+        Map<String, List<TestListItem>> testsByCategory =
+                new HashMap<String, List<TestListItem>>();
+
+        int size = list.size();
+        for (int i = 0; i < size; i++) {
+            ResolveInfo info = list.get(i);
+            String title = getTitle(mContext, info.activityInfo);
+            String testName = info.activityInfo.name;
+            Intent intent = getActivityIntent(info.activityInfo);
+            TestListItem item = TestListItem.newTest(title, testName, intent);
+
+            String testCategory = getTestCategory(mContext, info.activityInfo.metaData);
+            addTestToCategory(testsByCategory, testCategory, item);
+        }
+
+        return testsByCategory;
+    }
+
+    static String getTestCategory(Context context, Bundle metaData) {
+        String testCategory = null;
+        if (metaData != null) {
+            testCategory = metaData.getString(TEST_CATEGORY_META_DATA);
+        }
+        if (testCategory != null) {
+            return testCategory;
+        } else {
+            return context.getString(R.string.test_category_other);
+        }
+    }
+
+    static String getTestParent(Context context, Bundle metaData) {
+        return metaData != null ? metaData.getString(TEST_PARENT_META_DATA) : null;
+    }
+
+    static String getTitle(Context context, ActivityInfo activityInfo) {
+        if (activityInfo.labelRes != 0) {
+            return context.getString(activityInfo.labelRes);
+        } else {
+            return activityInfo.name;
+        }
+    }
+
+    static Intent getActivityIntent(ActivityInfo activityInfo) {
+        Intent intent = new Intent();
+        intent.setClassName(activityInfo.packageName, activityInfo.name);
+        return intent;
+    }
+
+    static void addTestToCategory(Map<String, List<TestListItem>> testsByCategory,
+            String testCategory, TestListItem item) {
+        List<TestListItem> tests;
+        if (testsByCategory.containsKey(testCategory)) {
+            tests = testsByCategory.get(testCategory);
+        } else {
+            tests = new ArrayList<TestListItem>();
+        }
+        testsByCategory.put(testCategory, tests);
+        tests.add(item);
+    }
+}
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/TestListActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/TestListActivity.java
index d407a63..f233312 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/TestListActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/TestListActivity.java
@@ -35,7 +35,7 @@
     @Override
     protected void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
-        prepareTestListAdapter(null);
+        setTestListAdapter(new ManifestTestListAdapter(this, null));
     }
 
     @Override
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/TestListAdapter.java b/apps/CtsVerifier/src/com/android/cts/verifier/TestListAdapter.java
index 79a8b48..36b1d6a 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/TestListAdapter.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/TestListAdapter.java
@@ -19,13 +19,9 @@
 import android.content.ContentResolver;
 import android.content.Context;
 import android.content.Intent;
-import android.content.pm.ActivityInfo;
-import android.content.pm.PackageManager;
-import android.content.pm.ResolveInfo;
 import android.database.ContentObserver;
 import android.database.Cursor;
 import android.os.AsyncTask;
-import android.os.Bundle;
 import android.os.Handler;
 import android.view.LayoutInflater;
 import android.view.View;
@@ -35,49 +31,21 @@
 import android.widget.TextView;
 
 import java.util.ArrayList;
-import java.util.Collections;
-import java.util.Comparator;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
 
 /**
- * {@link BaseAdapter} that populates the {@link TestListActivity}'s {@link ListView}.
- * Making a new test activity to appear in the list requires the following steps:
- *
- * <ol>
- *     <li>REQUIRED: Add an activity to the AndroidManifest.xml with an intent filter with a
- *         main action and the MANUAL_TEST category.
- *         <pre>
- *             <intent-filter>
- *                <action android:name="android.intent.action.MAIN" />
- *                <category android:name="android.cts.intent.category.MANUAL_TEST" />
- *             </intent-filter>
- *         </pre>
- *     </li>
- *     <li>OPTIONAL: Add a meta data attribute to indicate what category of tests the activity
- *         should belong to. If you don't add this attribute, your test will show up in the
- *         "Other" tests category.
- *         <pre>
- *             <meta-data android:name="test_category" android:value="@string/test_category_security" />
- *         </pre>
- *     </li>
- *     <li>OPTIONAL: Add a meta data attribute to indicate whether this test has a parent test.
- *         <pre>
- *             <meta-data android:name="test_parent" android:value="com.android.cts.verifier.bluetooth.BluetoothTestActivity" />
- *         </pre>
- *     </li>
- * </ol>
+ * {@link BaseAdapter} that handles loading, refreshing, and setting test
+ * results. What tests are shown can be customized by overriding
+ * {@link #getRows()}. See {@link ArrayTestListAdapter} and
+ * {@link ManifestTestListAdapter} for examples.
  */
-public class TestListAdapter extends BaseAdapter {
+public abstract class TestListAdapter extends BaseAdapter {
 
     /** Activities implementing {@link Intent#ACTION_MAIN} and this will appear in the list. */
     public static final String CATEGORY_MANUAL_TEST = "android.cts.intent.category.MANUAL_TEST";
 
-    private static final String TEST_CATEGORY_META_DATA = "test_category";
-
-    private static final String TEST_PARENT_META_DATA = "test_parent";
-
     /** View type for a category of tests like "Sensors" or "Features" */
     private static final int CATEGORY_HEADER_VIEW_TYPE = 0;
 
@@ -89,10 +57,8 @@
 
     private final Context mContext;
 
-    private final String mTestParent;
-
     /** Immutable data of tests like the test's title and launch intent. */
-    private final List<TestListItem> mRows = new ArrayList<TestListAdapter.TestListItem>();
+    private final List<TestListItem> mRows = new ArrayList<TestListItem>();
 
     /** Mutable test results that will change as each test activity finishes. */
     private final Map<String, Integer> mTestResults = new HashMap<String, Integer>();
@@ -111,9 +77,6 @@
         /** Intent used to launch the activity from the list. Null for categories. */
         final Intent intent;
 
-        /** Tests within this test. For instance, the Bluetooth test contains more tests. */
-        final List<TestListItem> subItems = new ArrayList<TestListItem>();
-
         public static TestListItem newTest(String title, String testName, Intent intent) {
             return new TestListItem(title, testName, intent);
         }
@@ -135,15 +98,10 @@
         boolean isTest() {
             return intent != null;
         }
-
-        void addTestListItem(TestListItem item) {
-            subItems.add(item);
-        }
     }
 
-    public TestListAdapter(Context context, String testParent) {
+    public TestListAdapter(Context context) {
         this.mContext = context;
-        this.mTestParent = testParent;
         this.mLayoutInflater =
                 (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
 
@@ -193,117 +151,7 @@
         }
     }
 
-    protected List<TestListItem> getRows() {
-
-        /*
-         * 1. Get all the tests belonging to the test parent.
-         * 2. Get all the tests keyed by their category.
-         * 3. Flatten the tests and categories into one giant list for the list view.
-         */
-
-        List<ResolveInfo> infos = getResolveInfosForParent();
-        Map<String, List<TestListItem>> testsByCategory = getTestsByCategory(infos);
-
-        List<String> testCategories = new ArrayList<String>(testsByCategory.keySet());
-        Collections.sort(testCategories);
-
-        List<TestListItem> allRows = new ArrayList<TestListItem>();
-        for (String testCategory : testCategories) {
-            allRows.add(TestListItem.newCategory(testCategory));
-
-            List<TestListItem> tests = testsByCategory.get(testCategory);
-            Collections.sort(tests, new Comparator<TestListItem>() {
-                @Override
-                public int compare(TestListItem item, TestListItem otherItem) {
-                    return item.title.compareTo(otherItem.title);
-                }
-            });
-            allRows.addAll(tests);
-        }
-        return allRows;
-    }
-
-    List<ResolveInfo> getResolveInfosForParent() {
-        Intent mainIntent = new Intent(Intent.ACTION_MAIN);
-        mainIntent.addCategory(CATEGORY_MANUAL_TEST);
-
-        PackageManager packageManager = mContext.getPackageManager();
-        List<ResolveInfo> list = packageManager.queryIntentActivities(mainIntent,
-                PackageManager.GET_ACTIVITIES | PackageManager.GET_META_DATA);
-        int size = list.size();
-
-        List<ResolveInfo> matchingList = new ArrayList<ResolveInfo>();
-        for (int i = 0; i < size; i++) {
-            ResolveInfo info = list.get(i);
-            String parent = getTestParent(mContext, info.activityInfo.metaData);
-            if ((mTestParent == null && parent == null)
-                    || (mTestParent != null && mTestParent.equals(parent))) {
-                matchingList.add(info);
-            }
-        }
-        return matchingList;
-    }
-
-    Map<String, List<TestListItem>> getTestsByCategory(List<ResolveInfo> list) {
-        Map<String, List<TestListItem>> testsByCategory =
-                new HashMap<String, List<TestListItem>>();
-
-        int size = list.size();
-        for (int i = 0; i < size; i++) {
-            ResolveInfo info = list.get(i);
-            String title = getTitle(mContext, info.activityInfo);
-            String testName = info.activityInfo.name;
-            Intent intent = getActivityIntent(info.activityInfo);
-            TestListItem item = TestListItem.newTest(title, testName, intent);
-
-            String testCategory = getTestCategory(mContext, info.activityInfo.metaData);
-            addTestToCategory(testsByCategory, testCategory, item);
-        }
-
-        return testsByCategory;
-    }
-
-    static String getTestCategory(Context context, Bundle metaData) {
-        String testCategory = null;
-        if (metaData != null) {
-            testCategory = metaData.getString(TEST_CATEGORY_META_DATA);
-        }
-        if (testCategory != null) {
-            return testCategory;
-        } else {
-            return context.getString(R.string.test_category_other);
-        }
-    }
-
-    static String getTestParent(Context context, Bundle metaData) {
-        return metaData != null ? metaData.getString(TEST_PARENT_META_DATA) : null;
-    }
-
-    static String getTitle(Context context, ActivityInfo activityInfo) {
-        if (activityInfo.labelRes != 0) {
-            return context.getString(activityInfo.labelRes);
-        } else {
-            return activityInfo.name;
-        }
-    }
-
-    static Intent getActivityIntent(ActivityInfo activityInfo) {
-        Intent intent = new Intent();
-        intent.setClassName(activityInfo.packageName, activityInfo.name);
-        return intent;
-    }
-
-    static void addTestToCategory(Map<String, List<TestListItem>> testsByCategory,
-            String testCategory, TestListItem item) {
-        List<TestListItem> tests;
-        if (testsByCategory.containsKey(testCategory)) {
-            tests = testsByCategory.get(testCategory);
-        } else {
-            tests = new ArrayList<TestListItem>();
-        }
-        testsByCategory.put(testCategory, tests);
-        tests.add(item);
-    }
+    protected abstract List<TestListItem> getRows();
 
     Map<String, Integer> getTestResults() {
         Map<String, Integer> results = new HashMap<String, Integer>();
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/bluetooth/BluetoothTestActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/bluetooth/BluetoothTestActivity.java
index 93d2804..4ddfa51 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/bluetooth/BluetoothTestActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/bluetooth/BluetoothTestActivity.java
@@ -16,6 +16,7 @@
 
 package com.android.cts.verifier.bluetooth;
 
+import com.android.cts.verifier.ManifestTestListAdapter;
 import com.android.cts.verifier.PassFailButtons;
 import com.android.cts.verifier.R;
 
@@ -33,7 +34,7 @@
         setPassFailButtonClickListeners();
         setInfoResources(R.string.bluetooth_test, R.string.bluetooth_test_info, -1);
 
-        prepareTestListAdapter(getClass().getName());
+        setTestListAdapter(new ManifestTestListAdapter(this, getClass().getName()));
 
         if (BluetoothAdapter.getDefaultAdapter() == null) {
             showNoBluetoothDialog();
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/streamquality/StreamingVideoActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/streamquality/StreamingVideoActivity.java
index 8a01423..6335648 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/streamquality/StreamingVideoActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/streamquality/StreamingVideoActivity.java
@@ -16,6 +16,7 @@
 
 package com.android.cts.verifier.streamquality;
 
+import com.android.cts.verifier.ArrayTestListAdapter;
 import com.android.cts.verifier.PassFailButtons;
 import com.android.cts.verifier.R;
 import com.android.cts.verifier.TestListAdapter;
@@ -26,8 +27,6 @@
 import android.os.Bundle;
 
 import java.io.Serializable;
-import java.util.ArrayList;
-import java.util.List;
 
 /**
  * Tests for verifying the quality of streaming videos.  Plays streams of different formats over
@@ -130,42 +129,37 @@
         setPassFailButtonClickListeners();
         setInfoResources(R.string.streaming_video, R.string.streaming_video_info, -1);
 
-        prepareTestListAdapter(getTestId());
         getPassButton().setEnabled(false);
+        setTestListAdapter(getStreamAdapter());
     }
 
-    @Override
-    protected void prepareTestListAdapter(String parent) {
-        mAdapter = new TestListAdapter(this, parent) {
-            @Override
-            protected List<TestListItem> getRows() {
-                List<TestListItem> streams = new ArrayList<TestListItem>();
-                // TODO: Enable RTSP streams
-                /*
-                streams.add(TestListItem.newCategory("RTSP"));
-                for (Stream stream : RTSP_STREAMS) {
-                    addStreamToTests(streams, stream);
-                }
-                */
+    private TestListAdapter getStreamAdapter() {
+        ArrayTestListAdapter adapter = new ArrayTestListAdapter(this);
 
-                streams.add(TestListItem.newCategory("HTTP Progressive"));
-                for (Stream stream : HTTP_STREAMS) {
-                    addStreamToTests(streams, stream);
-                }
-                return streams;
-            }
-        };
-        setListAdapter(mAdapter);
-        mAdapter.registerDataSetObserver(new DataSetObserver() {
+        // TODO: Enable RTSP streams
+        /*
+        adapter.add(TestListItem.newCategory("RTSP"));
+        for (Stream stream : RTSP_STREAMS) {
+            addStreamToTests(streams, stream);
+        }
+        */
+
+        adapter.add(TestListItem.newCategory("HTTP Progressive"));
+        for (Stream stream : HTTP_STREAMS) {
+            addStreamToTests(adapter, stream);
+        }
+
+        adapter.registerDataSetObserver(new DataSetObserver() {
             @Override
             public void onChanged() {
                 updatePassButton();
             }
         });
-        mAdapter.loadTestResults();
+
+        return adapter;
     }
 
-    private void addStreamToTests(List<TestListItem> streams, Stream stream) {
+    private void addStreamToTests(ArrayTestListAdapter streams, Stream stream) {
         Intent i = new Intent(StreamingVideoActivity.this, PlayVideoActivity.class);
         i.putExtra(PlayVideoActivity.EXTRA_STREAM, stream);
         streams.add(TestListItem.newTest(stream.name, PlayVideoActivity.getTestId(stream.code), i));