am e43f83ec: am 06f3009e: Merge "Add HTTP/RTSP streaming tests to CtsVerifier." into gingerbread

* commit 'e43f83ec66b2b78149b11f403670a48aaeedf856':
  Add HTTP/RTSP streaming tests to CtsVerifier.
diff --git a/apps/CtsVerifier/AndroidManifest.xml b/apps/CtsVerifier/AndroidManifest.xml
index ada4216..6e9deaa 100644
--- a/apps/CtsVerifier/AndroidManifest.xml
+++ b/apps/CtsVerifier/AndroidManifest.xml
@@ -165,6 +165,21 @@
             <meta-data android:name="test_category" android:value="@string/test_category_security" />
         </activity>
 
+        <activity android:name=".streamquality.StreamingVideoActivity"
+                android:label="@string/streaming_video"
+                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/test_category_streaming" />
+        </activity>
+
+        <activity android:name=".streamquality.PlayVideoActivity"
+                android:label="@string/streaming_video"
+                android:configChanges="keyboardHidden|orientation"
+                android:screenOrientation="nosensor" />
+
         <activity android:name=".features.FeatureSummaryActivity" android:label="@string/feature_summary">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
diff --git a/apps/CtsVerifier/res/layout/sv_main.xml b/apps/CtsVerifier/res/layout/sv_main.xml
new file mode 100644
index 0000000..8402b42
--- /dev/null
+++ b/apps/CtsVerifier/res/layout/sv_main.xml
@@ -0,0 +1,36 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+         android:orientation="vertical"
+         android:layout_width="match_parent"
+         android:layout_height="match_parent">
+
+     <ListView android:id="@id/android:list"
+               android:layout_width="match_parent"
+               android:layout_height="match_parent"
+               android:background="#000000"
+               android:layout_weight="1"
+               android:drawSelectorOnTop="false"/>
+
+     <TextView android:id="@id/android:empty"
+               android:layout_width="match_parent"
+               android:layout_height="match_parent"
+               android:background="#000000"
+               android:text="@string/sv_no_data"/>
+
+    <include layout="@layout/pass_fail_buttons" />
+
+</LinearLayout>
diff --git a/apps/CtsVerifier/res/layout/sv_play.xml b/apps/CtsVerifier/res/layout/sv_play.xml
new file mode 100644
index 0000000..be68868
--- /dev/null
+++ b/apps/CtsVerifier/res/layout/sv_play.xml
@@ -0,0 +1,35 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+         android:orientation="vertical"
+         android:layout_width="match_parent"
+         android:layout_height="match_parent">
+
+    <FrameLayout
+            android:id="@+id/videoframe"
+            android:layout_width="match_parent"
+            android:layout_height="0px"
+            android:layout_weight="1">
+        <SurfaceView android:id="@+id/surface"
+                android:layout_width="match_parent"
+                android:layout_height="match_parent"
+                android:layout_gravity="center">
+        </SurfaceView>
+    </FrameLayout>
+
+    <include layout="@layout/pass_fail_buttons" />
+
+</LinearLayout>
diff --git a/apps/CtsVerifier/res/values/strings.xml b/apps/CtsVerifier/res/values/strings.xml
index 80dbe95..1f79972 100644
--- a/apps/CtsVerifier/res/values/strings.xml
+++ b/apps/CtsVerifier/res/values/strings.xml
@@ -4,9 +4,9 @@
      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.
@@ -33,6 +33,7 @@
     <string name="test_category_networking">Networking</string>
     <string name="test_category_sensors">Sensors</string>
     <string name="test_category_security">Security</string>
+    <string name="test_category_streaming">Streaming</string>
     <string name="test_category_features">Features</string>
     <string name="test_category_other">Other</string>
     <string name="clear">Clear</string>
@@ -46,7 +47,7 @@
 
     <!-- Strings for BackupTestActivity -->
     <string name="backup_test">Data Backup Test</string>
-    <string name="backup_info">This test checks that data backup and automatic restore works 
+    <string name="backup_info">This test checks that data backup and automatic restore works
         properly. The test activity lists some preferences and files that are backed up and
         restored by the CTS Verifier. If backup and restore is working properly, these values
         should be restored after running the backup manager, uninstalling the app, and reinstalling
@@ -61,8 +62,8 @@
     <string name="bu_generate_error">Error occurred while generating test data...</string>
     <string name="bu_instructions">Random values for the preferences and files have been saved.
         \n\nFollow the instructions below to check that the data backup and restore works:
-        \n\n1. Make sure backup and automatic restore are enabled in settings. Depending on the 
-        backup transport supported by the device you may need to do additional steps. For instance 
+        \n\n1. Make sure backup and automatic restore are enabled in settings. Depending on the
+        backup transport supported by the device you may need to do additional steps. For instance
         you may need to set a Google account as the backup account for the device.
         \n\n2. Run the backup manager: adb shell bmgr run
         \n\n3. Uninstall the program: adb uninstall com.android.cts.verifier
@@ -109,7 +110,7 @@
 
     <!-- Strings for BluetoothActivity -->
     <string name="bluetooth_test">Bluetooth Test</string>
-    <string name="bluetooth_test_info">The Bluetooth Control tests check whether or not the device 
+    <string name="bluetooth_test_info">The Bluetooth Control tests check whether or not the device
         can disable and enable Bluetooth properly.\n\nThe Device Communication tests require two
         devices to pair and exchange messages. The two devices must be:
         \n\n1. a candidate device implementation running the software build to be tested
@@ -188,7 +189,7 @@
     <string name="no_suid_files">No unauthorized suid files detected!</string>
 
     <!--  Strings for Audio Quality Verifier -->
-    
+
      <!-- Title for Audio Quality Verifier activity -->
     <string name="aq_verifier">Audio Quality Verifier</string>
     <string name="aq_verifier_info">
@@ -207,7 +208,7 @@
            made are also attached as raw 16 bit, 16 kHz audio files to
            help you diagnose any failed tests.
     </string>
-    
+
     <!-- Button labels for VerifierActivity -->
     <string name="aq_calibrate">Calibrate</string>
     <string name="aq_run_all">Run All</string>
@@ -215,14 +216,14 @@
     <string name="aq_view_results">Results</string>
     <string name="aq_email_results">Send by email</string>
     <string name="aq_clear">Clear</string>
-    
+
     <!-- Title for ViewResultsActivity -->
     <string name="aq_view_results_name">Audio Quality Results</string>
     <!-- Button label for ViewResultsActivity -->
     <string name="aq_dismiss">Dismiss</string>
     <!-- E-mail subject line for test results -->
     <string name="aq_subject">Android Audio Quality Verifier Test Results</string>
-    
+
     <!--  Title for CalibrateVolumeActivity -->
     <string name="aq_calibrate_volume_name">Calibrate Volume</string>
     <!--  Instructions for calibrating the volume -->
@@ -234,7 +235,7 @@
     <string name="aq_status_low">Volume too low</string>
     <string name="aq_status_high">Volume too high</string>
     <string name="aq_status_ok">Volume OK</string>
-    
+
     <!-- Experiment names -->
     <string name="aq_default_exp">Unnamed experiment</string>
     <string name="aq_sound_level_exp">Sound level check</string>
@@ -245,12 +246,12 @@
     <string name="aq_bias_exp">Bias measurement</string>
     <string name="aq_cold_latency">Cold recording latency</string>
     <string name="aq_warm_latency">Warm recording latency</string>
-    
+
     <!-- Experiment outcomes -->
     <string name="aq_fail">Fail</string>
     <string name="aq_pass">Pass</string>
     <string name="aq_complete">Complete</string>
-    
+
     <!-- Experiment reports -->
     <string name="aq_loopback_report">Experiment ran successfully.</string>
     <string name="aq_bias_report">Mean = %1$.3g, tolerance = +/- %2$.0f\nRMS = %3$.0f, duration = %4$.1fs</string>
@@ -269,8 +270,8 @@
     <string name="aq_cold_latency_report">Latency = %1$dms, maximum allowed = %2$dms</string>
     <string name="aq_warm_latency_report_error">RMS = %1$.0f, target = %2$.0f</string>
     <string name="aq_warm_latency_report_normal">Latency = %1$dms</string>
-    
-    <!-- General experiment messages -->    
+
+    <!-- General experiment messages -->
     <string name="aq_audiorecord_buffer_size_error">Error getting minimum AudioRecord buffer size: %1$d</string>
     <string name="aq_audiotrack_buffer_size_error">Error getting minimum AudioTrack buffer size: %1$d</string>
     <string name="aq_init_audiorecord_error">Error initializing AudioRecord instance</string>
@@ -278,4 +279,10 @@
     <string name="aq_recording_error">Error reading data from AudioRecord instance</string>
     <string name="aq_exception_error">Exception thrown during test: %1$s</string>
 
+    <!-- Strings for StreamingVideoActivity -->
+    <string name="streaming_video">Streaming Video Quality Verifier</string>
+    <string name="streaming_video_info">This is a test for assessing the quality of streaming videos.  Play each stream and verify that the video is smooth and in sync with the audio, and that there are no quality problems.</string>
+    <string name="sv_no_data">No videos.</string>
+    <string name="sv_failed_title">Test Failed</string>
+    <string name="sv_failed_message">Unable to play stream.  See log for details.</string>
 </resources>
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/AbstractTestListActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/AbstractTestListActivity.java
new file mode 100644
index 0000000..bc1931b
--- /dev/null
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/AbstractTestListActivity.java
@@ -0,0 +1,70 @@
+/*
+ * 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 com.android.cts.verifier.TestListAdapter.TestListItem;
+
+import android.app.ListActivity;
+import android.content.Intent;
+import android.view.View;
+import android.widget.ListView;
+
+/** {@link ListActivity} that displays a list of manual tests. */
+public abstract class AbstractTestListActivity extends ListActivity {
+    private static final int LAUNCH_TEST_REQUEST_CODE = 9001;
+
+    protected TestListAdapter mAdapter;
+
+    protected void prepareTestListAdapter(String parent) {
+        mAdapter = new TestListAdapter(this, parent);
+        setListAdapter(mAdapter);
+        mAdapter.loadTestResults();
+    }
+
+    private Intent getIntent(int position) {
+        TestListItem item = mAdapter.getItem(position);
+        return item.intent;
+    }
+
+    @Override
+    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
+        super.onActivityResult(requestCode, resultCode, data);
+        switch (requestCode) {
+            case LAUNCH_TEST_REQUEST_CODE:
+                handleLaunchTestResult(resultCode, data);
+                break;
+
+            default:
+                throw new IllegalArgumentException("Unknown request code: " + requestCode);
+        }
+    }
+
+    private void handleLaunchTestResult(int resultCode, Intent data) {
+        if (resultCode == RESULT_OK) {
+            TestResult testResult = TestResult.fromActivityResult(resultCode, data);
+            mAdapter.setTestResult(testResult);
+        }
+    }
+
+    /** Launch the activity when its {@link ListView} item is clicked. */
+    @Override
+    protected void onListItemClick(ListView listView, View view, int position, long id) {
+        super.onListItemClick(listView, view, position, id);
+        Intent intent = getIntent(position);
+        startActivityForResult(intent, LAUNCH_TEST_REQUEST_CODE);
+    }
+}
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/PassFailButtons.java b/apps/CtsVerifier/src/com/android/cts/verifier/PassFailButtons.java
index 2b4661e..b31d6c6 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/PassFailButtons.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/PassFailButtons.java
@@ -78,6 +78,11 @@
 
         /* Added to the interface just to make sure it isn't forgotten in the implementations. */
         Dialog onCreateDialog(int id, Bundle args);
+
+        /**
+         * Returns a unique identifier for the test.  Usually, this is just the class name.
+         */
+        String getTestId();
     }
 
     public static class Activity extends android.app.Activity implements PassFailActivity {
@@ -101,6 +106,11 @@
         public Dialog onCreateDialog(int id, Bundle args) {
             return createDialog(this, id, args);
         }
+
+        @Override
+        public String getTestId() {
+            return getClass().getName();
+        }
     }
 
     public static class ListActivity extends android.app.ListActivity implements PassFailActivity {
@@ -124,13 +134,48 @@
         public Dialog onCreateDialog(int id, Bundle args) {
             return createDialog(this, id, args);
         }
+
+        @Override
+        public String getTestId() {
+            return getClass().getName();
+        }
     }
 
-    private static void setPassFailClickListeners(final android.app.Activity activity) {
+    public static class TestListActivity extends AbstractTestListActivity
+            implements PassFailActivity {
+
+        @Override
+        public void setPassFailButtonClickListeners() {
+            setPassFailClickListeners(this);
+        }
+
+        @Override
+        public void setInfoResources(int titleId, int messageId, int viewId) {
+            setInfo(this, titleId, messageId, viewId);
+        }
+
+        @Override
+        public Button getPassButton() {
+            return getPassButtonView(this);
+        }
+
+        @Override
+        public Dialog onCreateDialog(int id, Bundle args) {
+            return createDialog(this, id, args);
+        }
+
+        @Override
+        public String getTestId() {
+            return getClass().getName();
+        }
+    }
+
+    private static <T extends android.app.Activity & PassFailActivity>
+            void setPassFailClickListeners(final T activity) {
         View.OnClickListener clickListener = new View.OnClickListener() {
             @Override
             public void onClick(View target) {
-                setTestResultAndFinish(activity, target);
+                setTestResultAndFinish(activity, activity.getTestId(), target);
             }
         };
 
@@ -232,19 +277,30 @@
     }
 
     /** Set the test result corresponding to the button clicked and finish the activity. */
-    private static void setTestResultAndFinish(android.app.Activity activity, View target) {
+    private static void setTestResultAndFinish(android.app.Activity activity, String testId,
+            View target) {
+        boolean passed;
         switch (target.getId()) {
             case R.id.pass_button:
-                TestResult.setPassedResult(activity);
+                passed = true;
                 break;
-
             case R.id.fail_button:
-                TestResult.setFailedResult(activity);
+                passed = false;
                 break;
-
             default:
                 throw new IllegalArgumentException("Unknown id: " + target.getId());
         }
+        setTestResultAndFinish(activity, testId, passed);
+    }
+
+    /** Set the test result and finish the activity. */
+    public static void setTestResultAndFinish(android.app.Activity activity, String testId,
+            boolean passed) {
+        if (passed) {
+            TestResult.setPassedResult(activity, testId);
+        } else {
+            TestResult.setFailedResult(activity, testId);
+        }
 
         activity.finish();
     }
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/TestListActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/TestListActivity.java
index bc7a2b0..d407a63 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/TestListActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/TestListActivity.java
@@ -16,70 +16,26 @@
 
 package com.android.cts.verifier;
 
-import com.android.cts.verifier.TestListAdapter.TestListItem;
-
 import android.app.ListActivity;
-import android.content.Intent;
 import android.os.Bundle;
 import android.text.ClipboardManager;
 import android.util.Log;
 import android.view.Menu;
 import android.view.MenuInflater;
 import android.view.MenuItem;
-import android.view.View;
-import android.widget.ListView;
 import android.widget.Toast;
 
 import java.io.IOException;
 
-/** {@link ListActivity} that displays a  list of manual tests. */
-public class TestListActivity extends ListActivity {
+/** Top-level {@link ListActivity} for launching tests and managing results. */
+public class TestListActivity extends AbstractTestListActivity {
 
     private static final String TAG = TestListActivity.class.getSimpleName();
 
-    private static final int LAUNCH_TEST_REQUEST_CODE = 1;
-
-    private TestListAdapter mAdapter;
-
     @Override
     protected void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
-        mAdapter = new TestListAdapter(this, null);
-        setListAdapter(mAdapter);
-        mAdapter.loadTestResults();
-    }
-
-    /** Launch the activity when its {@link ListView} item is clicked. */
-    @Override
-    protected void onListItemClick(ListView listView, View view, int position, long id) {
-        super.onListItemClick(listView, view, position, id);
-        Intent intent = getIntent(position);
-        startActivityForResult(intent, LAUNCH_TEST_REQUEST_CODE);
-    }
-
-    private Intent getIntent(int position) {
-        TestListItem item = mAdapter.getItem(position);
-        return item.intent;
-    }
-
-    @Override
-    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
-        super.onActivityResult(requestCode, resultCode, data);
-        switch (requestCode) {
-            case LAUNCH_TEST_REQUEST_CODE:
-                handleLaunchTestResult(resultCode, data);
-                break;
-
-            default:
-                throw new IllegalArgumentException("Unknown request code: " + requestCode);
-        }
-    }
-
-    private void handleLaunchTestResult(int resultCode, Intent data) {
-        if (resultCode == RESULT_OK) {
-            TestResult testResult = TestResult.fromActivityResult(resultCode, data);
-            mAdapter.setTestResult(testResult);
-        }
+        prepareTestListAdapter(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 38b4dbc..79a8b48 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/TestListAdapter.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/TestListAdapter.java
@@ -17,7 +17,6 @@
 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;
@@ -106,8 +105,8 @@
         /** Title shown in the {@link ListView}. */
         final String title;
 
-        /** Class name with package to uniquely identify the test. Null for categories. */
-        final String className;
+        /** Test name with class and test ID to uniquely identify the test. Null for categories. */
+        final String testName;
 
         /** Intent used to launch the activity from the list. Null for categories. */
         final Intent intent;
@@ -115,17 +114,17 @@
         /** 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);
+        public static TestListItem newTest(String title, String testName, Intent intent) {
+            return new TestListItem(title, testName, intent);
         }
 
-        static TestListItem newCategory(String title) {
+        public static TestListItem newCategory(String title) {
             return new TestListItem(title, null, null);
         }
 
-        private TestListItem(String title, String className, Intent intent) {
+        private TestListItem(String title, String testName, Intent intent) {
             this.title = title;
-            this.className = className;
+            this.testName = testName;
             this.intent = intent;
         }
 
@@ -194,7 +193,7 @@
         }
     }
 
-    List<TestListItem> getRows() {
+    protected List<TestListItem> getRows() {
 
         /*
          * 1. Get all the tests belonging to the test parent.
@@ -214,6 +213,7 @@
 
             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);
                 }
@@ -252,9 +252,9 @@
         for (int i = 0; i < size; i++) {
             ResolveInfo info = list.get(i);
             String title = getTitle(mContext, info.activityInfo);
-            String className = info.activityInfo.name;
+            String testName = info.activityInfo.name;
             Intent intent = getActivityIntent(info.activityInfo);
-            TestListItem item = TestListItem.newTest(title, className, intent);
+            TestListItem item = TestListItem.newTest(title, testName, intent);
 
             String testCategory = getTestCategory(mContext, info.activityInfo.metaData);
             addTestToCategory(testsByCategory, testCategory, item);
@@ -314,9 +314,9 @@
                     TestResultsProvider.ALL_COLUMNS, null, null, null);
             if (cursor.moveToFirst()) {
                 do {
-                    String className = cursor.getString(1);
+                    String testName = cursor.getString(1);
                     int testResult = cursor.getInt(2);
-                    results.put(className, testResult);
+                    results.put(testName, testResult);
                 } while (cursor.moveToNext());
             }
         } finally {
@@ -389,25 +389,39 @@
         return 2;
     }
 
+    @Override
     public int getCount() {
         return mRows.size();
     }
 
+    @Override
     public TestListItem getItem(int position) {
         return mRows.get(position);
     }
 
+    @Override
     public long getItemId(int position) {
         return position;
     }
 
     public int getTestResult(int position) {
         TestListItem item = getItem(position);
-        return mTestResults.containsKey(item.className)
-                ? mTestResults.get(item.className)
+        return mTestResults.containsKey(item.testName)
+                ? mTestResults.get(item.testName)
                 : TestResult.TEST_RESULT_NOT_EXECUTED;
     }
 
+    public boolean allTestsPassed() {
+        for (TestListItem item : mRows) {
+            if (item.isTest() && (!mTestResults.containsKey(item.testName)
+                    || (mTestResults.get(item.testName) != TestResult.TEST_RESULT_PASSED))) {
+                return false;
+            }
+        }
+        return true;
+    }
+
+    @Override
     public View getView(int position, View convertView, ViewGroup parent) {
         TextView textView;
         if (convertView == null) {
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/TestResult.java b/apps/CtsVerifier/src/com/android/cts/verifier/TestResult.java
index 6269f97..3b42c3b 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/TestResult.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/TestResult.java
@@ -21,9 +21,9 @@
 
 /**
  * Object representing the result of a test activity like whether it succeeded or failed.
- * Use {@link #setPassedResult(Activity)} or {@link #setFailedResult(Activity)} from a test
- * activity like you would {@link Activity#setResult(int)} so that {@link TestListActivity} will
- * persist the test result and update its adapter and thus the list view.
+ * Use {@link #setPassedResult(Activity, String)} or {@link #setFailedResult(Activity, String)} from
+ * a test activity like you would {@link Activity#setResult(int)} so that {@link TestListActivity}
+ * will persist the test result and update its adapter and thus the list view.
  */
 public class TestResult {
 
@@ -39,18 +39,18 @@
     private final int mResult;
 
     /** Sets the test activity's result to pass. */
-    public static void setPassedResult(Activity activity) {
-        activity.setResult(Activity.RESULT_OK, createResult(activity, TEST_RESULT_PASSED));
+    public static void setPassedResult(Activity activity, String testId) {
+        activity.setResult(Activity.RESULT_OK, createResult(activity, TEST_RESULT_PASSED, testId));
     }
 
     /** Sets the test activity's result to failed. */
-    public static void setFailedResult(Activity activity) {
-        activity.setResult(Activity.RESULT_OK, createResult(activity, TEST_RESULT_FAILED));
+    public static void setFailedResult(Activity activity, String testId) {
+        activity.setResult(Activity.RESULT_OK, createResult(activity, TEST_RESULT_FAILED, testId));
     }
 
-    private static Intent createResult(Activity activity, int testResult) {
+    private static Intent createResult(Activity activity, int testResult, String testName) {
         Intent data = new Intent(activity, activity.getClass());
-        data.putExtra(TEST_NAME, activity.getClass().getName());
+        data.putExtra(TEST_NAME, testName);
         data.putExtra(TEST_RESULT, testResult);
         return data;
     }
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/TestResultsReport.java b/apps/CtsVerifier/src/com/android/cts/verifier/TestResultsReport.java
index 37d4819..efc9c85 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/TestResultsReport.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/TestResultsReport.java
@@ -116,7 +116,7 @@
             if (item.isTest()) {
                 xml.startTag(null, TEST_TAG);
                 xml.attribute(null, "title", item.title);
-                xml.attribute(null, "class-name", item.className);
+                xml.attribute(null, "class-name", item.testName);
                 xml.attribute(null, "result", getTestResultString(mAdapter.getTestResult(i)));
                 xml.endTag(null, TEST_TAG);
             }
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 2beff93..93d2804 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/bluetooth/BluetoothTestActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/bluetooth/BluetoothTestActivity.java
@@ -18,23 +18,13 @@
 
 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.DialogInterface;
-import android.content.Intent;
 import android.os.Bundle;
-import android.view.View;
-import android.widget.ListView;
 
-public class BluetoothTestActivity extends PassFailButtons.ListActivity {
-
-    private static final int LAUNCH_TEST_REQUEST_CODE = 1;
-
-    private TestListAdapter mAdapter;
+public class BluetoothTestActivity extends PassFailButtons.TestListActivity {
 
     @Override
     protected void onCreate(Bundle savedInstanceState) {
@@ -43,9 +33,7 @@
         setPassFailButtonClickListeners();
         setInfoResources(R.string.bluetooth_test, R.string.bluetooth_test_info, -1);
 
-        mAdapter = new TestListAdapter(this, getClass().getName());
-        setListAdapter(mAdapter);
-        mAdapter.loadTestResults();
+        prepareTestListAdapter(getClass().getName());
 
         if (BluetoothAdapter.getDefaultAdapter() == null) {
             showNoBluetoothDialog();
@@ -66,29 +54,4 @@
             })
             .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);
-        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 LAUNCH_TEST_REQUEST_CODE:
-                handleLaunchTestResult(resultCode, data);
-                break;
-        }
-    }
-
-    private void handleLaunchTestResult(int resultCode, Intent data) {
-        if (resultCode == RESULT_OK) {
-            TestResult testResult = TestResult.fromActivityResult(resultCode, data);
-            mAdapter.setTestResult(testResult);
-        }
-    }
 }
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 9405d71..28a711a 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/bluetooth/MessageTestActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/bluetooth/MessageTestActivity.java
@@ -338,7 +338,7 @@
                     new DialogInterface.OnClickListener() {
                 @Override
                 public void onClick(DialogInterface dialog, int which) {
-                    TestResult.setFailedResult(MessageTestActivity.this);
+                    TestResult.setFailedResult(MessageTestActivity.this, getTestId());
                     finish();
                 }
             })
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/streamquality/PlayVideoActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/streamquality/PlayVideoActivity.java
new file mode 100644
index 0000000..efd076d
--- /dev/null
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/streamquality/PlayVideoActivity.java
@@ -0,0 +1,215 @@
+/*
+ * 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.streamquality;
+
+import com.android.cts.verifier.PassFailButtons;
+import com.android.cts.verifier.R;
+import com.android.cts.verifier.streamquality.StreamingVideoActivity.Stream;
+
+import android.app.AlertDialog;
+import android.app.Dialog;
+import android.content.DialogInterface;
+import android.content.DialogInterface.OnClickListener;
+import android.graphics.Rect;
+import android.media.MediaPlayer;
+import android.media.MediaPlayer.OnErrorListener;
+import android.media.MediaPlayer.OnPreparedListener;
+import android.media.MediaPlayer.OnVideoSizeChangedListener;
+import android.os.Bundle;
+import android.os.Handler;
+import android.util.Log;
+import android.view.SurfaceHolder;
+import android.view.SurfaceView;
+import android.view.ViewGroup.LayoutParams;
+import android.widget.FrameLayout;
+
+import java.io.IOException;
+
+/**
+ * Activity that plays a video and allows the user to select pass/fail after 60 seconds.
+ */
+public class PlayVideoActivity extends PassFailButtons.Activity
+        implements SurfaceHolder.Callback, OnErrorListener, OnPreparedListener,
+        OnVideoSizeChangedListener {
+    /**
+     * Intent extra defining the {@link Stream} information
+     */
+    static final String EXTRA_STREAM = "com.android.cts.verifier.streamquality.EXTRA_STREAM";
+
+    private static final String TAG = PlayVideoActivity.class.getName();
+    private static final long ENABLE_PASS_DELAY = 60 * 1000;
+
+    private static final int FAIL_DIALOG_ID = 1;
+
+    private final Runnable enablePassButton = new Runnable() {
+        @Override public void run() {
+            setEnablePassButton(true);
+        }
+    };
+
+    private Stream mStream;
+    private SurfaceHolder mHolder;
+    private SurfaceView mSurfaceView;
+    private FrameLayout mVideoFrame;
+    private MediaPlayer mPlayer;
+    private Handler mHandler = new Handler();
+    private int mVideoWidth;
+    private int mVideoHeight;
+    private boolean mIsVideoSizeKnown = false;
+    private boolean mIsVideoReadyToBePlayed = false;
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.sv_play);
+        setPassFailButtonClickListeners();
+
+        setEnablePassButton(false);
+
+        mSurfaceView = (SurfaceView) findViewById(R.id.surface);
+        mVideoFrame = (FrameLayout) findViewById(R.id.videoframe);
+        mHolder = mSurfaceView.getHolder();
+        mHolder.addCallback(this);
+        mHolder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
+
+        mStream = (Stream) getIntent().getSerializableExtra(EXTRA_STREAM);
+    }
+
+    private void setEnablePassButton(boolean enable) {
+        getPassButton().setEnabled(enable);
+    }
+
+    private void playVideo() {
+        mPlayer = new MediaPlayer();
+        mPlayer.setDisplay(mHolder);
+        mPlayer.setScreenOnWhilePlaying(true);
+        mPlayer.setOnVideoSizeChangedListener(this);
+        mPlayer.setOnErrorListener(this);
+        mPlayer.setOnPreparedListener(this);
+        try {
+            mPlayer.setDataSource(mStream.uri);
+        } catch (IOException e) {
+            Log.e(TAG, "Unable to play video, setDataSource failed", e);
+            showDialog(FAIL_DIALOG_ID);
+            return;
+        }
+        mPlayer.prepareAsync();
+    }
+
+    @Override
+    public Dialog onCreateDialog(int id, Bundle args) {
+        switch (id) {
+            case FAIL_DIALOG_ID:
+                return new AlertDialog.Builder(this)
+                        .setTitle(getString(R.string.sv_failed_title))
+                        .setMessage(getString(R.string.sv_failed_message))
+                        .setNegativeButton("Close", new OnClickListener() {
+                            @Override
+                            public void onClick(DialogInterface dialog, int which) {
+                                PassFailButtons.setTestResultAndFinish(PlayVideoActivity.this,
+                                        getTestId(), false);
+                            }
+                        })
+                        .show();
+            default:
+                return super.onCreateDialog(id, args);
+        }
+    }
+
+    @Override
+    public String getTestId() {
+        return getTestId(mStream.code);
+    }
+
+    public static String getTestId(String code) {
+        return PlayVideoActivity.class.getName() + "_" + code;
+    }
+
+    @Override
+    protected void onPause() {
+        super.onPause();
+        // This test must be completed in one session
+        mHandler.removeCallbacks(enablePassButton);
+        finish();
+    }
+
+    @Override
+    protected void onDestroy() {
+        super.onDestroy();
+        if (mPlayer != null) {
+            mPlayer.release();
+            mPlayer = null;
+        }
+    }
+
+    @Override
+    public void surfaceCreated(SurfaceHolder holder) {
+        playVideo();
+    }
+
+    @Override
+    public boolean onError(MediaPlayer mp, int what, int extra) {
+        Log.e(TAG, "Unable to play video, got onError with code " + what + ", extra " + extra);
+        showDialog(FAIL_DIALOG_ID);
+        return true;
+    }
+
+    @Override
+    public void onVideoSizeChanged(MediaPlayer mp, int width, int height) {
+        mVideoWidth = width;
+        mVideoHeight = height;
+        mIsVideoSizeKnown = true;
+        if (mIsVideoReadyToBePlayed && mIsVideoSizeKnown) {
+            startVideoPlayback();
+        }
+    }
+
+    private void startVideoPlayback() {
+        mHolder.setFixedSize(mVideoWidth, mVideoHeight);
+        fillScreen();
+        mPlayer.start();
+
+        // Enable Pass button after 60 seconds
+        mHandler.postDelayed(enablePassButton, ENABLE_PASS_DELAY);
+    }
+
+    @Override
+    public void onPrepared(MediaPlayer mp) {
+        mIsVideoReadyToBePlayed = true;
+        if (mIsVideoReadyToBePlayed && mIsVideoSizeKnown) {
+            startVideoPlayback();
+        }
+    }
+
+    private void fillScreen() {
+        Rect rect = new Rect();
+        mVideoFrame.getDrawingRect(rect);
+        LayoutParams lp = mSurfaceView.getLayoutParams();
+        float aspectRatio = ((float) mVideoWidth) / mVideoHeight;
+        if (rect.width() / aspectRatio <= rect.height()) {
+            lp.width = rect.width();
+            lp.height = (int) (rect.width() / aspectRatio);
+        } else {
+            lp.width = (int) (rect.height() * aspectRatio);
+            lp.height = rect.height();
+        }
+        mSurfaceView.setLayoutParams(lp);
+    }
+
+    @Override public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {}
+    @Override public void surfaceDestroyed(SurfaceHolder holder) {}
+}
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/streamquality/StreamingVideoActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/streamquality/StreamingVideoActivity.java
new file mode 100644
index 0000000..8a01423
--- /dev/null
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/streamquality/StreamingVideoActivity.java
@@ -0,0 +1,177 @@
+/*
+ * 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.streamquality;
+
+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 android.content.Intent;
+import android.database.DataSetObserver;
+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
+ * different protocols for a short amount of time, after which users can mark Pass/Fail depending
+ * on the smoothness and subjective quality of the video.
+ */
+public class StreamingVideoActivity extends PassFailButtons.TestListActivity {
+    /**
+     * Simple storage class for stream information.
+     */
+    static class Stream implements Serializable {
+        /**
+         * Human-readable name for the stream.
+         */
+        public final String name;
+
+        /**
+         * Code name to append to the class name to identify this test.
+         */
+        public final String code;
+
+        /**
+         * URI of the stream
+         */
+        public final String uri;
+
+        public Stream(String name, String code, String uri) {
+            this.name = name;
+            this.code = code;
+            this.uri = uri;
+        }
+
+        @Override
+        public boolean equals(Object o) {
+            if (this == o) {
+                return true;
+            } else if (o == null || !(o instanceof Stream)) {
+                return false;
+            } else {
+                Stream stream = (Stream) o;
+                return name.equals(stream.name)
+                        && code.equals(stream.code)
+                        && uri.equals(stream.uri);
+            }
+        }
+
+        @Override
+        public int hashCode() {
+            return name.hashCode() ^ uri.hashCode() ^ code.hashCode();
+        }
+
+        @Override
+        public String toString() {
+            return name;
+        }
+    }
+
+    private static final Stream[] RTSP_STREAMS = {
+        new Stream("H263 Video, AMR Audio", "rtsp_h263_amr",
+                "rtsp://v2.cache7.c.youtube.com/video.3gp?"
+               + "cid=0x271de9756065677e&fmt=13&user=android-device-test"),
+        new Stream("MPEG4 SP Video, AAC Audio", "rtsp_mpeg4_aac",
+                "rtsp://v2.cache7.c.youtube.com/video.3gp?"
+                + "cid=0x271de9756065677e&fmt=13&user=android-device-test"),
+        new Stream("H264 Base Video, AAC Audio", "rtsp_h264_aac",
+                "rtsp://v2.cache7.c.youtube.com/video.3gp?"
+                + "cid=0x271de9756065677e&fmt=13&user=android-device-test"),
+    };
+
+    private static final Stream[] HTTP_STREAMS = {
+        new Stream("H263 Video, AMR Audio", "http_h263_amr", "http://v20.lscache8.c.youtube.com/"
+                + "videoplayback?id=271de9756065677e"
+                + "&itag=13&ip=0.0.0.0&ipbits=0&expire=999999999999999999"
+                + "&sparams=ip,ipbits,expire,ip,ipbits,expire,id,itag"
+                + "&signature=372FA4C532AA49D14EAF049BCDA66460EEE161E9"
+                + ".6D8BF096B73B7A68A7032CA8685053CFB498D30A"
+                + "&key=test_key1&user=android-device-test"),
+        new Stream("MPEG4 SP Video, AAC Audio", "http_mpeg4_aac",
+                "http://v20.lscache8.c.youtube.com/"
+                + "videoplayback?id=271de9756065677e"
+                + "&itag=17&ip=0.0.0.0&ipbits=0&expire=999999999999999999"
+                + "&sparams=ip,ipbits,expire,ip,ipbits,expire,id,itag"
+                + "&signature=3DCD3F79E045F95B6AF661765F046FB0440FF016"
+                + ".06A42661B3AF6BAF046F012549CC9BA34EBC80A9"
+                + "&key=test_key1&user=android-device-test"),
+        new Stream("H264 Base Video, AAC Audio", "http_h264_aac",
+                "http://v20.lscache8.c.youtube.com/"
+                + "videoplayback?id=271de9756065677e"
+                + "&itag=18&ip=0.0.0.0&ipbits=0&expire=999999999999999999"
+                + "&sparams=ip,ipbits,expire,ip,ipbits,expire,id,itag"
+                + "&signature=1219C2B07AF0638C27916307A6093C0E43CB894E"
+                + ".126B6B916BD57157782738AA7C03E59F21DBC168"
+                + "&key=test_key1&user=android-device-test"),
+    };
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.sv_main);
+        setPassFailButtonClickListeners();
+        setInfoResources(R.string.streaming_video, R.string.streaming_video_info, -1);
+
+        prepareTestListAdapter(getTestId());
+        getPassButton().setEnabled(false);
+    }
+
+    @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);
+                }
+                */
+
+                streams.add(TestListItem.newCategory("HTTP Progressive"));
+                for (Stream stream : HTTP_STREAMS) {
+                    addStreamToTests(streams, stream);
+                }
+                return streams;
+            }
+        };
+        setListAdapter(mAdapter);
+        mAdapter.registerDataSetObserver(new DataSetObserver() {
+            @Override
+            public void onChanged() {
+                updatePassButton();
+            }
+        });
+        mAdapter.loadTestResults();
+    }
+
+    private void addStreamToTests(List<TestListItem> 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));
+    }
+
+    private void updatePassButton() {
+        getPassButton().setEnabled(mAdapter.allTestsPassed());
+    }
+}
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/suid/SuidFilesActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/suid/SuidFilesActivity.java
index 4a7224b..826b82c 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/suid/SuidFilesActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/suid/SuidFilesActivity.java
@@ -73,16 +73,19 @@
             .setTitle(R.string.suid_files)
             .setMessage(R.string.suid_files_info)
             .setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
+                @Override
                 public void onClick(DialogInterface dialog, int which) {
                     startScan();
                 }
             })
             .setNegativeButton(android.R.string.cancel, new DialogInterface.OnClickListener() {
+                @Override
                 public void onClick(DialogInterface dialog, int which) {
                     finish();
                 }
             })
             .setOnCancelListener(new OnCancelListener() {
+                @Override
                 public void onCancel(DialogInterface dialog) {
                     finish();
                 }
@@ -94,6 +97,7 @@
         mProgressDialog = new ProgressDialog(this);
         mProgressDialog.setTitle(getString(R.string.scanning_directory));
         mProgressDialog.setOnCancelListener(new OnCancelListener() {
+            @Override
             public void onCancel(DialogInterface dialog) {
                 // If the scanning dialog is cancelled, then stop the task and finish the activity
                 // to prevent the user from just seeing a blank listview.
@@ -205,6 +209,7 @@
 
             private final FileStatus status = new FileStatus();
 
+            @Override
             public boolean accept(File pathname) {
                 // Don't follow symlinks to avoid infinite looping.
                 if (FileUtils.getFileStatus(pathname.getPath(), status, true)) {
@@ -221,6 +226,7 @@
 
             private final FileStatus status = new FileStatus();
 
+            @Override
             public boolean accept(File pathname) {
                 if (FileUtils.getFileStatus(pathname.getPath(), status, true)) {
                     return !status.isDirectory()
@@ -250,18 +256,19 @@
 
                 // Alert the user that nothing was found rather than showing an empty list view.
                 if (passed) {
-                    TestResult.setPassedResult(SuidFilesActivity.this);
+                    TestResult.setPassedResult(SuidFilesActivity.this, getClass().getName());
                     new AlertDialog.Builder(SuidFilesActivity.this)
                             .setTitle(R.string.congratulations)
                             .setMessage(R.string.no_suid_files)
                             .setPositiveButton(android.R.string.ok, new OnClickListener() {
+                                @Override
                                 public void onClick(DialogInterface dialog, int which) {
                                     dialog.dismiss();
                                 }
                             })
                             .show();
                 } else {
-                    TestResult.setFailedResult(SuidFilesActivity.this);
+                    TestResult.setFailedResult(SuidFilesActivity.this, getClass().getName());
                 }
             }
         }