Merge "CTS Verifier Sub Tests Implementation" into gingerbread
diff --git a/apps/CtsVerifier/AndroidManifest.xml b/apps/CtsVerifier/AndroidManifest.xml
index 9bdb37a..fddbcae 100644
--- a/apps/CtsVerifier/AndroidManifest.xml
+++ b/apps/CtsVerifier/AndroidManifest.xml
@@ -56,15 +56,63 @@
         
         <activity android:name=".bluetooth.BluetoothToggleActivity"
                 android:label="@string/bt_toggle_bluetooth"
-                android:configChanges="keyboardHidden|orientation" />
+                android:configChanges="keyboardHidden|orientation">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.cts.intent.category.MANUAL_TEST" />
+            </intent-filter>
+            <meta-data android:name="test_category" android:value="@string/bt_control" />
+            <meta-data android:name="test_parent" android:value="com.android.cts.verifier.bluetooth.BluetoothTestActivity" />
+        </activity>
 
+        <activity android:name=".bluetooth.SecureServerActivity"
+                android:label="@string/bt_secure_server"
+                android:configChanges="keyboardHidden|orientation">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.cts.intent.category.MANUAL_TEST" />
+            </intent-filter>
+            <meta-data android:name="test_category" android:value="@string/bt_device_communication" />
+            <meta-data android:name="test_parent" android:value="com.android.cts.verifier.bluetooth.BluetoothTestActivity" />
+        </activity>
+        
+        <activity android:name=".bluetooth.InsecureServerActivity"
+                android:label="@string/bt_insecure_server"
+                android:configChanges="keyboardHidden|orientation">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.cts.intent.category.MANUAL_TEST" />
+            </intent-filter>
+            <meta-data android:name="test_category" android:value="@string/bt_device_communication" />
+            <meta-data android:name="test_parent" android:value="com.android.cts.verifier.bluetooth.BluetoothTestActivity" />
+        </activity>
+
+        <activity android:name=".bluetooth.SecureClientActivity"
+                android:label="@string/bt_secure_client"
+                android:configChanges="keyboardHidden|orientation">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.cts.intent.category.MANUAL_TEST" />
+            </intent-filter>
+            <meta-data android:name="test_category" android:value="@string/bt_device_communication" />
+            <meta-data android:name="test_parent" android:value="com.android.cts.verifier.bluetooth.BluetoothTestActivity" />
+        </activity>
+        
+        <activity android:name=".bluetooth.InsecureClientActivity"
+                android:label="@string/bt_insecure_client"
+                android:configChanges="keyboardHidden|orientation">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.cts.intent.category.MANUAL_TEST" />
+            </intent-filter>
+            <meta-data android:name="test_category" android:value="@string/bt_device_communication" />
+            <meta-data android:name="test_parent" android:value="com.android.cts.verifier.bluetooth.BluetoothTestActivity" />
+        </activity>       
+        
         <activity android:name=".bluetooth.DevicePickerActivity"
                 android:label="@string/bt_device_picker"
                 android:configChanges="keyboardHidden|orientation" />
 
-        <activity android:name=".bluetooth.MessageTestActivity"
-                android:configChanges="keyboardHidden|orientation" />
-
         <activity android:name=".suid.SuidFilesActivity" 
                 android:label="@string/suid_files"
                 android:configChanges="keyboardHidden|orientation">
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/TestListActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/TestListActivity.java
index c563e13..244caab 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/TestListActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/TestListActivity.java
@@ -19,12 +19,8 @@
 import com.android.cts.verifier.TestListAdapter.TestListItem;
 
 import android.app.ListActivity;
-import android.content.ContentResolver;
-import android.content.ContentValues;
 import android.content.Intent;
-import android.database.ContentObserver;
 import android.os.Bundle;
-import android.os.Handler;
 import android.text.ClipboardManager;
 import android.view.Menu;
 import android.view.MenuInflater;
@@ -37,17 +33,14 @@
 public class TestListActivity extends ListActivity {
 
     private static final int LAUNCH_TEST_REQUEST_CODE = 1;
+    private TestListAdapter mAdapter;
 
     @Override
     protected void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
-
-        TestListAdapter adapter = new TestListAdapter(this);
-        setListAdapter(adapter);
-
-        TestResultContentObserver observer = new TestResultContentObserver(adapter);
-        ContentResolver resolver = getContentResolver();
-        resolver.registerContentObserver(TestResultsProvider.RESULTS_CONTENT_URI, true, observer);
+        mAdapter = new TestListAdapter(this, null);
+        setListAdapter(mAdapter);
+        mAdapter.loadTestResults();
     }
 
     /** Launch the activity when its {@link ListView} item is clicked. */
@@ -59,17 +52,11 @@
     }
 
     private Intent getIntent(int position) {
-        TestListAdapter adapter = getListAdapter();
-        TestListItem item = adapter.getItem(position);
+        TestListItem item = mAdapter.getItem(position);
         return item.intent;
     }
 
     @Override
-    public TestListAdapter getListAdapter() {
-        return (TestListAdapter) super.getListAdapter();
-    }
-
-    @Override
     protected void onActivityResult(int requestCode, int resultCode, Intent data) {
         super.onActivityResult(requestCode, resultCode, data);
         switch (requestCode) {
@@ -85,18 +72,7 @@
     private void handleLaunchTestResult(int resultCode, Intent data) {
         if (resultCode == RESULT_OK) {
             TestResult testResult = TestResult.fromActivityResult(resultCode, data);
-            ContentValues values = new ContentValues(2);
-            values.put(TestResultsProvider.COLUMN_TEST_RESULT, testResult.getResult());
-            values.put(TestResultsProvider.COLUMN_TEST_NAME, testResult.getName());
-
-            ContentResolver resolver = getContentResolver();
-            int numUpdated = resolver.update(TestResultsProvider.RESULTS_CONTENT_URI, values,
-                    TestResultsProvider.COLUMN_TEST_NAME + " = ?",
-                    new String[] {testResult.getName()});
-
-            if (numUpdated == 0) {
-                resolver.insert(TestResultsProvider.RESULTS_CONTENT_URI, values);
-            }
+            mAdapter.setTestResult(testResult);
         }
     }
 
@@ -128,13 +104,12 @@
     }
 
     private void handleClearItemSelected() {
-        ContentResolver resolver = getContentResolver();
-        resolver.delete(TestResultsProvider.RESULTS_CONTENT_URI, "1", null);
+        mAdapter.clearTestResults();
         Toast.makeText(this, R.string.test_results_cleared, Toast.LENGTH_SHORT).show();
     }
 
     private void handleCopyItemSelected() {
-        TestResultsReport report = new TestResultsReport(this, getListAdapter());
+        TestResultsReport report = new TestResultsReport(this, mAdapter);
         ClipboardManager clipboardManager = (ClipboardManager) getSystemService(CLIPBOARD_SERVICE);
         clipboardManager.setText(report.getBody());
         Toast.makeText(this, R.string.test_results_copied, Toast.LENGTH_SHORT).show();
@@ -144,31 +119,9 @@
         Intent target = new Intent(Intent.ACTION_SEND);
         target.setType("text/plain");
 
-        TestResultsReport report = new TestResultsReport(this, getListAdapter());
+        TestResultsReport report = new TestResultsReport(this, mAdapter);
         target.putExtra(Intent.EXTRA_SUBJECT, report.getSubject());
         target.putExtra(Intent.EXTRA_TEXT, report.getBody());
         startActivity(Intent.createChooser(target, getString(R.string.share_test_results)));
     }
-
-    /**
-     * {@link ContentResolver} that refreshes the {@link TestListAdapter} and thus
-     * the {@link ListView} when the test results change.
-     */
-    private static class TestResultContentObserver extends ContentObserver {
-
-        private final TestListAdapter mAdapter;
-
-        public TestResultContentObserver(TestListAdapter adapter) {
-            super(new Handler());
-            this.mAdapter = adapter;
-        }
-
-        @Override
-        public void onChange(boolean selfChange) {
-            super.onChange(selfChange);
-
-            // TODO: Could be improved by just refreshing the particular test result.
-            mAdapter.refreshTestResults();
-        }
-    }
 }
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/TestListAdapter.java b/apps/CtsVerifier/src/com/android/cts/verifier/TestListAdapter.java
index 420408e..f6e6f1b 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/TestListAdapter.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/TestListAdapter.java
@@ -17,13 +17,17 @@
 package com.android.cts.verifier;
 
 import android.content.ContentResolver;
+import android.content.ContentValues;
 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;
 import android.view.ViewGroup;
@@ -59,15 +63,22 @@
  *             <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>
  */
-class TestListAdapter extends BaseAdapter {
+public 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;
 
@@ -79,8 +90,10 @@
 
     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;
+    private final List<TestListItem> mRows = new ArrayList<TestListAdapter.TestListItem>();
 
     /** Mutable test results that will change as each test activity finishes. */
     private final Map<String, Integer> mTestResults = new HashMap<String, Integer>();
@@ -88,7 +101,7 @@
     private final LayoutInflater mLayoutInflater;
 
     /** {@link ListView} row that is either a test category header or a test. */
-    static class TestListItem {
+    public static class TestListItem {
 
         /** Title shown in the {@link ListView}. */
         final String title;
@@ -99,6 +112,9 @@
         /** 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>();
+
         static TestListItem newTest(String title, String className, Intent intent) {
             return new TestListItem(title, className, intent);
         }
@@ -113,26 +129,81 @@
             this.intent = intent;
         }
 
+        public Intent getIntent() {
+            return intent;
+        }
+
         boolean isTest() {
             return intent != null;
         }
+
+        void addTestListItem(TestListItem item) {
+            subItems.add(item);
+        }
     }
 
-    TestListAdapter(Context context) {
+    public TestListAdapter(Context context, String testParent) {
         this.mContext = context;
-        this.mRows = getRows(context);
+        this.mTestParent = testParent;
         this.mLayoutInflater =
                 (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
-        updateTestResults(mContext, mTestResults);
+
+        TestResultContentObserver observer = new TestResultContentObserver();
+        ContentResolver resolver = context.getContentResolver();
+        resolver.registerContentObserver(TestResultsProvider.RESULTS_CONTENT_URI, true, observer);
     }
 
-    static List<TestListItem> getRows(Context context) {
+    public void loadTestResults() {
+        new RefreshTestResultsTask().execute();
+    }
+
+    public void clearTestResults() {
+        new ClearTestResultsTask().execute();
+    }
+
+    public void setTestResult(TestResult testResult) {
+        new SetTestResultTask(testResult.getName(), testResult.getResult()).execute();
+    }
+
+    class RefreshTestResultsTask extends AsyncTask<Void, Void, RefreshResult> {
+        @Override
+        protected RefreshResult doInBackground(Void... params) {
+            List<TestListItem> rows = getRows();
+            Map<String, Integer> results = getTestResults();
+            return new RefreshResult(rows, results);
+        }
+
+        @Override
+        protected void onPostExecute(RefreshResult result) {
+            super.onPostExecute(result);
+            mRows.clear();
+            mRows.addAll(result.mItems);
+            mTestResults.clear();
+            mTestResults.putAll(result.mResults);
+            notifyDataSetChanged();
+        }
+    }
+
+    static class RefreshResult {
+        List<TestListItem> mItems;
+        Map<String, Integer> mResults;
+
+        RefreshResult(List<TestListItem> items, Map<String, Integer> results) {
+            mItems = items;
+            mResults = results;
+        }
+    }
+
+    List<TestListItem> getRows() {
+
         /*
-         * 1. Get all the tests keyed by their category.
-         * 2. Flatten the tests and categories into one giant list for the list view.
+         * 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.
          */
 
-        Map<String, List<TestListItem>> testsByCategory = getTestsByCategory(context);
+        List<ResolveInfo> infos = getResolveInfosForParent();
+        Map<String, List<TestListItem>> testsByCategory = getTestsByCategory(infos);
 
         List<String> testCategories = new ArrayList<String>(testsByCategory.keySet());
         Collections.sort(testCategories);
@@ -152,25 +223,41 @@
         return allRows;
     }
 
-    static Map<String, List<TestListItem>> getTestsByCategory(Context context) {
-        Map<String, List<TestListItem>> testsByCategory =
-                new HashMap<String, List<TestListItem>>();
-
+    List<ResolveInfo> getResolveInfosForParent() {
         Intent mainIntent = new Intent(Intent.ACTION_MAIN);
         mainIntent.addCategory(CATEGORY_MANUAL_TEST);
 
-        PackageManager packageManager = context.getPackageManager();
+        PackageManager packageManager = mContext.getPackageManager();
         List<ResolveInfo> list = packageManager.queryIntentActivities(mainIntent,
                 PackageManager.GET_ACTIVITIES | PackageManager.GET_META_DATA);
+        int size = list.size();
 
-        for (int i = 0; i < list.size(); i++) {
+        List<ResolveInfo> matchingList = new ArrayList<ResolveInfo>();
+        for (int i = 0; i < size; i++) {
             ResolveInfo info = list.get(i);
-            String testCategory = getTestCategory(context, info.activityInfo.metaData);
-            String title = getTitle(context, info.activityInfo);
+            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 className = info.activityInfo.name;
             Intent intent = getActivityIntent(info.activityInfo);
+            TestListItem item = TestListItem.newTest(title, className, intent);
 
-            addTestToCategory(testsByCategory, testCategory, title, className, intent);
+            String testCategory = getTestCategory(mContext, info.activityInfo.metaData);
+            addTestToCategory(testsByCategory, testCategory, item);
         }
 
         return testsByCategory;
@@ -188,6 +275,10 @@
         }
     }
 
+    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);
@@ -203,7 +294,7 @@
     }
 
     static void addTestToCategory(Map<String, List<TestListItem>> testsByCategory,
-            String testCategory, String title, String className, Intent intent) {
+            String testCategory, TestListItem item) {
         List<TestListItem> tests;
         if (testsByCategory.containsKey(testCategory)) {
             tests = testsByCategory.get(testCategory);
@@ -211,12 +302,12 @@
             tests = new ArrayList<TestListItem>();
         }
         testsByCategory.put(testCategory, tests);
-        tests.add(TestListItem.newTest(title, className, intent));
+        tests.add(item);
     }
 
-    static void updateTestResults(Context context, Map<String, Integer> testResults) {
-        testResults.clear();
-        ContentResolver resolver = context.getContentResolver();
+    Map<String, Integer> getTestResults() {
+        Map<String, Integer> results = new HashMap<String, Integer>();
+        ContentResolver resolver = mContext.getContentResolver();
         Cursor cursor = null;
         try {
             cursor = resolver.query(TestResultsProvider.RESULTS_CONTENT_URI,
@@ -225,7 +316,7 @@
                 do {
                     String className = cursor.getString(1);
                     int testResult = cursor.getInt(2);
-                    testResults.put(className, testResult);
+                    results.put(className, testResult);
                 } while (cursor.moveToNext());
             }
         } finally {
@@ -233,11 +324,59 @@
                 cursor.close();
             }
         }
+        return results;
     }
 
-    public void refreshTestResults() {
-        updateTestResults(mContext, mTestResults);
-        notifyDataSetChanged();
+    class ClearTestResultsTask extends AsyncTask<Void, Void, Void> {
+
+        @Override
+        protected Void doInBackground(Void... params) {
+            ContentResolver resolver = mContext.getContentResolver();
+            resolver.delete(TestResultsProvider.RESULTS_CONTENT_URI, "1", null);
+            return null;
+        }
+    }
+
+    class SetTestResultTask extends AsyncTask<Void, Void, Void> {
+
+        private final String mTestName;
+
+        private final int mResult;
+
+        SetTestResultTask(String testName, int result) {
+            mTestName = testName;
+            mResult = result;
+        }
+
+        @Override
+        protected Void doInBackground(Void... params) {
+            ContentValues values = new ContentValues(2);
+            values.put(TestResultsProvider.COLUMN_TEST_RESULT, mResult);
+            values.put(TestResultsProvider.COLUMN_TEST_NAME, mTestName);
+
+            ContentResolver resolver = mContext.getContentResolver();
+            int numUpdated = resolver.update(TestResultsProvider.RESULTS_CONTENT_URI, values,
+                    TestResultsProvider.COLUMN_TEST_NAME + " = ?",
+                    new String[] {mTestName});
+
+            if (numUpdated == 0) {
+                resolver.insert(TestResultsProvider.RESULTS_CONTENT_URI, values);
+            }
+            return null;
+        }
+    }
+
+    class TestResultContentObserver extends ContentObserver {
+
+        public TestResultContentObserver() {
+            super(new Handler());
+        }
+
+        @Override
+        public void onChange(boolean selfChange) {
+            super.onChange(selfChange);
+            loadTestResults();
+        }
     }
 
     @Override
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 b617fc2..2beff93 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/bluetooth/BluetoothTestActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/bluetooth/BluetoothTestActivity.java
@@ -18,62 +18,21 @@
 
 import com.android.cts.verifier.PassFailButtons;
 import com.android.cts.verifier.R;
+import com.android.cts.verifier.TestListAdapter;
+import com.android.cts.verifier.TestListAdapter.TestListItem;
 import com.android.cts.verifier.TestResult;
 
 import android.app.AlertDialog;
 import android.bluetooth.BluetoothAdapter;
-import android.content.ContentValues;
-import android.content.Context;
 import android.content.DialogInterface;
 import android.content.Intent;
-import android.database.Cursor;
-import android.database.sqlite.SQLiteDatabase;
-import android.database.sqlite.SQLiteOpenHelper;
-import android.os.AsyncTask;
 import android.os.Bundle;
-import android.view.LayoutInflater;
 import android.view.View;
-import android.view.ViewGroup;
-import android.widget.BaseAdapter;
 import android.widget.ListView;
-import android.widget.TextView;
-
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
 
 public class BluetoothTestActivity extends PassFailButtons.ListActivity {
 
-    public static final int TEST_BLUETOOTH_TOGGLE = 0;
-    public static final int TEST_SECURE_SERVER = 1;
-    public static final int TEST_INSECURE_SERVER = 2;
-    public static final int TEST_SECURE_CLIENT = 3;
-    public static final int TEST_INSECURE_CLIENT = 4;
-
-    private static final int START_TOGGLE_BLUETOOTH_TEST_REQUEST = 1;
-    private static final int START_SECURE_SERVER_REQUEST = 2;
-    private static final int START_INSECURE_SERVER_REQUEST = 3;
-    private static final int START_SECURE_PICK_SERVER_REQUEST = 4;
-    private static final int START_INSECURE_PICK_SERVER_REQUEST = 5;
-    private static final int START_SECURE_CLIENT_REQUEST = 6;
-    private static final int START_INSECURE_CLIENT_REQUEST = 7;
-
-    private TestListItem mBluetoothToggleTest;
-    private TestListItem mSecureServerTest;
-    private TestListItem mInsecureServerTest;
-    private TestListItem mSecureClientTest;
-    private TestListItem mInsecureClientTest;
-
-    private static final String TABLE_NAME = "results";
-    private static final String _ID = "_id";
-    private static final String COLUMN_TEST_ID = "test_id";
-    private static final String COLUMN_TEST_RESULT = "test_result";
-    private static final String[] ALL_COLUMNS = {
-          _ID,
-          COLUMN_TEST_ID,
-          COLUMN_TEST_RESULT,
-    };
+    private static final int LAUNCH_TEST_REQUEST_CODE = 1;
 
     private TestListAdapter mAdapter;
 
@@ -84,430 +43,52 @@
         setPassFailButtonClickListeners();
         setInfoResources(R.string.bluetooth_test, R.string.bluetooth_test_info, -1);
 
-        mBluetoothToggleTest = TestListItem.newTest(R.string.bt_toggle_bluetooth,
-                TEST_BLUETOOTH_TOGGLE);
-        mSecureServerTest = TestListItem.newTest(R.string.bt_secure_server,
-                TEST_SECURE_SERVER);
-        mInsecureServerTest = TestListItem.newTest(R.string.bt_insecure_server,
-                TEST_INSECURE_SERVER);
-        mSecureClientTest = TestListItem.newTest(R.string.bt_secure_client,
-                TEST_SECURE_CLIENT);
-        mInsecureClientTest = TestListItem.newTest(R.string.bt_insecure_client,
-                TEST_INSECURE_CLIENT);
-
-        mAdapter = new TestListAdapter(this);
-        mAdapter.add(TestListItem.newCategory(R.string.bt_control));
-        mAdapter.add(mBluetoothToggleTest);
-
-        mAdapter.add(TestListItem.newCategory(R.string.bt_device_communication));
-        mAdapter.add(mSecureServerTest);
-        mAdapter.add(mInsecureServerTest);
-        mAdapter.add(mSecureClientTest);
-        mAdapter.add(mInsecureClientTest);
-
+        mAdapter = new TestListAdapter(this, getClass().getName());
         setListAdapter(mAdapter);
-        refreshTestResults();
+        mAdapter.loadTestResults();
 
         if (BluetoothAdapter.getDefaultAdapter() == null) {
             showNoBluetoothDialog();
         }
     }
 
-    private void refreshTestResults() {
-        new RefreshTask().execute();
-    }
-
     private void showNoBluetoothDialog() {
         new AlertDialog.Builder(this)
-        .setIcon(android.R.drawable.ic_dialog_alert)
-        .setTitle(R.string.bt_not_available_title)
-        .setMessage(R.string.bt_not_available_message)
-        .setCancelable(false)
-        .setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
-            @Override
-            public void onClick(DialogInterface dialog, int which) {
-                finish();
-            }
-        })
-        .show();
+            .setIcon(android.R.drawable.ic_dialog_alert)
+            .setTitle(R.string.bt_not_available_title)
+            .setMessage(R.string.bt_not_available_message)
+            .setCancelable(false)
+            .setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
+                @Override
+                public void onClick(DialogInterface dialog, int which) {
+                    finish();
+                }
+            })
+            .show();
     }
 
     @Override
     protected void onListItemClick(ListView l, View v, int position, long id) {
         super.onListItemClick(l, v, position, id);
         TestListItem testItem = (TestListItem) l.getItemAtPosition(position);
-        switch (testItem.mId) {
-            case TEST_BLUETOOTH_TOGGLE:
-                startToggleBluetoothActivity();
-                break;
-
-            case TEST_SECURE_SERVER:
-                startServerActivity(true);
-                break;
-
-            case TEST_INSECURE_SERVER:
-                startServerActivity(false);
-                break;
-
-            case TEST_SECURE_CLIENT:
-                startDevicePickerActivity(true);
-                break;
-
-            case TEST_INSECURE_CLIENT:
-                startDevicePickerActivity(false);
-                break;
-        }
-    }
-
-    private void startToggleBluetoothActivity() {
-        Intent intent = new Intent(this, BluetoothToggleActivity.class);
-        startActivityForResult(intent, START_TOGGLE_BLUETOOTH_TEST_REQUEST);
-    }
-
-    private void startServerActivity(boolean secure) {
-        Intent intent = new Intent(this, MessageTestActivity.class)
-                .putExtra(MessageTestActivity.EXTRA_SECURE, secure);
-        startActivityForResult(intent, secure
-                ? START_SECURE_SERVER_REQUEST
-                : START_INSECURE_SERVER_REQUEST);
-    }
-
-    private void startDevicePickerActivity(boolean secure) {
-        Intent intent = new Intent(this, DevicePickerActivity.class);
-        startActivityForResult(intent, secure
-                ? START_SECURE_PICK_SERVER_REQUEST
-                : START_INSECURE_PICK_SERVER_REQUEST);
+        Intent intent = testItem.getIntent();
+        startActivityForResult(intent, LAUNCH_TEST_REQUEST_CODE);
     }
 
     @Override
     protected void onActivityResult(int requestCode, int resultCode, Intent data) {
         super.onActivityResult(requestCode, resultCode, data);
         switch (requestCode) {
-            case START_TOGGLE_BLUETOOTH_TEST_REQUEST:
-                handleEnableBluetoothResult(resultCode, data);
-                break;
-
-            case START_SECURE_SERVER_REQUEST:
-                handleServerResult(resultCode, data, true);
-                break;
-
-            case START_INSECURE_SERVER_REQUEST:
-                handleServerResult(resultCode, data, false);
-                break;
-
-            case START_SECURE_PICK_SERVER_REQUEST:
-                handleDevicePickerResult(resultCode, data, true);
-                break;
-
-            case START_INSECURE_PICK_SERVER_REQUEST:
-                handleDevicePickerResult(resultCode, data, false);
-                break;
-
-            case START_SECURE_CLIENT_REQUEST:
-                handleClientResult(resultCode, data, true);
-                break;
-
-            case START_INSECURE_CLIENT_REQUEST:
-                handleClientResult(resultCode, data, false);
+            case LAUNCH_TEST_REQUEST_CODE:
+                handleLaunchTestResult(resultCode, data);
                 break;
         }
     }
 
-    private void handleEnableBluetoothResult(int resultCode, Intent data) {
-        if (data != null) {
-            TestResult result = TestResult.fromActivityResult(resultCode, data);
-            mBluetoothToggleTest.setResult(result.getResult());
-            updateTest(mBluetoothToggleTest);
-        }
-    }
-
-    private void updateTest(TestListItem item) {
-        new UpdateTask().execute(item.mId, item.mResult);
-    }
-
-    private void handleServerResult(int resultCode, Intent data, boolean secure) {
-        if (data != null) {
-            TestResult result = TestResult.fromActivityResult(resultCode, data);
-            TestListItem test = secure ? mSecureServerTest : mInsecureServerTest;
-            test.setResult(result.getResult());
-            updateTest(test);
-        }
-    }
-
-    private void handleDevicePickerResult(int resultCode, Intent data, boolean secure) {
+    private void handleLaunchTestResult(int resultCode, Intent data) {
         if (resultCode == RESULT_OK) {
-            String address = data.getStringExtra(DevicePickerActivity.EXTRA_DEVICE_ADDRESS);
-            startClientActivity(address, secure);
-        }
-    }
-
-    private void startClientActivity(String address, boolean secure) {
-        Intent intent = new Intent(this, MessageTestActivity.class)
-                .putExtra(MessageTestActivity.EXTRA_DEVICE_ADDRESS, address)
-                .putExtra(MessageTestActivity.EXTRA_SECURE, secure);
-        startActivityForResult(intent, secure
-                ? START_SECURE_CLIENT_REQUEST
-                : START_INSECURE_CLIENT_REQUEST);
-    }
-
-    private void handleClientResult(int resultCode, Intent data, boolean secure) {
-        if (data != null) {
-            TestResult result = TestResult.fromActivityResult(resultCode, data);
-            TestListItem test = secure ? mSecureClientTest : mInsecureClientTest;
-            test.setResult(result.getResult());
-            updateTest(test);
-        }
-    }
-
-    private class UpdateTask extends AsyncTask<Integer, Void, Void> {
-
-        @Override
-        protected Void doInBackground(Integer[] resultPairs) {
-            TestResultsHelper openHelper = new TestResultsHelper(BluetoothTestActivity.this);
-            SQLiteDatabase db = openHelper.getWritableDatabase();
-
-            int testId = resultPairs[0];
-            int testResult = resultPairs[1];
-
-            ContentValues values = new ContentValues(2);
-            values.put(COLUMN_TEST_ID, testId);
-            values.put(COLUMN_TEST_RESULT, testResult);
-
-            try {
-                if (0 == db.update(TABLE_NAME, values, COLUMN_TEST_ID + " = ?",
-                        new String[] {Integer.toString(testId)})) {
-                    db.insert(TABLE_NAME, null, values);
-                }
-            } finally {
-                if (db != null) {
-                    db.close();
-                }
-            }
-
-            return null;
-        }
-
-        @Override
-        protected void onPostExecute(Void result) {
-            super.onPostExecute(result);
-            refreshTestResults();
-        }
-    }
-
-    private class RefreshTask extends AsyncTask<Void, Void, Map<Integer, Integer>> {
-
-        @Override
-        protected Map<Integer, Integer> doInBackground(Void... params) {
-            Map<Integer, Integer> results = new HashMap<Integer, Integer>();
-            TestResultsHelper openHelper = new TestResultsHelper(BluetoothTestActivity.this);
-            SQLiteDatabase db = openHelper.getReadableDatabase();
-            Cursor cursor = null;
-            try {
-                cursor = db.query(TABLE_NAME, ALL_COLUMNS, null, null, null, null, null, null);
-                while (cursor.moveToNext()) {
-                    int testId = cursor.getInt(1);
-                    int testResult = cursor.getInt(2);
-                    results.put(testId, testResult);
-                }
-            } finally {
-                if (cursor != null) {
-                    cursor.close();
-                }
-                if (db != null) {
-                    db.close();
-                }
-            }
-            return results;
-        }
-
-        @Override
-        protected void onPostExecute(Map<Integer, Integer> results) {
-            super.onPostExecute(results);
-            for (Integer testId : results.keySet()) {
-                TestListItem item = mAdapter.getTest(testId);
-                if (item != null) {
-                    item.setResult(results.get(testId));
-                }
-            }
-            mAdapter.notifyDataSetChanged();
-        }
-    }
-
-    static class TestListItem {
-
-        static final int NUM_VIEW_TYPES = 2;
-
-        static final int VIEW_TYPE_CATEGORY = 0;
-
-        static final int VIEW_TYPE_TEST = 1;
-
-        final int mViewType;
-
-        final int mTitle;
-
-        final int mId;
-
-        int mResult;
-
-        static TestListItem newTest(int title, int id) {
-            return new TestListItem(VIEW_TYPE_TEST, title, id);
-        }
-
-        static TestListItem newCategory(int title) {
-            return new TestListItem(VIEW_TYPE_CATEGORY, title, -1);
-        }
-
-        private TestListItem(int viewType, int title, int id) {
-            this.mViewType = viewType;
-            this.mTitle = title;
-            this.mId = id;
-        }
-
-        public boolean isTest() {
-            return mViewType == VIEW_TYPE_TEST;
-        }
-
-        public void setResult(int result) {
-            mResult = result;
-        }
-    }
-
-    static class TestListAdapter extends BaseAdapter {
-
-        private static final int PADDING = 10;
-
-        private final List<TestListItem> mItems = new ArrayList<TestListItem>();
-
-        private final Map<Integer, TestListItem> mTestsById = new HashMap<Integer, TestListItem>();
-
-        private final LayoutInflater mInflater;
-
-        public TestListAdapter(Context context) {
-            mInflater = (LayoutInflater) context.getSystemService(LAYOUT_INFLATER_SERVICE);
-        }
-
-        public void add(TestListItem item) {
-            mItems.add(item);
-            if (item.isTest()) {
-                mTestsById.put(item.mId, item);
-            }
-        }
-
-        @Override
-        public View getView(int position, View convertView, ViewGroup parent) {
-            int backgroundResource = 0;
-            int iconResource = 0;
-
-            TestListItem item = getItem(position);
-
-            TextView textView = null;
-            if (convertView == null) {
-                int layout = getLayout(position);
-                textView = (TextView) mInflater.inflate(layout, parent, false);
-            } else {
-                textView = (TextView) convertView;
-            }
-
-            textView.setText(item.mTitle);
-
-            if (item.isTest()) {
-                switch (item.mResult) {
-                    case TestResult.TEST_RESULT_PASSED:
-                        backgroundResource = R.drawable.test_pass_gradient;
-                        iconResource = R.drawable.fs_good;
-                        break;
-
-                    case TestResult.TEST_RESULT_FAILED:
-                        backgroundResource = R.drawable.test_fail_gradient;
-                        iconResource = R.drawable.fs_error;
-                        break;
-
-                    case TestResult.TEST_RESULT_NOT_EXECUTED:
-                        break;
-
-                    default:
-                        throw new IllegalArgumentException("Unknown test result: " + item.mResult);
-                }
-
-                textView.setBackgroundResource(backgroundResource);
-                textView.setCompoundDrawablesWithIntrinsicBounds(0, 0, iconResource, 0);
-                textView.setPadding(PADDING, 0, PADDING, 0);
-                textView.setCompoundDrawablePadding(PADDING);
-            }
-
-            return textView;
-        }
-
-        private int getLayout(int position) {
-            int viewType = getItemViewType(position);
-            switch (viewType) {
-                case TestListItem.VIEW_TYPE_CATEGORY:
-                    return R.layout.test_category_row;
-                case TestListItem.VIEW_TYPE_TEST:
-                    return android.R.layout.simple_list_item_1;
-                default:
-                    throw new IllegalArgumentException("Illegal view type: " + viewType);
-
-            }
-        }
-
-        public TestListItem getTest(int id) {
-            return mTestsById.get(id);
-        }
-
-        @Override
-        public int getCount() {
-            return mItems.size();
-        }
-
-        @Override
-        public TestListItem getItem(int position) {
-            return mItems.get(position);
-        }
-
-        @Override
-        public long getItemId(int position) {
-            return getItem(position).mId;
-        }
-
-        @Override
-        public int getViewTypeCount() {
-            return TestListItem.NUM_VIEW_TYPES;
-        }
-
-        @Override
-        public int getItemViewType(int position) {
-            return getItem(position).mViewType;
-        }
-
-        @Override
-        public boolean isEnabled(int position) {
-            return getItemViewType(position) != TestListItem.VIEW_TYPE_CATEGORY;
-        }
-    }
-
-    class TestResultsHelper extends SQLiteOpenHelper {
-
-        private static final String DATABASE_NAME = "bluetooth_results.db";
-
-        private static final int DATABASE_VERSION = 1;
-
-        TestResultsHelper(Context context) {
-            super(context, DATABASE_NAME, null, DATABASE_VERSION);
-        }
-
-        @Override
-        public void onCreate(SQLiteDatabase db) {
-            db.execSQL("CREATE TABLE " + TABLE_NAME + " ("
-                    + _ID + " INTEGER PRIMARY KEY AUTOINCREMENT, "
-                    + COLUMN_TEST_ID + " INTEGER, "
-                    + COLUMN_TEST_RESULT + " INTEGER DEFAULT 0);");
-        }
-
-        @Override
-        public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
-            db.execSQL("DROP TABLE IF EXISTS " + TABLE_NAME);
-            onCreate(db);
+            TestResult testResult = TestResult.fromActivityResult(resultCode, data);
+            mAdapter.setTestResult(testResult);
         }
     }
 }
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/bluetooth/InsecureClientActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/bluetooth/InsecureClientActivity.java
new file mode 100644
index 0000000..6dfbbea
--- /dev/null
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/bluetooth/InsecureClientActivity.java
@@ -0,0 +1,23 @@
+/*
+ * 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.bluetooth;
+
+public class InsecureClientActivity extends MessageTestActivity {
+    public InsecureClientActivity() {
+        super(false, false);
+    }
+}
\ No newline at end of file
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/bluetooth/InsecureServerActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/bluetooth/InsecureServerActivity.java
new file mode 100644
index 0000000..3526e04
--- /dev/null
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/bluetooth/InsecureServerActivity.java
@@ -0,0 +1,23 @@
+/*
+ * 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.bluetooth;
+
+public class InsecureServerActivity extends MessageTestActivity {
+    public InsecureServerActivity() {
+        super(false, true);
+    }
+}
\ No newline at end of file
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/bluetooth/MessageTestActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/bluetooth/MessageTestActivity.java
index 1d6706d..9405d71 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/bluetooth/MessageTestActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/bluetooth/MessageTestActivity.java
@@ -32,8 +32,8 @@
 import android.os.Handler;
 import android.os.Message;
 import android.view.View;
-import android.view.Window;
 import android.view.View.OnClickListener;
+import android.view.Window;
 import android.widget.ArrayAdapter;
 import android.widget.Button;
 import android.widget.ListView;
@@ -43,16 +43,14 @@
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
 
-public class MessageTestActivity extends PassFailButtons.Activity {
-
-    static final String EXTRA_DEVICE_ADDRESS = "deviceAddress";
-    static final String EXTRA_SECURE = "secure";
+class MessageTestActivity extends PassFailButtons.Activity {
 
     /** Broadcast action that should only be fired when pairing for a secure connection. */
     private static final String ACTION_PAIRING_REQUEST =
             "android.bluetooth.device.action.PAIRING_REQUEST";
 
     private static final int ENABLE_BLUETOOTH_REQUEST = 1;
+    private static final int PICK_SERVER_DEVICE_REQUEST = 2;
 
     private static final String MESSAGE_DELIMITER = "\n";
     private static final Pattern MESSAGE_PATTERN = Pattern.compile("Message (\\d+) to .*");
@@ -79,6 +77,11 @@
     private String mRemoteDeviceName = "";
     private StringBuilder mMessageBuffer = new StringBuilder();
 
+    MessageTestActivity(boolean secure, boolean server) {
+        mSecure = secure;
+        mServer = server;
+    }
+
     @Override
     protected void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
@@ -86,9 +89,6 @@
         setContentView(R.layout.bt_messages);
         setPassFailButtonClickListeners();
 
-        mDeviceAddress = getIntent().getStringExtra(EXTRA_DEVICE_ADDRESS);
-        mSecure = getIntent().getBooleanExtra(EXTRA_SECURE, true);
-        mServer = mDeviceAddress == null || mDeviceAddress.isEmpty();
         if (mServer) {
             setTitle(mSecure ? R.string.bt_secure_server : R.string.bt_insecure_server);
         } else {
@@ -127,24 +127,41 @@
         registerReceiver(mPairingActionReceiver, intentFilter);
 
         mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
-        if (mBluetoothAdapter.isEnabled()) {
-            startChatService();
+        if (!mServer) {
+            Intent intent = new Intent(this, DevicePickerActivity.class);
+            startActivityForResult(intent, PICK_SERVER_DEVICE_REQUEST);
         } else {
-            Intent intent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
-            startActivityForResult(intent, ENABLE_BLUETOOTH_REQUEST);
+            if (mBluetoothAdapter.isEnabled()) {
+                startChatService();
+            } else {
+                Intent intent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
+                startActivityForResult(intent, ENABLE_BLUETOOTH_REQUEST);
+            }
         }
     }
 
     @Override
     protected void onActivityResult(int requestCode, int resultCode, Intent data) {
         super.onActivityResult(requestCode, resultCode, data);
-        if (requestCode == ENABLE_BLUETOOTH_REQUEST) {
-            if (resultCode == RESULT_OK) {
-                startChatService();
-            } else {
-                setResult(RESULT_CANCELED);
-                finish();
-            }
+        switch (requestCode) {
+            case ENABLE_BLUETOOTH_REQUEST:
+                if (resultCode == RESULT_OK) {
+                    startChatService();
+                } else {
+                    setResult(RESULT_CANCELED);
+                    finish();
+                }
+                break;
+
+            case PICK_SERVER_DEVICE_REQUEST:
+                if (resultCode == RESULT_OK) {
+                    mDeviceAddress = data.getStringExtra(DevicePickerActivity.EXTRA_DEVICE_ADDRESS);
+                    startChatService();
+                } else {
+                    setResult(RESULT_CANCELED);
+                    finish();
+                }
+                break;
         }
     }
 
@@ -332,7 +349,9 @@
     @Override
     protected void onDestroy() {
         super.onDestroy();
-        mChatService.stop();
+        if (mChatService != null) {
+            mChatService.stop();
+        }
         unregisterReceiver(mPairingActionReceiver);
     }
 }
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/bluetooth/SecureClientActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/bluetooth/SecureClientActivity.java
new file mode 100644
index 0000000..799f0b8
--- /dev/null
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/bluetooth/SecureClientActivity.java
@@ -0,0 +1,23 @@
+/*
+ * 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.bluetooth;
+
+public class SecureClientActivity extends MessageTestActivity {
+    public SecureClientActivity() {
+        super(true, false);
+    }
+}
\ No newline at end of file
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/bluetooth/SecureServerActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/bluetooth/SecureServerActivity.java
new file mode 100644
index 0000000..25e26e6
--- /dev/null
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/bluetooth/SecureServerActivity.java
@@ -0,0 +1,23 @@
+/*
+ * 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.bluetooth;
+
+public class SecureServerActivity extends MessageTestActivity {
+    public SecureServerActivity() {
+        super(true, true);
+    }
+}
\ No newline at end of file