Merge "Switch from racy notify()/wait() to using a proper semaphore." into lmp-dev
diff --git a/apps/CtsVerifier/Android.mk b/apps/CtsVerifier/Android.mk
index c227665..4b75be2 100644
--- a/apps/CtsVerifier/Android.mk
+++ b/apps/CtsVerifier/Android.mk
@@ -21,7 +21,7 @@
 
 LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_APPS)
 
-LOCAL_SRC_FILES := $(call all-java-files-under, src)
+LOCAL_SRC_FILES := $(call all-java-files-under, src) $(call all-Iaidl-files-under, src)
 
 LOCAL_STATIC_JAVA_LIBRARIES := cts-sensors-tests
 
diff --git a/apps/CtsVerifier/AndroidManifest.xml b/apps/CtsVerifier/AndroidManifest.xml
index b67bdf8..c7563b7 100644
--- a/apps/CtsVerifier/AndroidManifest.xml
+++ b/apps/CtsVerifier/AndroidManifest.xml
@@ -34,6 +34,7 @@
     <uses-permission android:name="android.permission.FULLSCREEN" />
     <uses-permission android:name="android.permission.INTERNET" />
     <uses-permission android:name="android.permission.NFC" />
+    <uses-permission android:name="android.permission.VIBRATE" />
     <uses-feature android:name="android.hardware.camera.front"
                   android:required="false" />
     <uses-feature android:name="android.hardware.camera.autofocus"
@@ -43,7 +44,6 @@
     <uses-permission android:name="android.permission.WAKE_LOCK" />
     <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
     <uses-permission android:name="com.android.alarm.permission.SET_ALARM" />
-
     <uses-feature android:name="android.hardware.usb.accessory" />
 
     <!-- Needed by the Audio Quality Verifier to store the sound samples that will be mailed. -->
@@ -364,6 +364,17 @@
             <meta-data android:name="test_parent" android:value="com.android.cts.verifier.bluetooth.BleScannerTestActivity" />
         </activity>
 
+        <activity android:name=".bluetooth.BleScannerHardwareScanFilterActivity"
+                android:label="@string/ble_scanner_scan_filter_name"
+                android:configChanges="keyboardHidden|orientation|screenSize">
+            <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_le" />
+            <meta-data android:name="test_parent" android:value="com.android.cts.verifier.bluetooth.BleScannerTestActivity" />
+        </activity>
+
         <activity android:name=".bluetooth.BleAdvertiserTestActivity"
                 android:label="@string/ble_advertiser_test_name"
                 android:configChanges="keyboardHidden|orientation|screenSize">
@@ -397,6 +408,17 @@
             <meta-data android:name="test_parent" android:value="com.android.cts.verifier.bluetooth.BleAdvertiserTestActivity" />
         </activity>
 
+        <activity android:name=".bluetooth.BleAdvertiserHardwareScanFilterActivity"
+                android:label="@string/ble_advertiser_scan_filter_name"
+                android:configChanges="keyboardHidden|orientation|screenSize">
+            <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_le" />
+            <meta-data android:name="test_parent" android:value="com.android.cts.verifier.bluetooth.BleAdvertiserTestActivity" />
+        </activity>
+
         <activity android:name=".suid.SuidFilesActivity"
                 android:label="@string/suid_files"
                 android:configChanges="keyboardHidden|orientation|screenSize">
@@ -927,6 +949,32 @@
             <meta-data android:name="test_category" android:value="@string/test_category_deskclock" />
         </activity>
 
+        <activity
+                android:name="com.android.cts.verifier.sensors.StepCounterTestActivity"
+                android:label="@string/snsr_step_counter_test"
+                android:screenOrientation="nosensor" >
+            <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_sensors" />
+        </activity>
+
+        <activity
+            android:name="com.android.cts.verifier.sensors.SignificantMotionTestActivity"
+            android:label="@string/snsr_significant_motion_test"
+            android:screenOrientation="nosensor" >
+            <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_sensors" />
+        </activity>
+
         <receiver android:name=".widget.WidgetCtsProvider">
             <intent-filter>
                 <action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
@@ -941,6 +989,55 @@
             android:permission="android.permission.BIND_REMOTEVIEWS"
             android:exported="false" />
 
+        <activity android:name=".projection.cube.ProjectionCubeActivity"
+                  android:label="@string/pca_test">
+            <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_projection" />
+        </activity>
+
+        <activity android:name=".projection.widgets.ProjectionWidgetActivity"
+                  android:label="@string/pwa_test">
+            <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_projection" />
+        </activity>
+
+        <activity android:name=".projection.list.ProjectionListActivity"
+                  android:label="@string/pla_test">
+            <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_projection" />
+        </activity>
+
+        <activity android:name=".projection.video.ProjectionVideoActivity"
+                  android:label="@string/pva_test">
+            <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_projection" />
+        </activity>
+
+        <activity android:name=".projection.touch.ProjectionTouchActivity"
+                  android:label="@string/pta_test">
+            <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_projection" />
+        </activity>
+
+        <service android:name=".projection.ProjectionService"
+                 android:label="@string/projection_service_name"
+                 android:process=":projectionservice" />
+
    </application>
 
 </manifest>
diff --git a/apps/CtsVerifier/proguard.flags b/apps/CtsVerifier/proguard.flags
index 7e6587a..2dfa185 100644
--- a/apps/CtsVerifier/proguard.flags
+++ b/apps/CtsVerifier/proguard.flags
@@ -6,6 +6,11 @@
     private <fields>;
 }
 
+# ensure we keep public sensor test methods, these are needed at runtime
+-keepclassmembers class * extends com.android.cts.verifier.sensors.BaseSensorTestActivity {
+    public <methods>;
+}
+
 -keepclasseswithmembers class * extends com.android.cts.verifier.location.LocationModeTestActivity
 
 -dontwarn android.hardware.Sensor
diff --git a/apps/CtsVerifier/res/layout/ble_advertiser_hardware_scan_filter.xml b/apps/CtsVerifier/res/layout/ble_advertiser_hardware_scan_filter.xml
new file mode 100644
index 0000000..1496f81
--- /dev/null
+++ b/apps/CtsVerifier/res/layout/ble_advertiser_hardware_scan_filter.xml
@@ -0,0 +1,73 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2013 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.
+-->
+<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:orientation="vertical"
+        android:padding="10dip"
+        >
+
+    <LinearLayout android:orientation="vertical"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:layout_centerInParent="true"
+            >
+        <TextView android:text="@string/ble_advertiser_scannable"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+        />
+        <LinearLayout android:orientation="horizontal"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                >
+            <Button android:id="@+id/ble_advertiser_scannable_start"
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:text="@string/ble_advertiser_start"
+                    />
+            <Button android:id="@+id/ble_advertiser_scannable_stop"
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:text="@string/ble_advertiser_stop"
+                    />
+        </LinearLayout>
+        <TextView android:text="@string/ble_advertiser_unscannable"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+        />
+        <LinearLayout android:orientation="horizontal"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                >
+            <Button android:id="@+id/ble_advertiser_unscannable_start"
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:text="@string/ble_advertiser_start"
+                    />
+            <Button android:id="@+id/ble_advertiser_unscannable_stop"
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:text="@string/ble_advertiser_stop"
+                    />
+        </LinearLayout>
+    </LinearLayout>
+
+    <include android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:layout_alignParentBottom="true"
+            layout="@layout/pass_fail_buttons"
+            />
+</RelativeLayout>
diff --git a/apps/CtsVerifier/res/layout/ble_advertiser_power_level.xml b/apps/CtsVerifier/res/layout/ble_advertiser_power_level.xml
index ad45c52..37a7bbc 100644
--- a/apps/CtsVerifier/res/layout/ble_advertiser_power_level.xml
+++ b/apps/CtsVerifier/res/layout/ble_advertiser_power_level.xml
@@ -28,12 +28,12 @@
         <Button android:id="@+id/ble_power_level_start"
                 android:layout_width="wrap_content"
                 android:layout_height="wrap_content"
-                android:text="@string/ble_power_level_start"
+                android:text="@string/ble_advertiser_start"
                 />
         <Button android:id="@+id/ble_power_level_stop"
                 android:layout_width="wrap_content"
                 android:layout_height="wrap_content"
-                android:text="@string/ble_power_level_stop"
+                android:text="@string/ble_advertiser_stop"
                 />
     </LinearLayout>
 
diff --git a/apps/CtsVerifier/res/layout/ble_privacy_mac.xml b/apps/CtsVerifier/res/layout/ble_privacy_mac.xml
index 2609654..e67c61a 100644
--- a/apps/CtsVerifier/res/layout/ble_privacy_mac.xml
+++ b/apps/CtsVerifier/res/layout/ble_privacy_mac.xml
@@ -28,12 +28,12 @@
         <Button android:id="@+id/ble_privacy_mac_start"
                 android:layout_width="wrap_content"
                 android:layout_height="wrap_content"
-                android:text="@string/ble_privacy_mac_start"
+                android:text="@string/ble_advertiser_start"
                 />
         <Button android:id="@+id/ble_privacy_mac_stop"
                 android:layout_width="wrap_content"
                 android:layout_height="wrap_content"
-                android:text="@string/ble_privacy_mac_stop"
+                android:text="@string/ble_advertiser_stop"
                 />
     </LinearLayout>
 
diff --git a/apps/CtsVerifier/res/layout/ble_scanner_hardware_scan_filter.xml b/apps/CtsVerifier/res/layout/ble_scanner_hardware_scan_filter.xml
new file mode 100644
index 0000000..4f5de53
--- /dev/null
+++ b/apps/CtsVerifier/res/layout/ble_scanner_hardware_scan_filter.xml
@@ -0,0 +1,51 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2013 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.
+-->
+<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:orientation="vertical"
+        android:padding="10dip"
+        >
+    <LinearLayout android:orientation="vertical"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            >
+        <LinearLayout android:orientation="horizontal"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                >
+            <Button android:id="@+id/ble_scan_with_filter"
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:text="@string/ble_scan_with_filter"
+                    />
+            <Button android:id="@+id/ble_scan_without_filter"
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:text="@string/ble_scan_without_filter"
+                    />
+        </LinearLayout>
+        <ListView android:id="@+id/ble_scan_result_list"
+                android:layout_height="wrap_content"
+                android:layout_width="match_parent">
+        </ListView>
+    </LinearLayout>
+    <include android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:layout_alignParentBottom="true"
+            layout="@layout/pass_fail_buttons"
+    />
+</RelativeLayout>
diff --git a/apps/CtsVerifier/res/layout/pa_main.xml b/apps/CtsVerifier/res/layout/pa_main.xml
new file mode 100644
index 0000000..b6b8e4b
--- /dev/null
+++ b/apps/CtsVerifier/res/layout/pa_main.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2014 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.
+-->
+<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent" >
+
+    <TextureView
+        android:id="@+id/texture_view"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent" />
+
+     <include layout="@layout/pass_fail_buttons" />
+
+</RelativeLayout>
diff --git a/apps/CtsVerifier/res/layout/pca_cubes.xml b/apps/CtsVerifier/res/layout/pca_cubes.xml
new file mode 100644
index 0000000..25869b9
--- /dev/null
+++ b/apps/CtsVerifier/res/layout/pca_cubes.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2014 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.
+-->
+<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:fitsSystemWindows="true" >
+
+    <android.opengl.GLSurfaceView
+        android:id="@+id/cube_view"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent" />
+
+</RelativeLayout>
diff --git a/apps/CtsVerifier/res/layout/pla_list.xml b/apps/CtsVerifier/res/layout/pla_list.xml
new file mode 100644
index 0000000..0973136
--- /dev/null
+++ b/apps/CtsVerifier/res/layout/pla_list.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2014 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:layout_width="fill_parent"
+    android:layout_height="fill_parent"
+    android:orientation="vertical" >
+
+    <ListView
+        android:id="@+id/pla_list"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content" >
+    </ListView>
+
+</LinearLayout>
\ No newline at end of file
diff --git a/apps/CtsVerifier/res/layout/pta_touch.xml b/apps/CtsVerifier/res/layout/pta_touch.xml
new file mode 100644
index 0000000..8cf6e89
--- /dev/null
+++ b/apps/CtsVerifier/res/layout/pta_touch.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2014 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.
+-->
+<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent" >
+    <com.android.cts.verifier.projection.touch.TouchPointView
+        android:id="@+id/multi_touch_view"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent" />
+
+</RelativeLayout>
diff --git a/apps/CtsVerifier/res/layout/pva_video.xml b/apps/CtsVerifier/res/layout/pva_video.xml
new file mode 100644
index 0000000..a62c833
--- /dev/null
+++ b/apps/CtsVerifier/res/layout/pva_video.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2014 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.
+-->
+<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent" >
+
+    <VideoView
+        android:id="@+id/video_view"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent" />
+
+</RelativeLayout>
\ No newline at end of file
diff --git a/apps/CtsVerifier/res/layout/pwa_buttons.xml b/apps/CtsVerifier/res/layout/pwa_buttons.xml
new file mode 100644
index 0000000..fc2d10f
--- /dev/null
+++ b/apps/CtsVerifier/res/layout/pwa_buttons.xml
@@ -0,0 +1,42 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2014 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:layout_width="fill_parent"
+    android:layout_height="fill_parent"
+    android:gravity="bottom"
+    android:orientation="vertical" >
+
+    <Button
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:text="@string/pwa_button" />
+
+    <Button
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:text="@string/pwa_button" />
+
+    <Button
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:text="@string/pwa_button" />
+
+    <Button
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:text="@string/pwa_button" />
+
+</LinearLayout>
\ No newline at end of file
diff --git a/apps/CtsVerifier/res/layout/pwa_widgets.xml b/apps/CtsVerifier/res/layout/pwa_widgets.xml
new file mode 100644
index 0000000..4bfcec6
--- /dev/null
+++ b/apps/CtsVerifier/res/layout/pwa_widgets.xml
@@ -0,0 +1,73 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2014 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.
+-->
+<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent" >
+
+     <TextureView
+         android:id="@+id/texture_view"
+         android:layout_width="match_parent"
+         android:layout_height="match_parent" />
+
+     <LinearLayout
+         android:layout_width="fill_parent"
+         android:layout_height="wrap_content"
+         android:orientation="vertical" >
+
+         <LinearLayout
+             android:layout_width="match_parent"
+             android:layout_height="match_parent" >
+
+             <include layout="@layout/pass_fail_buttons" />
+         </LinearLayout>
+
+         <LinearLayout
+             android:layout_width="match_parent"
+             android:layout_height="wrap_content"
+             android:orientation="horizontal" >
+
+             <Button
+                 android:id="@+id/up_button"
+                 android:layout_width="match_parent"
+                 android:layout_height="wrap_content"
+                 android:layout_weight="1"
+                 android:text="@string/pwa_button_up" />
+
+             <Button
+                 android:id="@+id/down_button"
+                 android:layout_width="match_parent"
+                 android:layout_height="wrap_content"
+                 android:layout_weight="1"
+                 android:text="@string/pwa_button_down" />
+
+             <Button
+                 android:id="@+id/left_button"
+                 android:layout_width="match_parent"
+                 android:layout_height="match_parent"
+                 android:layout_weight="1"
+                 android:text="@string/pwa_button_left" />
+
+             <Button
+                 android:id="@+id/right_button"
+                 android:layout_width="match_parent"
+                 android:layout_height="wrap_content"
+                 android:layout_weight="1"
+                 android:text="@string/pwa_button_right" />
+
+         </LinearLayout>
+     </LinearLayout>
+
+</FrameLayout>
diff --git a/apps/CtsVerifier/res/raw/test_video.mp4 b/apps/CtsVerifier/res/raw/test_video.mp4
new file mode 100644
index 0000000..ab95ac0
--- /dev/null
+++ b/apps/CtsVerifier/res/raw/test_video.mp4
Binary files differ
diff --git a/apps/CtsVerifier/res/values/strings.xml b/apps/CtsVerifier/res/values/strings.xml
index 1fb7ecb..849698f 100644
--- a/apps/CtsVerifier/res/values/strings.xml
+++ b/apps/CtsVerifier/res/values/strings.xml
@@ -231,14 +231,16 @@
     <string name="ble_advertiser_test_name">BLE Advertiser Test</string>
     <string name="ble_advertiser_test_info">The BLE test must be done simultaneously on two devices, an advertiser and a scanner. This device is the advertiser.</string>
     <string name="ble_advertiser_service_name">Bluetooth LE Advertiser Handler Service</string>
-    <string name="ble_privacy_mac_start">Start</string>
-    <string name="ble_privacy_mac_stop">Stop</string>
     <string name="ble_privacy_mac_name">BLE Privacy Mac</string>
     <string name="ble_privacy_mac_info">BLE Advertiser should advertise in non-repeating MAC address.</string>
-    <string name="ble_power_level_start">Start</string>
-    <string name="ble_power_level_stop">Stop</string>
     <string name="ble_power_level_name">BLE Tx Power Level</string>
     <string name="ble_power_level_info">BLE Advertiser advertises in 4 different power levels. Scanner should receive them in different strength of Rssi, cannot receive weak signals beyond several feet.</string>
+    <string name="ble_advertiser_scan_filter_name">BLE Hardware Scan Filter</string>
+    <string name="ble_advertiser_scan_filter_info">BLE Advertiser advertises with 2 different data separately. One can wake up the scanner, the other cannot.</string>
+    <string name="ble_advertiser_scannable">Scannable advertising</string>
+    <string name="ble_advertiser_unscannable">Unscannble advertising</string>
+    <string name="ble_advertiser_start">Start</string>
+    <string name="ble_advertiser_stop">Stop</string>
 
     <!-- BLE scanner side strings -->
     <string name="ble_scanner_test_name">BLE Scanner Test</string>
@@ -249,6 +251,10 @@
     <string name="ble_low">Low</string>
     <string name="ble_medium">Medium</string>
     <string name="ble_high">High</string>
+    <string name="ble_scanner_scan_filter_name">BLE Hardware Scan Filter</string>
+    <string name="ble_scanner_scan_filter_info">Lock the screen of scanner, and connect to monsoon. It will not wake up when advertiser is advertising unscannable, and scanner is scanning with filter.</string>
+    <string name="ble_scan_with_filter">Scan with filter</string>
+    <string name="ble_scan_without_filter">Scan without filter</string>
 
     <!-- Strings for FeatureSummaryActivity -->
     <string name="feature_summary">Hardware/Software Feature Summary</string>
@@ -442,6 +448,12 @@
     <!-- Magnetic Field -->
     <string name="snsr_mag_m_test">Magnetic Field Measurement Tests</string>
 
+    <!-- Step Counter and Detector -->
+    <string name="snsr_step_counter_test">Step Counter and Detector Tests</string>
+
+    <!-- Significant Motion -->
+    <string name="snsr_significant_motion_test">Significant Motion Tests</string>
+
     <!-- Strings for Sample Test Activities -->
     <string name="share_button_text">Share</string>
     <string name="sample_framework_test">Sample Framework Test</string>
@@ -955,7 +967,6 @@
         2. Verify that a timer is started  and the timers UI is shown with a timer named "Start Timer Test".\n
         3. Verify that the timer rings after 30 seconds.\n
     </string>
-    
     <!-- Strings for LockConfirmBypassTest -->
     <string name="lock_confirm_test_title">Keyguard Password Verification</string>
     <string name="lock_set_button_text">Set password</string>
@@ -966,4 +977,31 @@
         \nThen click the \"Change password\" button to change it. You should be prompted for the current password first. If you are not, then mark the test as failed.
     </string>
 
+    <!-- String for Projection Tests -->
+    <string name="test_category_projection">Projection Tests</string>
+    <string name="projection_service_name">Projection Service</string>
+    <string name="pca_info">This tests whether or not OpenGL projection works.\n
+        You should see two "tumbling cubes." Tapping the screen should case the cubes to explode.</string>
+    <string name="pca_test">Projection Cube Test</string>
+    <string name="pwa_info">This tests whether or displaying widets and keyfocus navigation works.\n
+        You should see four buttons on the bottom of the screen.\n
+        Pressing the "up" and "down" buttons should highlight different buttons.\n
+        Further, you should also be able to touch them and they should highlight as ususual.</string>
+    <string name="pwa_test">Projection Widget Test</string>
+    <string name="pwa_button_up">Up</string>
+    <string name="pwa_button_down">Down</string>
+    <string name="pwa_button_left">Left</string>
+    <string name="pwa_button_right">Right</string>
+    <string name="pwa_button">Button</string>
+    <string name="pla_test">Projection Scrolling List Test</string>
+    <string name="pla_info">This tests whether a projected list view will scroll properly\n
+        You should see 50 list items and be able to scroll up and down the list</string>
+    <string name="pva_test">Projection Video Playback Test</string>
+    <string name="pva_info">This tests whether video playback works when projected.\n
+        You should see a blinking white box and here a beep that is synchronized with each blink</string>
+    <string name="pta_test">Projection Multitouch Test</string>
+    <string name="pta_info">This tests whether multitouch works.\n
+        Touching the screen should render a dot at the location you touched.\n
+        Touching with additional fingers will render additoinal dots and you should be able to drag them around.</string>
+
 </resources>
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/bluetooth/BleAdvertiserHardwareScanFilterActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/bluetooth/BleAdvertiserHardwareScanFilterActivity.java
new file mode 100644
index 0000000..242bb08
--- /dev/null
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/bluetooth/BleAdvertiserHardwareScanFilterActivity.java
@@ -0,0 +1,136 @@
+/*
+ * Copyright (C) 2013 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;
+
+import com.android.cts.verifier.PassFailButtons;
+import com.android.cts.verifier.R;
+
+import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothDevice;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.os.Bundle;
+import android.view.View;
+import android.view.View.OnClickListener;
+import android.widget.Button;
+import android.widget.Toast;
+
+public class BleAdvertiserHardwareScanFilterActivity extends PassFailButtons.Activity {
+
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.ble_advertiser_hardware_scan_filter);
+        setPassFailButtonClickListeners();
+        setInfoResources(R.string.ble_advertiser_scan_filter_name,
+                         R.string.ble_advertiser_scan_filter_info, -1);
+
+        ((Button) findViewById(R.id.ble_advertiser_scannable_start))
+            .setOnClickListener(new OnClickListener() {
+                @Override
+                public void onClick(View v) {
+                    Intent intent = new Intent(BleAdvertiserHardwareScanFilterActivity.this,
+                                               BleAdvertiserService.class);
+                    intent.putExtra(BleAdvertiserService.EXTRA_COMMAND,
+                                    BleAdvertiserService.COMMAND_START_SCANNABLE);
+                    startService(intent);
+                }
+            });
+        ((Button)findViewById(R.id.ble_advertiser_scannable_stop))
+            .setOnClickListener(new OnClickListener() {
+                @Override
+                public void onClick(View v) {
+                    stopAdvertising();
+                }
+            });
+        ((Button)findViewById(R.id.ble_advertiser_unscannable_start))
+            .setOnClickListener(new OnClickListener() {
+                @Override
+                public void onClick(View v) {
+                    Intent intent = new Intent(BleAdvertiserHardwareScanFilterActivity.this,
+                                               BleAdvertiserService.class);
+                    intent.putExtra(BleAdvertiserService.EXTRA_COMMAND,
+                                    BleAdvertiserService.COMMAND_START_UNSCANNABLE);
+                    startService(intent);
+                }
+        });
+        ((Button)findViewById(R.id.ble_advertiser_unscannable_stop))
+            .setOnClickListener(new OnClickListener() {
+                @Override
+                public void onClick(View v) {
+                    stopAdvertising();
+                }
+        });
+    }
+
+    @Override
+    public void onResume() {
+        super.onResume();
+        IntentFilter filter = new IntentFilter();
+        filter.addAction(BleAdvertiserService.BLE_START_SCANNABLE);
+        filter.addAction(BleAdvertiserService.BLE_START_UNSCANNABLE);
+        filter.addAction(BleAdvertiserService.BLE_STOP_SCANNABLE);
+        filter.addAction(BleAdvertiserService.BLE_STOP_UNSCANNABLE);
+        registerReceiver(onBroadcast, filter);
+    }
+
+    @Override
+    public void onPause() {
+        super.onPause();
+        unregisterReceiver(onBroadcast);
+    }
+
+    @Override
+    public void onDestroy() {
+        super.onDestroy();
+        stopAdvertising();
+    }
+
+    private void showMessage(String msg) {
+        Toast.makeText(this, msg, Toast.LENGTH_SHORT).show();
+    }
+
+    private void stopAdvertising() {
+        Intent intent = new Intent(BleAdvertiserHardwareScanFilterActivity.this,
+                                   BleAdvertiserService.class);
+        intent.putExtra(BleAdvertiserService.EXTRA_COMMAND,
+                        BleAdvertiserService.COMMAND_STOP_SCANNABLE);
+        intent.putExtra(BleAdvertiserService.EXTRA_COMMAND,
+                        BleAdvertiserService.COMMAND_STOP_UNSCANNABLE);
+        startService(intent);
+    }
+
+    private BroadcastReceiver onBroadcast = new BroadcastReceiver() {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            switch (intent.getAction()) {
+                case BleAdvertiserService.BLE_START_SCANNABLE:
+                    showMessage("Start advertising, this should be scanned");
+                    break;
+                case BleAdvertiserService.BLE_START_UNSCANNABLE:
+                    showMessage("Start advertising, this should not be scanned");
+                    break;
+                case BleAdvertiserService.BLE_STOP_SCANNABLE:
+                case BleAdvertiserService.BLE_STOP_UNSCANNABLE:
+                    showMessage("Stop advertising");
+                    break;
+            }
+        }
+    };
+}
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/bluetooth/BleAdvertiserService.java b/apps/CtsVerifier/src/com/android/cts/verifier/bluetooth/BleAdvertiserService.java
index 7191f58..e21c89e 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/bluetooth/BleAdvertiserService.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/bluetooth/BleAdvertiserService.java
@@ -48,6 +48,10 @@
     public static final int COMMAND_STOP_ADVERTISE = 1;
     public static final int COMMAND_START_POWER_LEVEL = 2;
     public static final int COMMAND_STOP_POWER_LEVEL = 3;
+    public static final int COMMAND_START_SCANNABLE = 4;
+    public static final int COMMAND_STOP_SCANNABLE = 5;
+    public static final int COMMAND_START_UNSCANNABLE = 6;
+    public static final int COMMAND_STOP_UNSCANNABLE = 7;
 
     public static final String BLE_START_ADVERTISE =
             "com.android.cts.verifier.bluetooth.BLE_START_ADVERTISE";
@@ -57,6 +61,14 @@
             "com.android.cts.verifier.bluetooth.BLE_START_POWER_LEVEL";
     public static final String BLE_STOP_POWER_LEVEL =
             "com.android.cts.verifier.bluetooth.BLE_STOP_POWER_LEVEL";
+    public static final String BLE_START_SCANNABLE =
+            "com.android.cts.verifier.bluetooth.BLE_START_SCANNABLE";
+    public static final String BLE_START_UNSCANNABLE =
+            "com.android.cts.verifier.bluetooth.BLE_START_UNSCANNABLE";
+    public static final String BLE_STOP_SCANNABLE =
+            "com.android.cts.verifier.bluetooth.BLE_STOP_SCANNABLE";
+    public static final String BLE_STOP_UNSCANNABLE =
+            "com.android.cts.verifier.bluetooth.BLE_STOP_UNSCANNABLE";
 
     public static final String EXTRA_COMMAND =
             "com.android.cts.verifier.bluetooth.EXTRA_COMMAND";
@@ -67,11 +79,18 @@
             UUID.fromString("00008888-0000-1000-8000-00805f9b34fb");
     protected static final UUID SCAN_RESP_UUID =
             UUID.fromString("00007777-0000-1000-8000-00805f9b34fb");
+    protected static final UUID SCANNABLE_UUID =
+            UUID.fromString("00006666-0000-1000-8000-00805f9b34fb");
+    protected static final UUID UNSCANNABLE_UUID =
+            UUID.fromString("00005555-0000-1000-8000-00805f9b34fb");
+
     public static final byte MANUFACTURER_TEST_ID = (byte)0x07;
     public static final byte[] PRIVACY_MAC_DATA = new byte[]{3, 1, 4};
     public static final byte[] PRIVACY_RESPONSE = new byte[]{9, 2, 6};
     public static final byte[] POWER_LEVEL_DATA = new byte[]{1, 5, 0};
     public static final byte[] POWER_LEVEL_MASK = new byte[]{1, 1, 0};
+    public static final byte[] SCANNABLE_DATA = new byte[]{5, 3, 5};
+    public static final byte[] UNSCANNABLE_DATA = new byte[]{8, 9, 7};
 
     private BluetoothManager mBluetoothManager;
     private BluetoothAdapter mBluetoothAdapter;
@@ -82,6 +101,10 @@
 
     private int[] mPowerLevel;
     private Map<Integer, AdvertiseCallback> mPowerCallback;
+    private int mAdvertiserStatus;
+
+    private AdvertiseCallback mScannableCallback;
+    private AdvertiseCallback mUnscannableCallback;
 
     @Override
     public void onCreate() {
@@ -92,9 +115,12 @@
         mAdvertiser = mBluetoothAdapter.getBluetoothLeAdvertiser();
         mGattServer = mBluetoothManager.openGattServer(getApplicationContext(),
             new BluetoothGattServerCallback() {});
-        mCallback = new BLEAdvertiseCallback();
         mHandler = new Handler();
+        mAdvertiserStatus = 0;
 
+        mCallback = new BLEAdvertiseCallback();
+        mScannableCallback = new BLEAdvertiseCallback();
+        mUnscannableCallback = new BLEAdvertiseCallback();
         mPowerLevel = new int[]{
             AdvertiseSettings.ADVERTISE_TX_POWER_ULTRA_LOW,
             AdvertiseSettings.ADVERTISE_TX_POWER_LOW,
@@ -123,55 +149,92 @@
         mAdvertiser.stopAdvertising(mCallback);
     }
 
+    private void stopAdvertiser() {
+        if ((mAdvertiserStatus & (1 << COMMAND_START_ADVERTISE)) > 0) {
+            mAdvertiser.stopAdvertising(mCallback);
+        }
+        if ((mAdvertiserStatus & (1 << COMMAND_START_POWER_LEVEL)) > 0) {
+            for (int t : mPowerLevel) {
+                mAdvertiser.stopAdvertising(mPowerCallback.get(t));
+            }
+        }
+        if ((mAdvertiserStatus & (1 << COMMAND_START_SCANNABLE)) > 0) {
+            mAdvertiser.stopAdvertising(mScannableCallback);
+        }
+        if ((mAdvertiserStatus & (1 << COMMAND_START_UNSCANNABLE)) > 0) {
+            mAdvertiser.stopAdvertising(mUnscannableCallback);
+        }
+        mAdvertiserStatus = 0;
+    }
+
+    private AdvertiseData generateAdvertiseData(UUID uuid, byte[] data) {
+        return new AdvertiseData.Builder()
+            .addManufacturerData(MANUFACTURER_TEST_ID, new byte[]{MANUFACTURER_TEST_ID, 0})
+            .addServiceData(new ParcelUuid(uuid), data)
+            .setIncludeTxPowerLevel(true)
+            .build();
+    }
+
+    private AdvertiseSettings generateSetting(int power) {
+        return new AdvertiseSettings.Builder()
+            .setAdvertiseMode(AdvertiseSettings.ADVERTISE_MODE_LOW_LATENCY)
+            .setTxPowerLevel(power)
+            .setIsConnectable(false)
+            .build();
+    }
+
     private void handleIntent(Intent intent) {
         int command = intent.getIntExtra(EXTRA_COMMAND, -1);
+        if (command >= 0) {
+            stopAdvertiser();
+            mAdvertiserStatus |= (1 << command);
+        }
 
         switch (command) {
             case COMMAND_START_ADVERTISE:
-                AdvertiseData data = new AdvertiseData.Builder()
-                    .addManufacturerData(MANUFACTURER_TEST_ID, new byte[]{MANUFACTURER_TEST_ID, 0})
-                    .addServiceData(new ParcelUuid(PRIVACY_MAC_UUID), PRIVACY_MAC_DATA)
-                    .build();
-                AdvertiseData response = new AdvertiseData.Builder()
-                    .addManufacturerData(MANUFACTURER_TEST_ID, new byte[]{MANUFACTURER_TEST_ID, 0})
-                    .addServiceData(new ParcelUuid(SCAN_RESP_UUID), PRIVACY_RESPONSE)
-                    .build();
+                AdvertiseData data = generateAdvertiseData(PRIVACY_MAC_UUID, PRIVACY_MAC_DATA);
+                AdvertiseData response = generateAdvertiseData(SCAN_RESP_UUID, PRIVACY_RESPONSE);
+                AdvertiseSettings setting =
+                        generateSetting(AdvertiseSettings.ADVERTISE_TX_POWER_MEDIUM);
 
-                AdvertiseSettings setting = new AdvertiseSettings.Builder()
-                    .setAdvertiseMode(AdvertiseSettings.ADVERTISE_MODE_LOW_LATENCY)
-                    .setTxPowerLevel(AdvertiseSettings.ADVERTISE_TX_POWER_MEDIUM)
-                    .setIsConnectable(false)
-                    .build();
                 mAdvertiser.startAdvertising(setting, data, response, mCallback);
                 sendBroadcast(new Intent(BLE_START_ADVERTISE));
                 break;
             case COMMAND_STOP_ADVERTISE:
-                mAdvertiser.stopAdvertising(mCallback);
                 sendBroadcast(new Intent(BLE_STOP_ADVERTISE));
                 break;
             case COMMAND_START_POWER_LEVEL:
                 for (int t : mPowerLevel) {
-                    AdvertiseData d = new AdvertiseData.Builder()
-                        .addManufacturerData(MANUFACTURER_TEST_ID,
-                            new byte[]{MANUFACTURER_TEST_ID, 0})
-                        .addServiceData(new ParcelUuid(POWER_LEVEL_UUID),
-                            new byte[]{1, 5, (byte)t})
-                        .setIncludeTxPowerLevel(true)
-                        .build();
-                    AdvertiseSettings settings = new AdvertiseSettings.Builder()
-                        .setTxPowerLevel(AdvertiseSettings.ADVERTISE_MODE_LOW_LATENCY)
-                        .setTxPowerLevel(t)
-                        .build();
+                    AdvertiseData d =
+                            generateAdvertiseData(POWER_LEVEL_UUID, new byte[]{1, 5, (byte)t});
+                    AdvertiseSettings settings = generateSetting(t);
                     mAdvertiser.startAdvertising(settings, d, mPowerCallback.get(t));
                 }
                 sendBroadcast(new Intent(BLE_START_POWER_LEVEL));
                 break;
             case COMMAND_STOP_POWER_LEVEL:
-                for (int t : mPowerLevel) {
-                    mAdvertiser.stopAdvertising(mPowerCallback.get(t));
-                }
                 sendBroadcast(new Intent(BLE_STOP_POWER_LEVEL));
                 break;
+            case COMMAND_START_SCANNABLE:
+                data = generateAdvertiseData(SCANNABLE_UUID, SCANNABLE_DATA);
+                setting = generateSetting(AdvertiseSettings.ADVERTISE_TX_POWER_MEDIUM);
+
+                mAdvertiser.startAdvertising(setting, data, mScannableCallback);
+                sendBroadcast(new Intent(BLE_START_SCANNABLE));
+                break;
+            case COMMAND_START_UNSCANNABLE:
+                data = generateAdvertiseData(UNSCANNABLE_UUID, UNSCANNABLE_DATA);
+                setting = generateSetting(AdvertiseSettings.ADVERTISE_TX_POWER_MEDIUM);
+
+                mAdvertiser.startAdvertising(setting, data, mUnscannableCallback);
+                sendBroadcast(new Intent(BLE_START_UNSCANNABLE));
+                break;
+            case COMMAND_STOP_SCANNABLE:
+                sendBroadcast(new Intent(BLE_STOP_SCANNABLE));
+                break;
+            case COMMAND_STOP_UNSCANNABLE:
+                sendBroadcast(new Intent(BLE_STOP_UNSCANNABLE));
+                break;
             default:
                 showMessage("Unrecognized command: " + command);
                 break;
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/bluetooth/BleScannerHardwareScanFilterActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/bluetooth/BleScannerHardwareScanFilterActivity.java
new file mode 100644
index 0000000..bf66acd
--- /dev/null
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/bluetooth/BleScannerHardwareScanFilterActivity.java
@@ -0,0 +1,174 @@
+/*
+ * Copyright (C) 2013 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;
+
+import com.android.cts.verifier.PassFailButtons;
+import com.android.cts.verifier.R;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.os.Bundle;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.View.OnClickListener;
+import android.view.ViewGroup;
+import android.widget.BaseAdapter;
+import android.widget.Button;
+import android.widget.ListView;
+import android.widget.TextView;
+
+import java.util.ArrayList;
+import java.util.Map;
+import java.util.HashMap;
+
+public class BleScannerHardwareScanFilterActivity extends PassFailButtons.Activity {
+
+    private static final String TAG = "BleScannerHardwareScanFilter";
+
+    private ListView mScanResultListView;
+    private MapAdapter mAdapter;
+
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.ble_scanner_hardware_scan_filter);
+        setPassFailButtonClickListeners();
+        setInfoResources(R.string.ble_scanner_scan_filter_name,
+                         R.string.ble_scanner_scan_filter_info, -1);
+        getPassButton().setEnabled(false);
+
+        mScanResultListView = (ListView)findViewById(R.id.ble_scan_result_list);
+        mAdapter = new MapAdapter();
+        mScanResultListView.setAdapter(mAdapter);
+
+        ((Button) findViewById(R.id.ble_scan_with_filter))
+                .setOnClickListener(new OnClickListener() {
+                    @Override
+                    public void onClick(View v) {
+                        Intent intent = new Intent(BleScannerHardwareScanFilterActivity.this,
+                                BleScannerService.class);
+                        intent.putExtra(BleScannerService.EXTRA_COMMAND,
+                                BleScannerService.COMMAND_SCAN_WITH_FILTER);
+                        startService(intent);
+                    }
+                });
+
+        ((Button) findViewById(R.id.ble_scan_without_filter))
+                .setOnClickListener(new OnClickListener() {
+                    @Override
+                    public void onClick(View v) {
+                        Intent intent = new Intent(BleScannerHardwareScanFilterActivity.this,
+                                BleScannerService.class);
+                        intent.putExtra(BleScannerService.EXTRA_COMMAND,
+                                BleScannerService.COMMAND_SCAN_WITHOUT_FILTER);
+                        startService(intent);
+                    }
+                });
+    }
+
+    @Override
+    public void onResume() {
+        super.onResume();
+
+        IntentFilter filter = new IntentFilter();
+        filter.addAction(BleScannerService.BLE_SCAN_RESULT);
+        registerReceiver(onBroadcast, filter);
+    }
+
+
+    private BroadcastReceiver onBroadcast = new BroadcastReceiver() {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            switch (intent.getAction()) {
+                case BleScannerService.BLE_SCAN_RESULT:
+                    String uuid = intent.getStringExtra(BleScannerService.EXTRA_UUID);
+                    String data = intent.getStringExtra(BleScannerService.EXTRA_DATA);
+                    if (data != null) {
+                        mAdapter.addItem(uuid + " : " + data);
+                    }
+                    break;
+            }
+        }
+    };
+
+    @Override
+    public void onPause() {
+        super.onPause();
+        unregisterReceiver(onBroadcast);
+    }
+
+    private void stop() {
+        stopService(new Intent(this, BleScannerService.class));
+    }
+
+    @Override
+    public void onDestroy() {
+        super.onDestroy();
+        stop();
+    }
+
+    public class MapAdapter extends BaseAdapter {
+        private Map<String, Integer> mData;
+        private ArrayList<String> mKeys;
+        public MapAdapter() {
+            mData = new HashMap<>();
+            mKeys = new ArrayList<>();
+        }
+
+        @Override
+        public int getCount() {
+            return mData.size();
+        }
+
+        @Override
+        public Object getItem(int position) {
+            return mData.get(mKeys.get(position));
+        }
+
+        @Override
+        public long getItemId(int arg0) {
+            return arg0;
+        }
+
+        public void addItem(String key) {
+            if (!mData.containsKey(key)) {
+                mKeys.add(key);
+                mData.put(key, new Integer(1));
+            } else {
+                mData.put(key, mData.get(key) + 1);
+            }
+            this.notifyDataSetChanged();
+        }
+
+        @Override
+        public View getView(int pos, View view, ViewGroup parent) {
+            if (view == null) {
+                view = LayoutInflater.from(parent.getContext())
+                    .inflate(android.R.layout.simple_list_item_2, parent, false);
+            }
+            String key = mKeys.get(pos);
+            String value = getItem(pos).toString();
+            TextView text1 = (TextView) view.findViewById(android.R.id.text1);
+            TextView text2 = (TextView) view.findViewById(android.R.id.text2);
+            text1.setText(key);
+            text2.setText(value);
+            return view;
+        }
+    }
+}
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/bluetooth/BleScannerService.java b/apps/CtsVerifier/src/com/android/cts/verifier/bluetooth/BleScannerService.java
index 495f89d..ee71154 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/bluetooth/BleScannerService.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/bluetooth/BleScannerService.java
@@ -46,6 +46,8 @@
 
     public static final int COMMAND_PRIVACY_MAC = 0;
     public static final int COMMAND_POWER_LEVEL = 1;
+    public static final int COMMAND_SCAN_WITH_FILTER = 2;
+    public static final int COMMAND_SCAN_WITHOUT_FILTER = 3;
 
     public static final String BLE_PRIVACY_NEW_MAC_RECEIVE =
             "com.android.cts.verifier.bluetooth.BLE_PRIVACY_NEW_MAC_RECEIVE";
@@ -55,6 +57,8 @@
             "com.android.cts.verifier.bluetooth.BLE_POWER_LEVEL";
     public static final String BLE_SCAN_RESP =
             "com.android.cts.verifier.bluetooth.BLE_SCAN_RESP";
+    public static final String BLE_SCAN_RESULT =
+            "com.android.cts.verifier.bluetooth.BLE_SCAN_RESULT";
 
     public static final String EXTRA_COMMAND =
             "com.google.cts.verifier.bluetooth.EXTRA_COMMAND";
@@ -66,6 +70,10 @@
             "com.google.cts.verifier.bluetooth.EXTRA_POWER_LEVEL";
     public static final String EXTRA_POWER_LEVEL_BIT =
             "com.google.cts.verifier.bluetooth.EXTRA_POWER_LEVEL_BIT";
+    public static final String EXTRA_UUID =
+            "com.google.cts.verifier.bluetooth.EXTRA_UUID";
+    public static final String EXTRA_DATA =
+            "com.google.cts.verifier.bluetooth.EXTRA_DATA";
 
     private static final byte MANUFACTURER_TEST_ID = (byte)0x07;
 
@@ -123,6 +131,20 @@
                         .build());
                     settingBuilder.setScanMode(ScanSettings.SCAN_MODE_LOW_LATENCY);
                     break;
+                case COMMAND_SCAN_WITH_FILTER:
+                    mScanner.stopScan(mCallback);
+                    filters.add(new ScanFilter.Builder()
+                        .setManufacturerData(MANUFACTURER_TEST_ID,
+                            new byte[]{MANUFACTURER_TEST_ID, 0})
+                        .setServiceData(new ParcelUuid(BleAdvertiserService.SCANNABLE_UUID),
+                            BleAdvertiserService.SCANNABLE_DATA)
+                        .build());
+                    settingBuilder.setScanMode(ScanSettings.SCAN_MODE_LOW_LATENCY);
+                    break;
+                case COMMAND_SCAN_WITHOUT_FILTER:
+                    mScanner.stopScan(mCallback);
+                    settingBuilder.setScanMode(ScanSettings.SCAN_MODE_LOW_LATENCY);
+                    break;
             }
             mOldMac = null;
             mScanner.startScan(filters, settingBuilder.build(), mCallback);
@@ -193,6 +215,28 @@
                 Intent responseIntent = new Intent(BLE_SCAN_RESP);
                 sendBroadcast(responseIntent);
             }
+
+            byte[] data = null;
+            String uuid = "";
+            if (serviceData.containsKey(new ParcelUuid(BleAdvertiserService.SCANNABLE_UUID))) {
+                uuid = BleAdvertiserService.SCANNABLE_UUID.toString();
+                data = serviceData.get(new ParcelUuid(BleAdvertiserService.SCANNABLE_UUID));
+            }
+            if (serviceData.containsKey(new ParcelUuid(BleAdvertiserService.UNSCANNABLE_UUID))) {
+                uuid = BleAdvertiserService.UNSCANNABLE_UUID.toString();
+                data = serviceData.get(new ParcelUuid(BleAdvertiserService.UNSCANNABLE_UUID));
+            }
+            if (uuid.length() > 0) {
+                Intent scanIntent = new Intent(BLE_SCAN_RESULT);
+                scanIntent.putExtra(EXTRA_UUID, uuid);
+                String dataStr = "{";
+                for (byte x : data) {
+                    dataStr = dataStr + " " + x;
+                }
+                dataStr = dataStr + "}";
+                scanIntent.putExtra(EXTRA_DATA, dataStr);
+                sendBroadcast(scanIntent);
+            }
         }
 
         public void onScanFailed(int errorCode) {
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/projection/IProjectionService.aidl b/apps/CtsVerifier/src/com/android/cts/verifier/projection/IProjectionService.aidl
new file mode 100644
index 0000000..0bc1b20
--- /dev/null
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/projection/IProjectionService.aidl
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2014 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.projection;
+
+import android.view.Surface;
+import android.view.KeyEvent;
+import android.view.MotionEvent;
+
+interface IProjectionService {
+    void startRendering(in Surface surface, int width, int height, int density, int viewType);
+    void stopRendering();
+    void onTouchEvent(in MotionEvent event);
+    void onKeyEvent(in KeyEvent event);
+}
\ No newline at end of file
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/projection/ProjectedPresentation.java b/apps/CtsVerifier/src/com/android/cts/verifier/projection/ProjectedPresentation.java
new file mode 100644
index 0000000..4990ed4
--- /dev/null
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/projection/ProjectedPresentation.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright (C) 2014 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.projection;
+
+import android.app.Presentation;
+import android.content.Context;
+import android.view.Display;
+import android.view.KeyEvent;
+import android.view.MotionEvent;
+import android.view.WindowManager;
+
+/**
+ * Base class for Presentations which are to be projected onto a VirtualDisplay
+ */
+public abstract class ProjectedPresentation extends Presentation {
+    public ProjectedPresentation(Context outerContext, Display display) {
+        // This theme is required to prevent an extra view from obscuring the presentation
+        super(outerContext, display, android.R.style.Theme_Holo_Light_NoActionBar_TranslucentDecor);
+
+        getWindow().setType(WindowManager.LayoutParams.TYPE_PRIVATE_PRESENTATION);
+
+        // So we can control the input
+        getWindow().addFlags(WindowManager.LayoutParams.FLAG_LOCAL_FOCUS_MODE);
+        getWindow().addFlags(WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED);
+    }
+
+    public void injectTouchEvent(MotionEvent event) {
+        getWindow().setLocalFocus(true, true);
+        getWindow().injectInputEvent(event);
+    }
+
+    public void injectKeyEvent(KeyEvent event) {
+        getWindow().setLocalFocus(true, false);
+        getWindow().injectInputEvent(event);
+    }
+
+}
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/projection/ProjectionActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/projection/ProjectionActivity.java
new file mode 100644
index 0000000..18d9d43
--- /dev/null
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/projection/ProjectionActivity.java
@@ -0,0 +1,160 @@
+/*
+ * Copyright (C) 2014 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.projection;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.ServiceConnection;
+import android.graphics.SurfaceTexture;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.util.DisplayMetrics;
+import android.util.Log;
+import android.view.MotionEvent;
+import android.view.Surface;
+import android.view.TextureView;
+import android.view.View;
+import android.view.View.OnTouchListener;
+
+import com.android.cts.verifier.PassFailButtons;
+import com.android.cts.verifier.R;
+
+/**
+ * Base activity for each projection test case. Handles the service connection and TextureView
+ * listeners
+ */
+public abstract class ProjectionActivity extends PassFailButtons.Activity
+        implements TextureView.SurfaceTextureListener {
+    private static final String TAG = ProjectionActivity.class.getSimpleName();
+    protected Intent mStartIntent;
+    protected TextureView mTextureView;
+    protected volatile Surface mSurface;
+    protected int mWidth;
+    protected int mHeight;
+    protected ProjectionPresentationType mType;
+
+    protected IProjectionService mService;
+    protected final ServiceConnection mConnection = new ServiceConnection() {
+
+        @Override
+        public void onServiceConnected(ComponentName name, IBinder binder) {
+            mService = IProjectionService.Stub.asInterface(binder);
+            new Handler().post(new Runnable() {
+
+                @Override
+                public void run() {
+                    Log.i(TAG, "onServiceConnected thread " + Thread.currentThread());
+                    DisplayMetrics metrics = ProjectionActivity.this.getResources().getDisplayMetrics();
+                    try {
+                        mService.startRendering(mSurface, mWidth, mHeight, metrics.densityDpi, mType.ordinal());
+                    } catch (RemoteException e) {
+                        Log.e(TAG, "Failed to execute startRendering", e);
+                    }
+                }
+
+            });
+
+        }
+
+        @Override
+        public void onServiceDisconnected(ComponentName name) {
+            mService = null;
+        }
+
+    };
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        Log.i(TAG, "onCreate");
+        mStartIntent = new Intent(this, ProjectionService.class);
+
+    }
+
+    protected View setContentViewAndInfoResources(int layoutId, int titleId, int infoId) {
+        View view = getLayoutInflater().inflate(layoutId, null);
+        setContentView(view);
+
+        mTextureView = (TextureView) view.findViewById(R.id.texture_view);
+        mTextureView.setSurfaceTextureListener(this);
+        mTextureView.setOnTouchListener(new OnTouchListener() {
+
+            @Override
+            public boolean onTouch(View view, MotionEvent event) {
+                if (mService != null) {
+                    try {
+                        mService.onTouchEvent(event);
+                    } catch (RemoteException e) {
+                        Log.e(TAG, "Failed to execute onTouchEvent", e);
+                    }
+                }
+                return true;
+            }
+
+        });
+
+        setInfoResources(titleId, infoId, -1);
+        setPassFailButtonClickListeners();
+        return view;
+    }
+
+    @Override
+    public void onSurfaceTextureAvailable(SurfaceTexture surface, int width, int height) {
+        Log.i(TAG, "onSurfaceTextureAvailable " + "w: " + width + " h: " + height);
+        mSurface = new Surface(surface);
+        mWidth = width;
+        mHeight = height;
+        if (mService == null) {
+            bindService(mStartIntent, mConnection,
+                    Context.BIND_AUTO_CREATE);
+        }
+
+    }
+
+    @Override
+    public boolean onSurfaceTextureDestroyed(SurfaceTexture surface) {
+        Log.i(TAG, "onSurfaceTextureDestroyed");
+        if (mService != null) {
+            try {
+                mService.stopRendering();
+            } catch (RemoteException e) {
+                Log.e(TAG, "Failed to execute stopRendering", e);
+            }
+        }
+        mSurface.release();
+        mSurface = null;
+
+        unbindService(mConnection);
+        return true;
+    }
+
+    @Override
+    public void onSurfaceTextureSizeChanged(SurfaceTexture surface, int width, int height) {
+        Log.i(TAG, "onSurfaceTextureSizeChanged " + surface.toString() + "w: " + width + " h: "
+                + height);
+    }
+
+    @Override
+    public void onSurfaceTextureUpdated(SurfaceTexture surface) {
+        Log.i(TAG, "onSurfaceTextureUpdated " + surface.toString());
+    }
+
+}
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/projection/ProjectionPresentationType.java b/apps/CtsVerifier/src/com/android/cts/verifier/projection/ProjectionPresentationType.java
new file mode 100644
index 0000000..28f5d46
--- /dev/null
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/projection/ProjectionPresentationType.java
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2014 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.projection;
+
+public enum ProjectionPresentationType {
+    TUMBLING_CUBES,
+    BASIC_WIDGETS,
+    MULTI_TOUCH,
+    SCROLLING_LIST,
+    VIDEO_PLAYBACK
+}
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/projection/ProjectionService.java b/apps/CtsVerifier/src/com/android/cts/verifier/projection/ProjectionService.java
new file mode 100644
index 0000000..bfe5a30
--- /dev/null
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/projection/ProjectionService.java
@@ -0,0 +1,147 @@
+/*
+ * Copyright (C) 2014 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.projection;
+
+import android.app.Service;
+import android.content.Intent;
+import android.hardware.display.DisplayManager;
+import android.hardware.display.VirtualDisplay;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Looper;
+import android.os.RemoteException;
+import android.util.Log;
+import android.view.KeyEvent;
+import android.view.MotionEvent;
+import android.view.Surface;
+
+import com.android.cts.verifier.projection.cube.CubePresentation;
+import com.android.cts.verifier.projection.list.ListPresentation;
+import com.android.cts.verifier.projection.touch.TouchPresentation;
+import com.android.cts.verifier.projection.video.VideoPresentation;
+import com.android.cts.verifier.projection.widgets.WidgetPresentation;
+
+/**
+ * Service to handle rendering of views on a virtual display and to forward input events to the
+ * display
+ */
+public class ProjectionService extends Service {
+    private final String TAG = ProjectionService.class.getSimpleName();
+    private final String DISPLAY_NAME = "CtsVerifier Virtual Display";
+
+    private Handler mUIHandler;
+
+    private ProjectedPresentation createPresentation(int typeOrdinal) {
+        ProjectionPresentationType type = ProjectionPresentationType.values()[typeOrdinal];
+        switch (type) {
+            case TUMBLING_CUBES:
+                return new CubePresentation(ProjectionService.this, mDisplay.getDisplay());
+
+            case BASIC_WIDGETS:
+                return new WidgetPresentation(ProjectionService.this, mDisplay.getDisplay());
+
+            case SCROLLING_LIST:
+                return new ListPresentation(ProjectionService.this, mDisplay.getDisplay());
+
+            case VIDEO_PLAYBACK:
+                return new VideoPresentation(ProjectionService.this, mDisplay.getDisplay());
+
+            case MULTI_TOUCH:
+                return new TouchPresentation(ProjectionService.this, mDisplay.getDisplay());
+        }
+
+        return null;
+    }
+
+    private class ProjectionServiceBinder extends IProjectionService.Stub {
+        @Override
+        public void startRendering(final Surface surface, final int width, final int height,
+                final int density,
+                final int viewType) throws RemoteException {
+            mUIHandler.post(new Runnable() {
+                @Override
+                public void run() {
+                    DisplayManager manager = (DisplayManager) getSystemService(DISPLAY_SERVICE);
+                    Log.i(TAG, "Surface " + surface.toString() + ": "
+                            + Boolean.toString(surface.isValid()));
+                    mDisplay = manager.createVirtualDisplay(DISPLAY_NAME, width, height, density,
+                            surface, 0);
+                    mPresentation = createPresentation(viewType);
+                    if (mPresentation == null) {
+                        return;
+                    }
+
+                    mPresentation.show();
+                }
+            });
+        }
+
+        @Override
+        public void stopRendering() throws RemoteException {
+            mUIHandler.post(new Runnable() {
+
+                @Override
+                public void run() {
+                    if (mPresentation != null) {
+                        mPresentation.dismiss();
+                        mPresentation = null;
+                    }
+                }
+
+            });
+        }
+
+        @Override
+        public void onTouchEvent(final MotionEvent event) throws RemoteException {
+            mUIHandler.post(new Runnable() {
+
+                @Override
+                public void run() {
+                    if (mPresentation != null) {
+                        mPresentation.injectTouchEvent(event);
+                    }
+                }
+
+            });
+        }
+
+        @Override
+        public void onKeyEvent(final KeyEvent event) throws RemoteException {
+            mUIHandler.post(new Runnable() {
+
+                @Override
+                public void run() {
+                    if (mPresentation != null) {
+                        mPresentation.injectKeyEvent(event);
+                    }
+                }
+
+            });
+        }
+    }
+
+    private final IBinder mBinder = new ProjectionServiceBinder();
+    private VirtualDisplay mDisplay;
+    private ProjectedPresentation mPresentation;
+
+    @Override
+    public IBinder onBind(Intent intent) {
+        Log.i(TAG, "onBind");
+        mUIHandler = new Handler(Looper.getMainLooper());
+        return mBinder;
+    }
+}
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/projection/cube/Cube.java b/apps/CtsVerifier/src/com/android/cts/verifier/projection/cube/Cube.java
new file mode 100644
index 0000000..0b521fb
--- /dev/null
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/projection/cube/Cube.java
@@ -0,0 +1,100 @@
+/*
+ * Copyright (C) 2014 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.projection.cube;
+
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.nio.IntBuffer;
+
+import javax.microedition.khronos.opengles.GL10;
+
+/**
+ * A vertex shaded cube.
+ */
+class Cube
+{
+    public Cube()
+    {
+        int one = 0x10000;
+        int vertices[] = {
+                -one, -one, -one,
+                one, -one, -one,
+                one,  one, -one,
+                -one,  one, -one,
+                -one, -one,  one,
+                one, -one,  one,
+                one,  one,  one,
+                -one,  one,  one,
+        };
+
+        int colors[] = {
+                0,    0,    0,  one,
+                one,    0,    0,  one,
+                one,  one,    0,  one,
+                0,  one,    0,  one,
+                0,    0,  one,  one,
+                one,    0,  one,  one,
+                one,  one,  one,  one,
+                0,  one,  one,  one,
+        };
+
+        byte indices[] = {
+                0, 4, 5,    0, 5, 1,
+                1, 5, 6,    1, 6, 2,
+                2, 6, 7,    2, 7, 3,
+                3, 7, 4,    3, 4, 0,
+                4, 7, 6,    4, 6, 5,
+                3, 0, 1,    3, 1, 2
+        };
+
+        // Buffers to be passed to gl*Pointer() functions
+        // must be direct, i.e., they must be placed on the
+        // native heap where the garbage collector cannot
+        // move them.
+        //
+        // Buffers with multi-byte datatypes (e.g., short, int, float)
+        // must have their byte order set to native order
+
+        ByteBuffer vbb = ByteBuffer.allocateDirect(vertices.length*4);
+        vbb.order(ByteOrder.nativeOrder());
+        mVertexBuffer = vbb.asIntBuffer();
+        mVertexBuffer.put(vertices);
+        mVertexBuffer.position(0);
+
+        ByteBuffer cbb = ByteBuffer.allocateDirect(colors.length*4);
+        cbb.order(ByteOrder.nativeOrder());
+        mColorBuffer = cbb.asIntBuffer();
+        mColorBuffer.put(colors);
+        mColorBuffer.position(0);
+
+        mIndexBuffer = ByteBuffer.allocateDirect(indices.length);
+        mIndexBuffer.put(indices);
+        mIndexBuffer.position(0);
+    }
+
+    public void draw(GL10 gl)
+    {
+        gl.glFrontFace(GL10.GL_CW);
+        gl.glVertexPointer(3, GL10.GL_FIXED, 0, mVertexBuffer);
+        gl.glColorPointer(4, GL10.GL_FIXED, 0, mColorBuffer);
+        gl.glDrawElements(GL10.GL_TRIANGLES, 36, GL10.GL_UNSIGNED_BYTE, mIndexBuffer);
+    }
+
+    private IntBuffer   mVertexBuffer;
+    private IntBuffer   mColorBuffer;
+    private ByteBuffer  mIndexBuffer;
+}
\ No newline at end of file
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/projection/cube/CubePresentation.java b/apps/CtsVerifier/src/com/android/cts/verifier/projection/cube/CubePresentation.java
new file mode 100644
index 0000000..bf7825b
--- /dev/null
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/projection/cube/CubePresentation.java
@@ -0,0 +1,60 @@
+/*
+ * Copyright (C) 2014 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.projection.cube;
+
+import android.content.Context;
+import android.opengl.GLSurfaceView;
+import android.os.Bundle;
+import android.view.Display;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.View.OnTouchListener;
+
+import com.android.cts.verifier.R;
+import com.android.cts.verifier.projection.ProjectedPresentation;
+
+
+
+/**
+ * Render tumbling cubes
+ *
+ */
+public class CubePresentation extends ProjectedPresentation {
+    public CubePresentation(Context context, Display display) {
+        super(context, display);
+    }
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        View view = getLayoutInflater().inflate(R.layout.pca_cubes, null);
+        setContentView(view);
+
+        GLSurfaceView cubeView = (GLSurfaceView) view.findViewById(R.id.cube_view);
+        final CubeRenderer renderer = new CubeRenderer(true);
+        cubeView.setRenderer(renderer);
+
+        cubeView.setOnTouchListener(new OnTouchListener() {
+
+            @Override
+            public boolean onTouch(View view, MotionEvent event) {
+                renderer.explode();
+                return true;
+            }
+        });
+    }
+}
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/projection/cube/CubeRenderer.java b/apps/CtsVerifier/src/com/android/cts/verifier/projection/cube/CubeRenderer.java
new file mode 100644
index 0000000..9205fec
--- /dev/null
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/projection/cube/CubeRenderer.java
@@ -0,0 +1,128 @@
+/*
+ * Copyright (C) 2014 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.projection.cube;
+
+import android.opengl.GLSurfaceView;
+
+import javax.microedition.khronos.egl.EGLConfig;
+import javax.microedition.khronos.opengles.GL10;
+
+/**
+ * Render a pair of tumbling cubes.
+ */
+public class CubeRenderer implements GLSurfaceView.Renderer {
+
+    private final boolean mTranslucentBackground;
+
+    private final Cube mCube;
+    private float mAngle;
+    private float mScale = 1.0f;
+    private boolean mExploding;
+
+    public CubeRenderer(boolean useTranslucentBackground) {
+        mTranslucentBackground = useTranslucentBackground;
+        mCube = new Cube();
+    }
+
+    public void explode() {
+        mExploding = true;
+    }
+
+    @Override
+    public void onDrawFrame(GL10 gl) {
+        /*
+         * Usually, the first thing one might want to do is to clear
+         * the screen. The most efficient way of doing this is to use
+         * glClear().
+         */
+
+        gl.glClear(GL10.GL_COLOR_BUFFER_BIT | GL10.GL_DEPTH_BUFFER_BIT);
+
+        /*
+         * Now we're ready to draw some 3D objects
+         */
+
+        gl.glMatrixMode(GL10.GL_MODELVIEW);
+        gl.glLoadIdentity();
+        gl.glTranslatef(0, 0, -3.0f);
+        gl.glRotatef(mAngle,        0, 1, 0);
+        gl.glRotatef(mAngle*0.25f,  1, 0, 0);
+
+        gl.glEnableClientState(GL10.GL_VERTEX_ARRAY);
+        gl.glEnableClientState(GL10.GL_COLOR_ARRAY);
+
+        gl.glScalef(mScale, mScale, mScale);
+        mCube.draw(gl);
+
+        gl.glRotatef(mAngle*2.0f, 0, 1, 1);
+        gl.glTranslatef(0.5f, 0.5f, 0.5f);
+
+        mCube.draw(gl);
+
+        mAngle += 1.2f;
+
+        if (mExploding) {
+            mScale *= 1.02f;
+            if (mScale > 4.0f) {
+                mScale = 1.0f;
+                mExploding = false;
+            }
+        }
+    }
+
+    @Override
+    public void onSurfaceChanged(GL10 gl, int width, int height) {
+        gl.glViewport(0, 0, width, height);
+
+        /*
+         * Set our projection matrix. This doesn't have to be done
+         * each time we draw, but usually a new projection needs to
+         * be set when the viewport is resized.
+         */
+
+        float ratio = (float) width / height;
+        gl.glMatrixMode(GL10.GL_PROJECTION);
+        gl.glLoadIdentity();
+        gl.glFrustumf(-ratio, ratio, -1, 1, 1, 10);
+    }
+
+    @Override
+    public void onSurfaceCreated(GL10 gl, EGLConfig config) {
+        /*
+         * By default, OpenGL enables features that improve quality
+         * but reduce performance. One might want to tweak that
+         * especially on software renderer.
+         */
+        gl.glDisable(GL10.GL_DITHER);
+
+        /*
+         * Some one-time OpenGL initialization can be made here
+         * probably based on features of this particular context
+         */
+        gl.glHint(GL10.GL_PERSPECTIVE_CORRECTION_HINT,
+                GL10.GL_FASTEST);
+
+        if (mTranslucentBackground) {
+            gl.glClearColor(0,0,0,0);
+        } else {
+            gl.glClearColor(1,1,1,1);
+        }
+        gl.glEnable(GL10.GL_CULL_FACE);
+        gl.glShadeModel(GL10.GL_SMOOTH);
+        gl.glEnable(GL10.GL_DEPTH_TEST);
+    }
+}
\ No newline at end of file
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/projection/cube/ProjectionCubeActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/projection/cube/ProjectionCubeActivity.java
new file mode 100644
index 0000000..0ef9a30
--- /dev/null
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/projection/cube/ProjectionCubeActivity.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2014 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.projection.cube;
+
+import com.android.cts.verifier.R;
+
+import android.os.Bundle;
+import android.util.Log;
+
+import com.android.cts.verifier.projection.ProjectionActivity;
+import com.android.cts.verifier.projection.ProjectionPresentationType;
+
+public class ProjectionCubeActivity extends ProjectionActivity {
+    private static final String TAG = ProjectionCubeActivity.class.getSimpleName();
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        Log.i(TAG, "onCreate");
+        setContentViewAndInfoResources(R.layout.pa_main, R.string.pca_test, R.string.pca_info);
+        mType = ProjectionPresentationType.TUMBLING_CUBES;
+    }
+}
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/projection/list/ListPresentation.java b/apps/CtsVerifier/src/com/android/cts/verifier/projection/list/ListPresentation.java
new file mode 100644
index 0000000..dad4945
--- /dev/null
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/projection/list/ListPresentation.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright (C) 2014 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.projection.list;
+
+import android.content.Context;
+import android.os.Bundle;
+import android.view.Display;
+import android.view.View;
+import android.widget.ArrayAdapter;
+import android.widget.ListView;
+
+import com.android.cts.verifier.R;
+import com.android.cts.verifier.projection.ProjectedPresentation;
+
+import java.util.ArrayList;
+
+/**
+ * Render a list view that scrolls
+ */
+public class ListPresentation extends ProjectedPresentation {
+    private ArrayList<String> mItemList = new ArrayList<String>();
+    private static final int NUM_ITEMS = 50; // Enough to make the list scroll
+
+    /**
+     * @param outerContext
+     * @param display
+     */
+    public ListPresentation(Context outerContext, Display display) {
+        super(outerContext, display);
+    }
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        View view = getLayoutInflater().inflate(R.layout.pla_list, null);
+        setContentView(view);
+
+        for (int i = 0; i < NUM_ITEMS; ++i) {
+            mItemList.add("Item #" + i);
+        }
+
+        ListView listView = (ListView) view.findViewById(R.id.pla_list);
+
+        listView.setAdapter(new ArrayAdapter<String>(getContext(),
+                android.R.layout.simple_list_item_1, mItemList));
+    }
+
+}
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/projection/list/ProjectionListActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/projection/list/ProjectionListActivity.java
new file mode 100644
index 0000000..c166320
--- /dev/null
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/projection/list/ProjectionListActivity.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2014 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.projection.list;
+
+import android.os.Bundle;
+import android.util.Log;
+
+import com.android.cts.verifier.R;
+import com.android.cts.verifier.projection.ProjectionActivity;
+import com.android.cts.verifier.projection.ProjectionPresentationType;
+import com.android.cts.verifier.projection.cube.ProjectionCubeActivity;
+
+public class ProjectionListActivity extends ProjectionActivity {
+    private static final String TAG = ProjectionCubeActivity.class.getSimpleName();
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        Log.i(TAG, "onCreate");
+        setContentViewAndInfoResources(R.layout.pa_main, R.string.pla_test, R.string.pla_info);
+        mType = ProjectionPresentationType.SCROLLING_LIST;
+    }
+}
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/projection/touch/ProjectionTouchActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/projection/touch/ProjectionTouchActivity.java
new file mode 100644
index 0000000..ed1d881
--- /dev/null
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/projection/touch/ProjectionTouchActivity.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2014 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.projection.touch;
+
+import android.os.Bundle;
+import android.util.Log;
+
+import com.android.cts.verifier.R;
+import com.android.cts.verifier.projection.ProjectionActivity;
+import com.android.cts.verifier.projection.ProjectionPresentationType;
+import com.android.cts.verifier.projection.cube.ProjectionCubeActivity;
+
+public class ProjectionTouchActivity extends ProjectionActivity {
+    private static final String TAG = ProjectionCubeActivity.class.getSimpleName();
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        Log.i(TAG, "onCreate");
+        setContentViewAndInfoResources(R.layout.pa_main, R.string.pta_test, R.string.pta_info);
+        mType = ProjectionPresentationType.MULTI_TOUCH;
+    }
+}
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/projection/touch/TouchPointView.java b/apps/CtsVerifier/src/com/android/cts/verifier/projection/touch/TouchPointView.java
new file mode 100644
index 0000000..c88fd79
--- /dev/null
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/projection/touch/TouchPointView.java
@@ -0,0 +1,98 @@
+/*
+ * Copyright (C) 2014 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.projection.touch;
+
+import android.content.Context;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Paint;
+import android.graphics.Point;
+import android.util.AttributeSet;
+import android.view.MotionEvent;
+import android.view.View;
+
+import java.util.ArrayList;
+import java.util.List;
+/*
+ * Simple view that draws a circle where a touch is registered
+ */
+public class TouchPointView extends View {
+    @SuppressWarnings("unused")
+    private static final String TAG = TouchPointView.class.getSimpleName();
+
+    private final int[] mColors = {
+            Color.RED,
+            Color.GREEN,
+            Color.BLUE,
+            Color.YELLOW,
+            Color.MAGENTA,
+            Color.BLACK,
+            Color.DKGRAY
+    };
+    List<Finger> mFingers;
+    Paint mPaint;
+
+    public TouchPointView(Context context, AttributeSet attrs) {
+        this(context, attrs, 0);
+    }
+
+    public TouchPointView(Context context, AttributeSet attrs, int defStyle) {
+        super(context, attrs, defStyle);
+        mFingers = new ArrayList<Finger>();
+
+        mPaint = new Paint();
+        mPaint.setStyle(Paint.Style.FILL);
+    }
+
+    @Override
+    public boolean onTouchEvent(MotionEvent event) {
+        mFingers.clear();
+        if (event.getActionMasked() == MotionEvent.ACTION_UP) {
+            invalidate();
+            return true;
+        }
+        for (int i = 0; i < event.getPointerCount(); i++) {
+            int pointerId = event.getPointerId(i);
+            int pointerIndex = event.findPointerIndex(pointerId);
+            Finger finger = new Finger();
+            finger.point =  new Point((int)event.getX(pointerIndex), (int)event.getY(pointerIndex));
+            finger.pointerId = pointerId;
+
+            mFingers.add(finger);
+        }
+        invalidate();
+        return true;
+    }
+
+    @Override
+    public void onDraw(Canvas canvas) {
+        int radius = canvas.getWidth() / 20;
+        for (int i = 0; i < mFingers.size(); i++) {
+            Finger finger = mFingers.get(i);
+            Point point = finger.point;
+            int color = mColors[finger.pointerId % mColors.length];
+            mPaint.setColor(color);
+            canvas.drawCircle(point.x, point.y, radius, mPaint);
+        }
+    }
+
+    private class Finger {
+        public Point point;
+        public int pointerId;
+    }
+}
+
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/projection/touch/TouchPresentation.java b/apps/CtsVerifier/src/com/android/cts/verifier/projection/touch/TouchPresentation.java
new file mode 100644
index 0000000..84834b5
--- /dev/null
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/projection/touch/TouchPresentation.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2014 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.projection.touch;
+
+import android.content.Context;
+import android.os.Bundle;
+import android.view.Display;
+
+import com.android.cts.verifier.R;
+import com.android.cts.verifier.projection.ProjectedPresentation;
+
+/**
+ * Draw the TouchPointview to see if multitouch works in projected presentations
+ */
+public class TouchPresentation extends ProjectedPresentation {
+
+    /**
+     * @param outerContext
+     * @param display
+     */
+    public TouchPresentation(Context outerContext, Display display) {
+        super(outerContext, display);
+    }
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.pta_touch);
+    }
+
+}
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/projection/video/ProjectionVideoActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/projection/video/ProjectionVideoActivity.java
new file mode 100644
index 0000000..7a32b27
--- /dev/null
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/projection/video/ProjectionVideoActivity.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2014 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.projection.video;
+
+import android.os.Bundle;
+import android.util.Log;
+
+import com.android.cts.verifier.R;
+import com.android.cts.verifier.projection.ProjectionActivity;
+import com.android.cts.verifier.projection.ProjectionPresentationType;
+
+public class ProjectionVideoActivity extends ProjectionActivity {
+    private static final String TAG = ProjectionVideoActivity.class.getSimpleName();
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        Log.i(TAG, "onCreate");
+        setContentViewAndInfoResources(R.layout.pa_main, R.string.pva_test, R.string.pva_info);
+        mType = ProjectionPresentationType.VIDEO_PLAYBACK;
+    }
+}
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/projection/video/VideoPresentation.java b/apps/CtsVerifier/src/com/android/cts/verifier/projection/video/VideoPresentation.java
new file mode 100644
index 0000000..4275cb8
--- /dev/null
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/projection/video/VideoPresentation.java
@@ -0,0 +1,60 @@
+/*
+ * Copyright (C) 2014 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.projection.video;
+
+import android.content.Context;
+import android.media.MediaPlayer;
+import android.media.MediaPlayer.OnPreparedListener;
+import android.net.Uri;
+import android.os.Bundle;
+import android.view.Display;
+import android.view.View;
+import android.widget.VideoView;
+
+import com.android.cts.verifier.R;
+import com.android.cts.verifier.projection.ProjectedPresentation;
+
+/**
+ * Play a test video that determines if the video and audio are in sync in projected presentations
+ */
+public class VideoPresentation extends ProjectedPresentation {
+
+    /**
+     * @param outerContext
+     * @param display
+     */
+    public VideoPresentation(Context outerContext, Display display) {
+        super(outerContext, display);
+    }
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        View view = getLayoutInflater().inflate(R.layout.pva_video, null);
+        setContentView(view);
+        VideoView videoView = (VideoView) view.findViewById(R.id.video_view);
+        videoView.setOnPreparedListener(new OnPreparedListener() {
+            @Override
+            public void onPrepared(MediaPlayer mp) {
+                mp.setLooping(true);
+            }
+        });
+        String packageName = getContext().getPackageName();
+        Uri uri = Uri.parse("android.resource://" + packageName + "/" + R.raw.test_video);
+        videoView.setVideoURI(uri);
+        videoView.start();
+    }
+}
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/projection/widgets/ProjectionWidgetActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/projection/widgets/ProjectionWidgetActivity.java
new file mode 100644
index 0000000..9b862de
--- /dev/null
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/projection/widgets/ProjectionWidgetActivity.java
@@ -0,0 +1,79 @@
+/*
+ * Copyright (C) 2014 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.projection.widgets;
+
+import android.os.Bundle;
+import android.os.RemoteException;
+import android.util.Log;
+import android.view.KeyEvent;
+import android.view.View;
+import android.view.View.OnClickListener;
+import android.widget.Button;
+
+import com.android.cts.verifier.R;
+import com.android.cts.verifier.projection.ProjectionActivity;
+import com.android.cts.verifier.projection.ProjectionPresentationType;
+
+public class ProjectionWidgetActivity extends ProjectionActivity {
+    private static final String TAG = ProjectionWidgetActivity.class.getSimpleName();
+
+    private class InjectDPadClickListener implements OnClickListener {
+        private int mKeyCode;
+
+        InjectDPadClickListener(int keyCode) {
+            mKeyCode = keyCode;
+        }
+
+        @Override
+        public void onClick(View view) {
+            if (mService != null) {
+                try {
+                    mService.onKeyEvent(new KeyEvent(KeyEvent.ACTION_DOWN, mKeyCode));
+                } catch (RemoteException e) {
+                    Log.e(TAG, "Error executing onKeyEvent", e);
+                }
+            }
+        }
+    }
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        View view = setContentViewAndInfoResources(R.layout.pwa_widgets, R.string.pwa_test,
+                R.string.pwa_info);
+
+        mType = ProjectionPresentationType.BASIC_WIDGETS;
+
+        Button button;
+        {
+            button = (Button) view.findViewById(R.id.up_button);
+            button.setOnClickListener(new InjectDPadClickListener(KeyEvent.KEYCODE_DPAD_UP));
+        }
+        {
+            button = (Button) view.findViewById(R.id.down_button);
+            button.setOnClickListener(new InjectDPadClickListener(KeyEvent.KEYCODE_DPAD_DOWN));
+        }
+        {
+            button = (Button) view.findViewById(R.id.right_button);
+            button.setOnClickListener(new InjectDPadClickListener(KeyEvent.KEYCODE_DPAD_RIGHT));
+        }
+        {
+            button = (Button) view.findViewById(R.id.left_button);
+            button.setOnClickListener(new InjectDPadClickListener(KeyEvent.KEYCODE_DPAD_LEFT));
+        }
+    }
+}
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/projection/widgets/WidgetPresentation.java b/apps/CtsVerifier/src/com/android/cts/verifier/projection/widgets/WidgetPresentation.java
new file mode 100644
index 0000000..8af2757
--- /dev/null
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/projection/widgets/WidgetPresentation.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2014 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.projection.widgets;
+
+import android.content.Context;
+import android.os.Bundle;
+import android.view.Display;
+
+import com.android.cts.verifier.R;
+import com.android.cts.verifier.projection.ProjectedPresentation;
+import com.android.cts.verifier.projection.ProjectionPresentationType;
+
+/**
+ * Check if widgets display and that key focus works in projected mode
+ */
+public class WidgetPresentation extends ProjectedPresentation {
+
+    /**
+     * @param outerContext
+     * @param display
+     */
+    public WidgetPresentation(Context outerContext, Display display) {
+        super(outerContext, display);
+    }
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.pwa_buttons);
+    }
+
+}
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/sensors/AccelerometerMeasurementTestActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/sensors/AccelerometerMeasurementTestActivity.java
index 5a0af28..edac3c5 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/sensors/AccelerometerMeasurementTestActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/sensors/AccelerometerMeasurementTestActivity.java
@@ -26,30 +26,43 @@
 /**
  * Semi-automated test that focuses on characteristics associated with Accelerometer measurements.
  */
-public class AccelerometerMeasurementTestActivity extends BaseSensorSemiAutomatedTestActivity {
-    @Override
-    protected void onRun() throws Throwable {
-        verifyMeasurements(
+public class AccelerometerMeasurementTestActivity extends BaseSensorTestActivity {
+    public AccelerometerMeasurementTestActivity() {
+        super(AccelerometerMeasurementTestActivity.class);
+    }
+
+    public String testFaceUp() throws Throwable {
+        return verifyMeasurements(
                 "Place the device in a flat surface with the screen facing the ceiling",
                 0, 0, SensorManager.STANDARD_GRAVITY);
+    }
 
-        delayedVerifyMeasurements(
+    public String testFaceDown() throws Throwable {
+        return delayedVerifyMeasurements(
                 "Press 'Next' and place the device in a flat surface with the screen facing it",
                 0, 0, -SensorManager.STANDARD_GRAVITY);
+    }
 
-        verifyMeasurements(
+    public String testRightSide() throws Throwable {
+        return verifyMeasurements(
                 "Place the device in a flat surface resting vertically on its right side",
                 -SensorManager.STANDARD_GRAVITY, 0, 0);
+    }
 
-        verifyMeasurements(
+    public String testLeftSide() throws Throwable {
+        return verifyMeasurements(
                 "Place the device in a flat surface resting vertically on its left side",
                 SensorManager.STANDARD_GRAVITY, 0, 0);
+    }
 
-        verifyMeasurements(
+    public String testTopSide() throws Throwable {
+        return verifyMeasurements(
                 "Place the device in a flat surface resting vertically on its top side",
                 0, -SensorManager.STANDARD_GRAVITY, 0);
+    }
 
-        verifyMeasurements(
+    public String testBottomSide() throws Throwable {
+        return verifyMeasurements(
                 "Place the device in a flat surface resting vertically on its bottom side",
                 0, SensorManager.STANDARD_GRAVITY, 0);
     }
@@ -73,7 +86,7 @@
      * - the values representing the expectation of the test
      * - the mean of values sampled from the sensor
      */
-    private void verifyMeasurements(float ... expectations) throws Throwable {
+    private String verifyMeasurements(float ... expectations) throws Throwable {
         Thread.sleep(500 /*ms*/);
         TestSensorOperation verifyMeasurements = new TestSensorOperation(
                 getApplicationContext(),
@@ -85,10 +98,10 @@
                 expectations,
                 new float[]{1.95f, 1.95f, 1.95f} /* m / s^2 */));
         verifyMeasurements.execute();
-        logSuccess();
+        return null;
     }
 
-    private void delayedVerifyMeasurements(
+    private String delayedVerifyMeasurements(
             String message,
             float ... expectations) throws Throwable {
         appendText(String.format("\n%s.", message));
@@ -97,17 +110,17 @@
         Thread.sleep(TimeUnit.MILLISECONDS.convert(10, TimeUnit.SECONDS));
 
         try {
-            verifyMeasurements(expectations);
+            return verifyMeasurements(expectations);
         } finally {
             playSound();
         }
     }
 
-    private void verifyMeasurements(String message, float ... expectations) throws Throwable {
+    private String verifyMeasurements(String message, float ... expectations) throws Throwable {
         appendText(String.format("\n%s.", message));
         appendText("Press 'Next' when ready and keep the device steady.");
         waitForUser();
 
-        verifyMeasurements(expectations);
+        return verifyMeasurements(expectations);
     }
 }
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/sensors/BaseSensorSemiAutomatedTestActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/sensors/BaseSensorSemiAutomatedTestActivity.java
index c634e16..a550cde 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/sensors/BaseSensorSemiAutomatedTestActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/sensors/BaseSensorSemiAutomatedTestActivity.java
@@ -16,64 +16,20 @@
 
 package com.android.cts.verifier.sensors;
 
-import android.app.Activity;
 import android.graphics.Color;
 import android.hardware.cts.helpers.SensorNotSupportedException;
-import android.media.MediaPlayer;
-import android.os.Bundle;
-import android.provider.Settings;
-import android.text.Spannable;
-import android.text.SpannableStringBuilder;
-import android.text.method.ScrollingMovementMethod;
-import android.text.style.ForegroundColorSpan;
-import android.view.View;
-import android.widget.TextView;
-
-import com.android.cts.verifier.R;
-import com.android.cts.verifier.TestResult;
-
-import java.security.InvalidParameterException;
-import java.util.concurrent.Semaphore;
 
 /**
  * Base class to author Sensor semi-automated test cases.
  * These tests can only wait for operators to notify at some intervals, but the test needs to be
  * autonomous to verify the data collected.
+ *
+ * @deprecated use {@link BaseSensorTestActivity} instead.
  */
-public abstract class BaseSensorSemiAutomatedTestActivity
-        extends Activity
-        implements View.OnClickListener, Runnable {
-    protected final String LOG_TAG = "TestRunner";
-
-    private final Semaphore mSemaphore = new Semaphore(0);
-
-    private TextView mLogView;
-    private View mNextView;
-    private Thread mWorkerThread;
-
-    @Override
-    protected void onCreate(Bundle savedInstanceState) {
-        super.onCreate(savedInstanceState);
-        setContentView(R.layout.snsr_semi_auto_test);
-    }
-
-    @Override
-    public void onStart() {
-        super.onStart();
-
-        mLogView = (TextView) this.findViewById(R.id.log_text);
-        mNextView = this.findViewById(R.id.next_button);
-        mNextView.setOnClickListener(this);
-        mLogView.setMovementMethod(new ScrollingMovementMethod());
-
-        updateButton(false /*enabled*/);
-        mWorkerThread = new Thread(this);
-        mWorkerThread.start();
-    }
-
-    @Override
-    public void onClick(View target) {
-        mSemaphore.release();
+@Deprecated
+public abstract class BaseSensorSemiAutomatedTestActivity extends BaseSensorTestActivity {
+    public BaseSensorSemiAutomatedTestActivity() {
+        super(BaseSensorSemiAutomatedTestActivity.class);
     }
 
     @Override
@@ -90,7 +46,7 @@
             testResult = SensorTestResult.FAIL;
             message = e.getMessage();
         }
-        setTestResult(testResult, message);
+        setTestResult(getTestId(), testResult, message);
         appendText("\nTest completed. Press 'Next' to finish.\n");
         waitForUser();
         finish();
@@ -106,125 +62,11 @@
      */
     protected abstract void onRun() throws Throwable;
 
-    /**
-     * Helper methods for subclasses to interact with the UI and the operator.
-     */
-    protected void appendText(String text, int textColor) {
-        this.runOnUiThread(new TextAppender(mLogView, text, textColor));
-    }
-
-    protected void appendText(String text) {
-        this.runOnUiThread(new TextAppender(mLogView, text));
-    }
-
-    protected void clearText() {
-        this.runOnUiThread(new Runnable() {
-            @Override
-            public void run() {
-                mLogView.setText("");
-            }
-        });
-    }
-
-    protected void updateButton(boolean enabled) {
-        this.runOnUiThread(new ButtonEnabler(this.mNextView, enabled));
-    }
-
-    protected void waitForUser() {
-        updateButton(true);
-        try {
-            mSemaphore.acquire();
-        } catch(InterruptedException e) {}
-        updateButton(false);
-    }
-
     protected void logSuccess() {
-        appendText("PASS", Color.GREEN);
+        appendText("SUCCESS", Color.GREEN);
     }
 
-    protected void playSound() {
-        MediaPlayer player = MediaPlayer.create(this, Settings.System.DEFAULT_NOTIFICATION_URI);
-        player.start();
-        try {
-            Thread.sleep(500);
-        } catch(InterruptedException e) {
-        } finally {
-            player.stop();
-        }
-    }
-
-    /**
-     * Private methods.
-     */
     private String getTestId() {
         return this.getClass().getName();
     }
-
-    private void setTestResult(SensorTestResult testResult, String message) {
-        int textColor;
-        switch(testResult) {
-            case SKIPPED:
-                textColor = Color.YELLOW;
-                TestResult.setPassedResult(this, this.getTestId(), message);
-                break;
-            case PASS:
-                textColor = Color.GREEN;
-                TestResult.setPassedResult(this, this.getTestId(), message);
-                break;
-            case FAIL:
-                textColor = Color.RED;
-                TestResult.setFailedResult(this, this.getTestId(), message);
-                break;
-            default:
-                throw new InvalidParameterException("Unrecognized testResult.");
-        }
-        appendText(message, textColor);
-    }
-
-    private enum SensorTestResult {
-        SKIPPED,
-        PASS,
-        FAIL
-    }
-
-    private class TextAppender implements Runnable {
-        private final TextView mTextView;
-        private final SpannableStringBuilder mMessageBuilder;
-
-        public TextAppender(TextView textView, String message, int textColor) {
-            mTextView = textView;
-            mMessageBuilder = new SpannableStringBuilder(message + "\n");
-
-            ForegroundColorSpan colorSpan = new ForegroundColorSpan(textColor);
-            mMessageBuilder.setSpan(
-                    colorSpan,
-                    0 /*start*/,
-                    message.length(),
-                    Spannable.SPAN_INCLUSIVE_INCLUSIVE);
-        }
-
-        public TextAppender(TextView textView, String message) {
-            this(textView, message, textView.getCurrentTextColor());
-        }
-
-        @Override
-        public void run() {
-            mTextView.append(mMessageBuilder);
-        }
-    }
-
-    private class ButtonEnabler implements Runnable {
-        private final View mButtonView;
-        private final boolean mButtonEnabled;
-
-        public ButtonEnabler(View buttonView, boolean buttonEnabled) {
-            mButtonView = buttonView;
-            mButtonEnabled = buttonEnabled;
-        }
-
-        @Override
-        public void run() {
-            mButtonView.setEnabled(mButtonEnabled);
-        }
-    }
 }
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/sensors/BaseSensorTestActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/sensors/BaseSensorTestActivity.java
new file mode 100644
index 0000000..01616e1
--- /dev/null
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/sensors/BaseSensorTestActivity.java
@@ -0,0 +1,415 @@
+/*
+ * Copyright (C) 2014 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.sensors;
+
+import com.android.cts.verifier.R;
+import com.android.cts.verifier.TestResult;
+
+import android.annotation.NonNull;
+import android.app.Activity;
+import android.content.ContentResolver;
+import android.content.Intent;
+import android.graphics.Color;
+import android.hardware.cts.helpers.SensorNotSupportedException;
+import android.media.MediaPlayer;
+import android.os.Build;
+import android.os.Bundle;
+import android.provider.Settings;
+import android.text.Spannable;
+import android.text.SpannableStringBuilder;
+import android.text.TextUtils;
+import android.text.method.ScrollingMovementMethod;
+import android.text.style.ForegroundColorSpan;
+import android.util.Log;
+import android.view.View;
+import android.widget.TextView;
+
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
+import java.security.InvalidParameterException;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.Semaphore;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Base class to author Sensor test cases. It provides access to the following flow:
+ *      Activity set up
+ *          for test unit : all test units
+ *              execute test unit
+ *      Activity clean up
+ *
+ * Each test unit can wait for operators to notify at some intervals, but the test needs to be
+ * autonomous to verify the data collected.
+ */
+public abstract class BaseSensorTestActivity
+        extends Activity
+        implements View.OnClickListener, Runnable {
+    protected final String LOG_TAG = "TestRunner";
+
+    protected final Class mTestClass;
+
+    private final Semaphore mSemaphore = new Semaphore(0);
+
+    private TextView mLogView;
+    private View mNextView;
+    private Thread mWorkerThread;
+    private CountDownLatch mCountDownLatch;
+
+    protected BaseSensorTestActivity(Class testClass) {
+        mTestClass = testClass;
+    }
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.snsr_semi_auto_test);
+
+        mLogView = (TextView) this.findViewById(R.id.log_text);
+        mNextView = this.findViewById(R.id.next_button);
+        mNextView.setOnClickListener(this);
+        mLogView.setMovementMethod(new ScrollingMovementMethod());
+
+        updateButton(false /*enabled*/);
+        mWorkerThread = new Thread(this);
+        mWorkerThread.start();
+    }
+
+    @Override
+    public void onClick(View target) {
+        mSemaphore.release();
+    }
+
+    @Override
+    public void run() {
+        String testClassName = mTestClass.getName();
+
+        try {
+            activitySetUp();
+        } catch (Throwable e) {
+            setTestResult(testClassName, SensorTestResult.SKIPPED, e.getMessage());
+            return;
+        }
+
+        // TODO: it might be necessary to implement fall through so passed tests do not need to
+        //       be re-executed
+        int testPassedCounter = 0;
+        int testSkippedCounter = 0;
+        int testFailedCounter = 0;
+        for (Method testMethod : findTestMethods()) {
+            String testName = String.format("%s.%s", testClassName, testMethod.getName());
+            try {
+                appendText("\nExecuting test case '" + testName + "'...");
+                String testDetails = (String) testMethod.invoke(this);
+                setTestResult(testName, SensorTestResult.PASS, testDetails);
+                ++testPassedCounter;
+            } catch (InvocationTargetException e) {
+                // get the inner exception, because we use reflection APIs to execute the test
+                Throwable cause = e.getCause();
+                SensorTestResult testResult;
+                if (cause instanceof SensorNotSupportedException) {
+                    testResult = SensorTestResult.SKIPPED;
+                    ++testSkippedCounter;
+                } else {
+                    testResult = SensorTestResult.FAIL;
+                    ++testFailedCounter;
+                }
+                setTestResult(testName, testResult, cause.getMessage());
+            } catch (Throwable e) {
+                setTestResult(testName, SensorTestResult.FAIL, e.getMessage());
+                ++testFailedCounter;
+            }
+        }
+        setOverallTestResult(
+                testClassName,
+                testPassedCounter,
+                testSkippedCounter,
+                testFailedCounter);
+
+        try {
+            activityCleanUp();
+        } catch (Throwable e) {
+            appendText("An error occurred on Activity CleanUp.");
+            appendText(e.getLocalizedMessage(), Color.RED);
+        }
+
+        appendText("\nTest completed. Press 'Next' to finish.\n");
+        waitForUser();
+        finish();
+    }
+
+    @Override
+    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
+        mCountDownLatch.countDown();
+    }
+
+    protected enum SensorTestResult {
+        SKIPPED,
+        PASS,
+        FAIL
+    }
+
+    /**
+     * For use only by {@link BaseSensorSemiAutomatedTestActivity} and other base classes.
+     */
+    protected void setTestResult(String testId, SensorTestResult testResult, String testDetails) {
+        int textColor;
+        int logPriority;
+        String testResultString;
+        switch(testResult) {
+            case SKIPPED:
+                textColor = Color.YELLOW;
+                logPriority = Log.INFO;
+                testResultString = "SKIPPED";
+                TestResult.setPassedResult(this, testId, testDetails);
+                break;
+            case PASS:
+                textColor = Color.GREEN;
+                logPriority = Log.DEBUG;
+                testResultString = "PASS";
+                TestResult.setPassedResult(this, testId, testDetails);
+                break;
+            case FAIL:
+                textColor = Color.RED;
+                logPriority = Log.ERROR;
+                testResultString = "FAIL";
+                TestResult.setFailedResult(this, testId, testDetails);
+                break;
+            default:
+                throw new InvalidParameterException("Unrecognized testResult.");
+        }
+        if (TextUtils.isEmpty(testDetails)) {
+            testDetails = testResultString;
+        }
+        appendText(testDetails, textColor);
+        Log.println(logPriority, LOG_TAG, testDetails);
+    }
+
+    /**
+     * A general set up routine. It executes only once before the first test case.
+     *
+     * @throws Throwable An exception that denotes the failure of set up. No tests will be executed.
+     */
+    protected void activitySetUp() throws Throwable {}
+
+    /**
+     * A general clean up routine. It executes upon successful execution of {@link #activitySetUp()}
+     * and after all the test cases.
+     *
+     * @throws Throwable An exception that will be logged and ignored, for ease of implementation
+     *                   by subclasses.
+     */
+    protected void activityCleanUp() throws Throwable {}
+
+    protected void appendText(String text, int textColor) {
+        this.runOnUiThread(new TextAppender(mLogView, text, textColor));
+    }
+
+    protected void appendText(String text) {
+        this.runOnUiThread(new TextAppender(mLogView, text));
+    }
+
+    protected void clearText() {
+        this.runOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                mLogView.setText("");
+            }
+        });
+    }
+
+    protected void updateButton(boolean enabled) {
+        this.runOnUiThread(new ButtonEnabler(this.mNextView, enabled));
+    }
+
+    protected void waitForUser() {
+        updateButton(true);
+        try {
+            mSemaphore.acquire();
+        } catch(InterruptedException e) {}
+        updateButton(false);
+    }
+
+    protected void playSound() {
+        MediaPlayer player = MediaPlayer.create(this, Settings.System.DEFAULT_NOTIFICATION_URI);
+        player.start();
+        try {
+            Thread.sleep(500);
+        } catch(InterruptedException e) {
+        } finally {
+            player.stop();
+        }
+    }
+
+    @NonNull
+    private List<Method> findTestMethods() {
+        ArrayList<Method> testMethods = new ArrayList<Method>();
+        for (Method method : mTestClass.getDeclaredMethods()) {
+            if (Modifier.isPublic(method.getModifiers())
+                    && method.getParameterTypes().length == 0
+                    && method.getName().startsWith("test")
+                    && method.getReturnType().equals(String.class)) {
+                testMethods.add(method);
+            }
+        }
+        return testMethods;
+    }
+
+    private void setOverallTestResult(
+            String testClassName,
+            int testPassedCount,
+            int testSkippedCount,
+            int testFailedCount) {
+        SensorTestResult overallTestResult = SensorTestResult.PASS;
+        if (testFailedCount > 0) {
+            overallTestResult = SensorTestResult.FAIL;
+        } else if (testSkippedCount > 0 || testPassedCount == 0) {
+            overallTestResult = SensorTestResult.SKIPPED;
+        }
+
+        String testSummary = String.format(
+                "\n\nTestsPassed=%d, TestsSkipped=%d, TestFailed=%d",
+                testPassedCount,
+                testSkippedCount,
+                testFailedCount);
+        setTestResult(testClassName, overallTestResult, testSummary);
+    }
+
+    private class TextAppender implements Runnable {
+        private final TextView mTextView;
+        private final SpannableStringBuilder mMessageBuilder;
+
+        public TextAppender(TextView textView, String message, int textColor) {
+            mTextView = textView;
+            mMessageBuilder = new SpannableStringBuilder(message + "\n");
+
+            ForegroundColorSpan colorSpan = new ForegroundColorSpan(textColor);
+            mMessageBuilder.setSpan(
+                    colorSpan,
+                    0 /*start*/,
+                    message.length(),
+                    Spannable.SPAN_INCLUSIVE_INCLUSIVE);
+        }
+
+        public TextAppender(TextView textView, String message) {
+            this(textView, message, textView.getCurrentTextColor());
+        }
+
+        @Override
+        public void run() {
+            mTextView.append(mMessageBuilder);
+        }
+    }
+
+    private class ButtonEnabler implements Runnable {
+        private final View mButtonView;
+        private final boolean mButtonEnabled;
+
+        public ButtonEnabler(View buttonView, boolean buttonEnabled) {
+            mButtonView = buttonView;
+            mButtonEnabled = buttonEnabled;
+        }
+
+        @Override
+        public void run() {
+            mButtonView.setEnabled(mButtonEnabled);
+        }
+    }
+
+    // TODO: ideally we want to store the original state of each feature, and make sure that we can
+    // restore their values at the end of the test
+
+    protected void askToSetAirplaneMode() throws InterruptedException {
+        if (isAirplaneModeOn()) {
+            appendText("Airplane mode set.");
+            return;
+        }
+
+        appendText("You will be redirected to set 'Airplane Mode' ON, after doing so, go back to " +
+                "this App. Press Next to continue.\n");
+        waitForUser();
+        launchAndWaitForSubactivity(Settings.ACTION_WIRELESS_SETTINGS);
+
+        if (!isAirplaneModeOn()) {
+            throw new IllegalStateException("Airplane Mode is not set.");
+        }
+    }
+
+    protected void askToSetScreenOffTimeout(int timeoutInSec) throws InterruptedException {
+        long timeoutInMs = TimeUnit.SECONDS.toMillis(timeoutInSec);
+        if (isScreenOffTimeout(timeoutInMs)) {
+            appendText("Screen Off Timeout set to: " + timeoutInSec + " seconds.");
+            return;
+        }
+
+        appendText("You will be redirected to set 'Display Sleep' to " + timeoutInSec + " seconds" +
+                ", after doing so, go back to this App. Press Next to continue.\n");
+        waitForUser();
+        launchAndWaitForSubactivity(Settings.ACTION_DISPLAY_SETTINGS);
+
+        if (!isScreenOffTimeout(timeoutInMs)) {
+            throw new IllegalStateException("'Display Sleep' not set to " + timeoutInSec +
+                    " seconds.");
+        }
+    }
+
+    private void launchAndWaitForSubactivity(String action) throws InterruptedException {
+        launchAndWaitForSubactivity(new Intent(action));
+    }
+
+    private void launchAndWaitForSubactivity(Intent intent) throws InterruptedException {
+        mCountDownLatch = new CountDownLatch(1);
+        startActivityForResult(intent, 0);
+        mCountDownLatch.await();
+    }
+
+    private boolean isAirplaneModeOn() {
+        ContentResolver contentResolver = getContentResolver();
+        int airplaneModeOn;
+        // Settings.System.AIRPLANE_MODE_ON is deprecated in API 17
+        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN_MR1) {
+            try {
+                airplaneModeOn =
+                        Settings.System.getInt(contentResolver, Settings.System.AIRPLANE_MODE_ON);
+            } catch (Settings.SettingNotFoundException e) {
+                airplaneModeOn = 0;
+            }
+        } else {
+            try {
+                airplaneModeOn =
+                        Settings.Global.getInt(contentResolver, Settings.Global.AIRPLANE_MODE_ON);
+            } catch (Settings.SettingNotFoundException e) {
+                airplaneModeOn = 0;
+            }
+        }
+        return airplaneModeOn != 0;
+    }
+
+    private boolean isScreenOffTimeout(long expectedTimeoutInMs) {
+        ContentResolver contentResolver = getContentResolver();
+        long screenOffTimeoutInMs;
+        try {
+            screenOffTimeoutInMs =
+                    Settings.System.getLong(contentResolver, Settings.System.SCREEN_OFF_TIMEOUT);
+        } catch(Settings.SettingNotFoundException e) {
+            screenOffTimeoutInMs = Integer.MAX_VALUE;
+        }
+        return screenOffTimeoutInMs <= expectedTimeoutInMs;
+    }
+}
\ No newline at end of file
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/sensors/SignificantMotionTestActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/sensors/SignificantMotionTestActivity.java
new file mode 100644
index 0000000..9258ba6
--- /dev/null
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/sensors/SignificantMotionTestActivity.java
@@ -0,0 +1,224 @@
+/*
+ * Copyright (C) 2014 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.sensors;
+
+import junit.framework.Assert;
+import android.annotation.TargetApi;
+import android.app.Activity;
+import android.content.Context;
+import android.graphics.Color;
+import android.hardware.Sensor;
+import android.hardware.SensorManager;
+import android.hardware.TriggerEvent;
+import android.hardware.TriggerEventListener;
+import android.media.AudioManager;
+import android.media.ToneGenerator;
+import android.os.Build;
+import android.os.Bundle;
+import android.os.Vibrator;
+
+@TargetApi(Build.VERSION_CODES.KITKAT)
+class TriggerListener extends TriggerEventListener {
+    // how much difference between system time and event time considered to be
+    // acceptable [msec]
+    private final long MAX_ACCEPTABLE_EVENT_TIME_DELAY_MILLIS = 500;
+
+    // state used for internal recording of the event detection
+    private boolean mEventDetected = false;
+
+    public void onTrigger(TriggerEvent event) {
+        final long NANOS_PER_MS = 1000000L;
+
+        Assert.assertEquals("values should be of length 1 for significant motion event", 1,
+                event.values.length);
+        Assert.assertEquals("values[0] should be 1.0 for significant motion event", 1.0f,
+                event.values[0]);
+
+        // Check that timestamp is within MAX_ACCEPTABLE_EVENT_TIME_DELAY_MILLIS
+        // It might take time to determine Significant Motion, but then that
+        // event should be reported to the host in a timely fashion.
+        long timeReportedMillis = event.timestamp / NANOS_PER_MS;
+        long timeActualMillis = System.currentTimeMillis();
+        Assert.assertEquals("Incorrect time reported in the event",
+                timeReportedMillis, timeActualMillis, MAX_ACCEPTABLE_EVENT_TIME_DELAY_MILLIS);
+
+        // Verify event type is truly Significant Motion
+        Assert.assertEquals("Triggered event type is not Significant Motion",
+                event.sensor.getType(), Sensor.TYPE_SIGNIFICANT_MOTION);
+
+        // Event detected flag should be false if indeed only one event per
+        // request
+        Assert.assertFalse("Significant Motion sensor did not automatically "
+                + "disable itself from subsequent detection", mEventDetected);
+
+        // audible cue to indicate Significant Motion occurred
+        beep();
+        mEventDetected = true;
+    }
+
+    public boolean wasEventTriggered() {
+        return mEventDetected;
+    }
+
+    public void reset() {
+        mEventDetected = false;
+    }
+
+    private void beep() {
+        final ToneGenerator tg = new ToneGenerator(
+                AudioManager.STREAM_NOTIFICATION, 100);
+        tg.startTone(ToneGenerator.TONE_PROP_BEEP);
+    }
+}
+
+@TargetApi(Build.VERSION_CODES.KITKAT)
+public class SignificantMotionTestActivity extends BaseSensorSemiAutomatedTestActivity {
+    // minimum time for test to consider valid [msec]
+    private final int MIN_TEST_TIME_MILLIS = 20000;
+    private final int VIBRATE_DURATION_MILLIS = 10000;
+
+    private SensorManager mSensorManager;
+    private Sensor mSensorSignificantMotion;
+    private final TriggerListener mTriggeredListener = new TriggerListener();
+    private long mTestStartTimestamp;
+    private static int sNumPassedTests = 0;
+
+    @Override
+    protected void onRun() throws Throwable {
+        switch (sNumPassedTests) {
+        // avoid re-running passed tests, so purposely want fallthroughs here
+            case 0:
+                // use walking to change location and trigger significant motion
+                runTest("walk 15 steps for significant motion to be detected", true, false, false);
+            case 1:
+                runTest("walk another 15 steps to ensure significant motion "
+                        + "is not reported after trigger cancelled", false, true, false);
+            case 2:
+                // use vibrator to ensure significant motion is not triggered
+                runTest("leave the device on a level surface", false, false, true);
+            case 3:
+                // use natural motion that does not change location to ensure
+                // significant motion is not triggered
+                runTest("hold the device in hand while performing natural "
+                        + "hand movements", false, false, false);
+            case 4:
+                runTest("keep the device in pocket and move naturally while "
+                        + "sitting in a chair", false, false, false);
+            default:
+                break;
+        }
+    }
+
+    private void vibrateDevice(int timeInMs) {
+        Vibrator vibrator = (Vibrator) this.getSystemService(Context.VIBRATOR_SERVICE);
+        vibrator.vibrate(timeInMs);
+    }
+
+    /**
+     * @param instructions Instruction to be shown to testers
+     * @param isMotionExpected Should the device detect significant motion event
+     *            for this test?
+     * @param cancelEventNotification If TRUE, motion notifications will be
+     *            requested first and request will be cancelled
+     * @param vibrate If TRUE, vibration will be concurrent with the test
+     * @throws Throwable
+     */
+    private void runTest(String instructions, final boolean isMotionExpected,
+            final boolean cancelEventNotification, final boolean vibrate) throws Throwable {
+
+        appendText("Click 'Next' and " + instructions);
+        waitForUser();
+
+        if (vibrate) {
+            vibrateDevice(VIBRATE_DURATION_MILLIS);
+        }
+
+        mTestStartTimestamp = System.currentTimeMillis();
+        startMeasurements(cancelEventNotification);
+
+        long testTime = System.currentTimeMillis() - mTestStartTimestamp;
+
+        while (!mTriggeredListener.wasEventTriggered()
+                && testTime < MIN_TEST_TIME_MILLIS) {
+            int timeWaitSec = Math
+                    .round((MIN_TEST_TIME_MILLIS - testTime) / 1000);
+            clearText();
+            appendText("Current test: " + instructions);
+            appendText(
+                    String.format("%d seconds for the test to complete", timeWaitSec),
+                    Color.GRAY);
+
+            Thread.sleep(1000);
+            testTime = System.currentTimeMillis() - mTestStartTimestamp;
+        }
+        clearText();
+        appendText("Current test: " + instructions);
+        playSound();
+        verifyMeasurements(isMotionExpected);
+        sNumPassedTests++;
+    }
+
+    private void startMeasurements(boolean isCancelTriggerRequested) throws Throwable {
+        mTriggeredListener.reset();
+
+        mSensorManager.requestTriggerSensor(mTriggeredListener, mSensorSignificantMotion);
+
+        if (isCancelTriggerRequested) {
+            mSensorManager.cancelTriggerSensor(mTriggeredListener, mSensorSignificantMotion);
+        }
+    }
+
+    private void verifyMeasurements(boolean isMotionExpected) throws Throwable {
+        Assert.assertEquals("Significant motion event expected/detected mismatch: "
+                + isMotionExpected + " / " + mTriggeredListener.wasEventTriggered(),
+                isMotionExpected, mTriggeredListener.wasEventTriggered());
+        appendText("Significant motion event " + isMotionExpected + " as expected", Color.GRAY);
+        logSuccess();
+    }
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        mSensorManager = (SensorManager) getApplicationContext()
+                .getSystemService(Context.SENSOR_SERVICE);
+
+        mSensorSignificantMotion = mSensorManager
+                .getDefaultSensor(Sensor.TYPE_SIGNIFICANT_MOTION);
+
+    }
+
+    @Override
+    protected void onResume() {
+        super.onResume();
+
+        if (mSensorManager != null && mSensorSignificantMotion != null) {
+            mSensorManager.requestTriggerSensor(mTriggeredListener,
+                    mSensorSignificantMotion);
+        }
+    }
+
+    @Override
+    protected void onPause() {
+        super.onPause();
+
+        if (mSensorManager != null && mSensorSignificantMotion != null) {
+            mSensorManager.cancelTriggerSensor(mTriggeredListener,
+                    mSensorSignificantMotion);
+        }
+    }
+}
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/sensors/StepCounterTestActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/sensors/StepCounterTestActivity.java
new file mode 100644
index 0000000..0dfe341
--- /dev/null
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/sensors/StepCounterTestActivity.java
@@ -0,0 +1,361 @@
+/*
+ * Copyright (C) 2014 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.sensors;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import junit.framework.Assert;
+import android.annotation.TargetApi;
+import android.content.Context;
+import android.graphics.Color;
+import android.hardware.Sensor;
+import android.hardware.SensorEvent;
+import android.hardware.SensorEventListener;
+import android.hardware.SensorManager;
+import android.hardware.cts.helpers.SensorCtsHelper;
+import android.media.AudioManager;
+import android.media.ToneGenerator;
+import android.os.Build;
+import android.os.SystemClock;
+import android.os.Vibrator;
+import android.view.View;
+import android.view.View.OnClickListener;
+
+import com.android.cts.verifier.R;
+
+@TargetApi(Build.VERSION_CODES.KITKAT)
+public class StepCounterTestActivity extends BaseSensorSemiAutomatedTestActivity
+        implements SensorEventListener {
+
+    private SensorManager mSensorManager;
+    private Sensor mSensorStepCounter;
+    private Sensor mSensorStepDetector;
+
+    private int mStepsReported = 0; // number of steps as reported by user
+    private int mInitialStepCount = 0; // step counter at the start of test
+    private int mStepsDetected = 0; // number of steps during the test
+
+    private List<Long> mTimestampsUserReported = new ArrayList<Long>();
+    private List<Long> mTimestampsStepCounter = new ArrayList<Long>();
+    private List<Long> mTimestampsStepDetector = new ArrayList<Long>();
+
+    private final int MIN_TEST_TIME_MILLIS = 20000; // 20 sec
+    private final double NANOSECONDS_IN_SEC = 1e9;
+    private final int MIN_NUM_STEPS_PER_TEST = 10;
+    private final int MAX_STEP_DISCREPANCY = 4;
+    private final int MAX_TOLERANCE_STEP_TIME_LATENCY_SECONDS = 8;
+
+    private boolean mCheckForMotion = false;
+
+    private Sensor mSensorAcceleration;
+    private boolean mMoveDetected = false;
+    private static int sNumPassedTests = 0;
+
+    @Override
+    protected void onRun() throws Throwable {
+        View screen = (View) findViewById(R.id.log_text).getParent();
+        Assert.assertNotNull(screen);
+        screen.setOnClickListener(mClickListener);
+
+        switch (sNumPassedTests) {
+        // avoid re-running passed tests, so purposely want fallthroughs here
+            case 0:
+                runTest("walk at least " + MIN_NUM_STEPS_PER_TEST
+                        + " steps and tap on the screen with each step",
+                        MIN_NUM_STEPS_PER_TEST, MAX_STEP_DISCREPANCY, false, false);
+            case 1:
+                runTest("hold device still in hand", 0, MAX_STEP_DISCREPANCY, true, true);
+            case 2:
+                runTest("wave device in hand throughout test", 0, MAX_STEP_DISCREPANCY, false,
+                        true);
+            default:
+                break;
+        }
+    }
+
+    private OnClickListener mClickListener = new OnClickListener() {
+        public void onClick(View v) {
+            if (!mCheckForMotion) {
+                SensorCtsHelper.beep(ToneGenerator.TONE_PROP_BEEP);
+                mTimestampsUserReported.add(SystemClock.elapsedRealtimeNanos());
+                mStepsReported = mTimestampsUserReported.size();
+            }
+        }
+    };
+
+    /**
+     * @param instructions Instruction to be shown to testers
+     * @param expectedSteps Number of steps expected in this test
+     * @param tolerance Number of steps the count can be off by and still pass
+     * @param vibrate If TRUE, vibration will be concurrent with the test
+     * @param onlyWarn If TRUE, only warn the user if the test fails. This
+     *            option will be removed on a future release of CTS. TODO:
+     *            remove this option
+     * @throws Throwable
+     */
+    static long[] sVibratePattern = {
+            1000L, 500L, 1000L, 750L, 1000L, 500L, 1000L, 750L, 1000L, 1000L, 500L, 1000L,
+            750L, 1000L, 500L, 1000L
+    };
+    private void runTest(String instructions, int expectedSteps, int tolerance, boolean vibrate,
+            boolean onlyWarn)
+            throws Throwable {
+
+        mTimestampsUserReported.clear();
+        mTimestampsStepCounter.clear();
+        mTimestampsStepDetector.clear();
+
+        mMoveDetected = false;
+        mCheckForMotion = true;
+
+        appendText("Click 'Next' and " + instructions);
+        waitForUser();
+
+        mInitialStepCount = 0;
+        mStepsDetected = 0;
+        mStepsReported = 0;
+        if (vibrate) {
+            vibrate(sVibratePattern);
+        }
+
+        mCheckForMotion = (expectedSteps == 0);
+        startMeasurements();
+
+        long testStartTime = System.currentTimeMillis();
+        long testTime = 0;
+
+        while (testTime < MIN_TEST_TIME_MILLIS) {
+            int timeWaitSec = Math.round((MIN_TEST_TIME_MILLIS - testTime) / 1000);
+            clearText();
+            appendText("Current test: " + instructions);
+            appendText(String.format("%d seconds left, %d steps detected, %d reported",
+                    timeWaitSec, mStepsDetected, mStepsReported), Color.GRAY);
+            Thread.sleep(1000);
+            testTime = System.currentTimeMillis() - testStartTime;
+        }
+        clearText();
+        appendText("Current test: " + instructions);
+        verifyMeasurements(expectedSteps, tolerance, onlyWarn);
+        appendText(mERNWarning + "\n" + mSCWarning, Color.YELLOW);
+        mCheckForMotion = false;
+        sNumPassedTests++;
+        mERNWarning = "";
+        mSCWarning = "";
+    }
+
+    private void startMeasurements() throws Throwable {
+        mSensorStepCounter = mSensorManager.getDefaultSensor(Sensor.TYPE_STEP_COUNTER);
+        if (mSensorStepCounter != null) {
+            mSensorManager.registerListener(this, mSensorStepCounter,
+                    SensorManager.SENSOR_DELAY_NORMAL);
+        } else {
+            appendText("Failed test, step counter sensor was not found", Color.RED);
+            Assert.fail("Step counter sensor was not found");
+        }
+
+        mSensorStepDetector = mSensorManager.getDefaultSensor(Sensor.TYPE_STEP_DETECTOR);
+        if (mSensorStepDetector != null) {
+            mSensorManager.registerListener(this, mSensorStepDetector,
+                    SensorManager.SENSOR_DELAY_NORMAL);
+        } else {
+            appendText("Failed test, step detector sensor was not found", Color.RED);
+            Assert.fail("Step detector sensor was not found");
+        }
+
+        mSensorAcceleration = mSensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);
+        if (mSensorAcceleration != null && mCheckForMotion) {
+            mSensorManager.registerListener(this, mSensorAcceleration,
+                    SensorManager.SENSOR_DELAY_NORMAL);
+        }
+    }
+
+    private void verifyMeasurements(int stepsExpected, int tolerance, boolean onlyWarn)
+            throws Throwable {
+        if (mSensorManager != null) {
+            mSensorManager.unregisterListener(this);
+        }
+
+        Assert.assertFalse(String.format("You need to report at least %d steps", stepsExpected),
+                mStepsReported < stepsExpected);
+        double maxStepReportTime = compareTimestamps();
+        Assert.assertTrue(String.format("Step report time %f longer than %d seconds",
+                maxStepReportTime, MAX_TOLERANCE_STEP_TIME_LATENCY_SECONDS),
+                maxStepReportTime < MAX_TOLERANCE_STEP_TIME_LATENCY_SECONDS);
+
+        if (mCheckForMotion && !mMoveDetected) {
+            String message = "Movement is needed during this test";
+
+            warnOrAssert(onlyWarn, message);
+        }
+
+        if (Math.abs(mStepsDetected - mStepsReported) > tolerance) {
+            String message = String.format("Step count test: "
+                    + "detected %d steps but %d were expected (to within %d steps)",
+                    mStepsDetected, mStepsReported, tolerance);
+            warnOrAssert(onlyWarn, message);
+        }
+
+        appendText("PASS step count test", Color.GREEN);
+
+        if (Math.abs(mTimestampsStepDetector.size() - mStepsReported) > tolerance) {
+            String message = String.format("Step detector test: "
+                    + "detected %d steps but %d were expected (to within %d steps)",
+                    mTimestampsStepDetector.size(), mStepsReported, tolerance);
+            warnOrAssert(onlyWarn, message);
+        }
+
+        appendText("PASS step detection test", Color.GREEN);
+
+        logSuccess();
+    }
+
+    public final void onAccuracyChanged(Sensor sensor, int accuracy) {
+    }
+
+    private void warnOrAssert(boolean onlyWarn, String message) throws Throwable {
+        if (onlyWarn) {
+            appendText("WARNING: " + message, Color.YELLOW);
+        } else {
+            Assert.fail("FAILED " + message);
+        }
+    }
+
+    String mERNWarning = "";
+    String mSCWarning = "";
+
+    public long checkTimestamp(long eventTimestamp) {
+        long timestamp = SystemClock.elapsedRealtimeNanos();
+        if (Math.abs(timestamp - eventTimestamp) > MIN_TEST_TIME_MILLIS * 1e6) {
+            // elapsedRealtimeNanos will lead to test failure, warn for now
+            mERNWarning = "WARNING: elapsedRealtimeNanos is significantly different than "
+                    + " sensor event timestamps.  This should be rectified.";
+        } else {
+            timestamp = eventTimestamp;
+        }
+        return timestamp;
+    }
+
+    public void onStepCounterChanged(SensorEvent event) throws Throwable {
+        int steps = (int) event.values[0] - mInitialStepCount;
+
+        if (mInitialStepCount == 0) { // set the initial number of steps
+            mInitialStepCount = steps;
+        } else if (steps > 0) {
+            mTimestampsStepCounter.add(checkTimestamp(event.timestamp));
+            Assert.assertTrue(String.format("Step counter did not increase monotonically: "
+                    + "%d changed to %d", mStepsDetected, steps), steps >= mStepsDetected);
+            mStepsDetected = steps;
+        } else {
+            Assert.fail("Step Counter change called when no steps reported");
+        }
+    }
+
+    public void onStepDetectorChanged(SensorEvent event) throws Throwable {
+        Assert.assertEquals("Incorrect value[0] in step detector event", event.values[0], 1.0f);
+        mTimestampsStepDetector.add(checkTimestamp(event.timestamp));
+    }
+
+    public final void onSensorChanged(SensorEvent event) {
+        int type = event.sensor.getType();
+        try {
+            if (type == Sensor.TYPE_STEP_COUNTER) {
+                onStepCounterChanged(event);
+            } else if (type == Sensor.TYPE_STEP_DETECTOR) {
+                onStepDetectorChanged(event);
+            } else if (type == Sensor.TYPE_ACCELEROMETER) {
+                mMoveDetected = SensorCtsHelper.checkMovementDetection(event);
+            } else {
+                Assert.fail("Sensor type " + type + " called when not registered for by this test");
+            }
+        } catch (Throwable ae) {
+            mSCWarning = ae.getMessage();
+        }
+    }
+
+    protected double compareTimestamps() {
+        double timeDeltaInSec;
+        double maxTimeDeltaInSec = 0;
+        StringBuilder reportLine = new StringBuilder();
+        reportLine.append("Reported Step: Step Detector / Counter Latency (sec)\n");
+        for (int eventCounter = 0; eventCounter < mStepsReported; eventCounter++) {
+            reportLine.append((eventCounter + 1) + ":  ");
+
+            if (eventCounter < mTimestampsStepDetector.size()) {
+                timeDeltaInSec = (mTimestampsStepDetector.get(eventCounter)
+                        - mTimestampsUserReported.get(eventCounter)) / NANOSECONDS_IN_SEC;
+                maxTimeDeltaInSec = Math.max(maxTimeDeltaInSec, Math.abs(timeDeltaInSec));
+                reportLine.append(String.format("%.2f", timeDeltaInSec));
+            } else {
+                reportLine.append("--");
+            }
+
+            reportLine.append("  /  ");
+            if (eventCounter < mTimestampsStepCounter.size()) {
+                timeDeltaInSec = (mTimestampsStepCounter.get(eventCounter)
+                        - mTimestampsUserReported.get(eventCounter)) / NANOSECONDS_IN_SEC;
+                maxTimeDeltaInSec = Math.max(maxTimeDeltaInSec, Math.abs(timeDeltaInSec));
+                reportLine.append(String.format("%.2f", timeDeltaInSec));
+            } else {
+                reportLine.append("--");
+            }
+            reportLine.append("\n");
+        }
+        appendText(reportLine.toString(), Color.GRAY);
+
+        return maxTimeDeltaInSec;
+    }
+
+    protected void vibrate(long[] pattern) {
+        Vibrator v = (Vibrator) getSystemService(Context.VIBRATOR_SERVICE);
+        if(v==null) {
+            appendText("Cannot access vibrator for this test...continuing anyway", Color.YELLOW);
+        } else {
+            v.vibrate(pattern, -1);
+        }
+    }
+
+    @Override
+    protected void onResume() {
+        super.onResume();
+        if (mSensorManager == null) {
+            mSensorManager = (SensorManager) getApplicationContext()
+                    .getSystemService(Context.SENSOR_SERVICE);
+        }
+
+        if (mSensorStepCounter != null) {
+            mSensorManager.registerListener(this, mSensorStepCounter,
+                    SensorManager.SENSOR_DELAY_NORMAL);
+        }
+        if (mSensorStepDetector != null) {
+            mSensorManager.registerListener(this, mSensorStepDetector,
+                    SensorManager.SENSOR_DELAY_NORMAL);
+        }
+        if (mSensorAcceleration != null && mCheckForMotion) {
+            mSensorManager.registerListener(this, mSensorAcceleration,
+                    SensorManager.SENSOR_DELAY_NORMAL);
+        }
+    }
+
+    @Override
+    protected void onPause() {
+        super.onPause();
+        if (mSensorManager != null) {
+            mSensorManager.unregisterListener(this);
+        }
+    }
+}
diff --git a/suite/cts/deviceTests/simplecpu/src/com/android/cts/simplecpu/SimpleCpuTest.java b/suite/cts/deviceTests/simplecpu/src/com/android/cts/simplecpu/SimpleCpuTest.java
index 7485abe..ce0feac 100644
--- a/suite/cts/deviceTests/simplecpu/src/com/android/cts/simplecpu/SimpleCpuTest.java
+++ b/suite/cts/deviceTests/simplecpu/src/com/android/cts/simplecpu/SimpleCpuTest.java
@@ -76,7 +76,7 @@
     }
 
     // will exceed L2
-    @TimeoutReq(minutes = 20)
+    @TimeoutReq(minutes = 30)
     public void testMatrixMultiplication600() {
         doMatrixMultiplication(NUMBER_REPEAT, 600);
     }
diff --git a/tests/res/values/strings.xml b/tests/res/values/strings.xml
index 2cd2284..c167278 100644
--- a/tests/res/values/strings.xml
+++ b/tests/res/values/strings.xml
@@ -168,6 +168,12 @@
     <string name="long_text">This is a really long string which exceeds the width of the view.
 New devices have a much larger screen which actually enables long strings to be displayed
 with no fading. I have made this string longer to fix this case. If you are correcting this
-text, I would love to see the kind of devices you guys now use! Guys, maybe some devices need longer string! </string>
+text, I would love to see the kind of devices you guys now use! Guys, maybe some devices need longer string!
+I think so, so how about double this string, like copy and paste!
+This is a really long string which exceeds the width of the view.
+New devices have a much larger screen which actually enables long strings to be displayed
+with no fading. I have made this string longer to fix this case. If you are correcting this
+text, I would love to see the kind of devices you guys now use! Guys, maybe some devices need longer string!
+I think so, so how about double this string, like copy and paste! </string>
     <string name="rectangle200">"M 0,0 l 200,0 l 0, 200 l -200, 0 z"</string>
 </resources>
diff --git a/tests/tests/hardware/src/android/hardware/camera2/cts/CameraTestUtils.java b/tests/tests/hardware/src/android/hardware/camera2/cts/CameraTestUtils.java
index bbfd7b5..1f4fc49 100644
--- a/tests/tests/hardware/src/android/hardware/camera2/cts/CameraTestUtils.java
+++ b/tests/tests/hardware/src/android/hardware/camera2/cts/CameraTestUtils.java
@@ -70,8 +70,10 @@
     private static final String TAG = "CameraTestUtils";
     private static final boolean VERBOSE = Log.isLoggable(TAG, Log.VERBOSE);
     private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
-    // Only test the preview and video size that is no larger than 1080p.
-    public static final Size PREVIEW_SIZE_BOUND = new Size(1920, 1080);
+    public static final Size SIZE_BOUND_1080P = new Size(1920, 1088);
+    public static final Size SIZE_BOUND_2160P = new Size(3840, 2160);
+    // Only test the preview size that is no larger than 1080p.
+    public static final Size PREVIEW_SIZE_BOUND = SIZE_BOUND_1080P;
     // Default timeouts for reaching various states
     public static final int CAMERA_OPEN_TIMEOUT_MS = 2000;
     public static final int CAMERA_CLOSE_TIMEOUT_MS = 2000;
diff --git a/tests/tests/hardware/src/android/hardware/camera2/cts/MultiViewTest.java b/tests/tests/hardware/src/android/hardware/camera2/cts/MultiViewTest.java
index fdf4774..72e18bb 100644
--- a/tests/tests/hardware/src/android/hardware/camera2/cts/MultiViewTest.java
+++ b/tests/tests/hardware/src/android/hardware/camera2/cts/MultiViewTest.java
@@ -17,41 +17,40 @@
 package android.hardware.camera2.cts;
 
 import static android.hardware.camera2.cts.CameraTestUtils.*;
+
 import android.graphics.ImageFormat;
 import android.graphics.SurfaceTexture;
+import android.hardware.camera2.CameraDevice;
 import android.hardware.camera2.cts.CameraTestUtils.ImageVerifierListener;
 import android.hardware.camera2.cts.testcases.Camera2MultiViewTestCase;
 import android.hardware.camera2.cts.testcases.Camera2MultiViewTestCase.CameraPreviewListener;
 import android.media.ImageReader;
 import android.os.SystemClock;
+import android.util.Log;
 import android.util.Size;
 import android.view.Surface;
 import android.view.TextureView;
 
+import com.android.ex.camera2.blocking.BlockingCameraManager.BlockingOpenException;
+
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.List;
 
 /**
  * CameraDevice test by using combination of SurfaceView, TextureView and ImageReader
  */
 public class MultiViewTest extends Camera2MultiViewTestCase {
-
+    private static final String TAG = "MultiViewTest";
     private final static long WAIT_FOR_COMMAND_TO_COMPLETE = 2000;
     private final static long PREVIEW_TIME_MS = 2000;
 
-    private enum NumberOfPreview {
-        ONE(1), TWO(2);
-
-        private final int mValue;
-        private NumberOfPreview(int value) { mValue = value; }
-        public int getValue() { return mValue; }
-    }
-
     public void testTextureViewPreview() throws Exception {
         for (String cameraId : mCameraIds) {
             openCamera(cameraId);
-            textureViewPreview(NumberOfPreview.ONE, /*testImagerReader*/false);
-            closeCamera();
+            List<TextureView> views = Arrays.asList(mTextureView[0]);
+            textureViewPreview(cameraId, views, /*withImagerReader*/false);
+            closeCamera(cameraId);
         }
     }
 
@@ -59,13 +58,15 @@
         for (String cameraId : mCameraIds) {
             try {
                 openCamera(cameraId);
-                int maxNumStreamsProc = mStaticInfo.getMaxNumOutputStreamsProcessedChecked();
+                int maxNumStreamsProc =
+                        getStaticInfo(cameraId).getMaxNumOutputStreamsProcessedChecked();
                 if (maxNumStreamsProc < 2) {
                     continue;
                 }
-                textureViewPreview(NumberOfPreview.ONE, /*testImagerReader*/true);
+                List<TextureView> views = Arrays.asList(mTextureView[0]);
+                textureViewPreview(cameraId, views, /*withImagerReader*/true);
             } finally {
-                closeCamera();
+                closeCamera(cameraId);
             }
         }
     }
@@ -74,13 +75,15 @@
         for (String cameraId : mCameraIds) {
             try {
                 openCamera(cameraId);
-                int maxNumStreamsProc = mStaticInfo.getMaxNumOutputStreamsProcessedChecked();
+                int maxNumStreamsProc =
+                        getStaticInfo(cameraId).getMaxNumOutputStreamsProcessedChecked();
                 if (maxNumStreamsProc < 2) {
                     continue;
                 }
-                textureViewPreview(NumberOfPreview.TWO, /*testImagerReader*/false);
+                List<TextureView> views = Arrays.asList(mTextureView[0], mTextureView[1]);
+                textureViewPreview(cameraId, views, /*withImagerReader*/false);
             } finally {
-                closeCamera();
+                closeCamera(cameraId);
             }
         }
     }
@@ -89,32 +92,65 @@
         for (String cameraId : mCameraIds) {
             try {
                 openCamera(cameraId);
-                int maxNumStreamsProc = mStaticInfo.getMaxNumOutputStreamsProcessedChecked();
+                int maxNumStreamsProc =
+                        getStaticInfo(cameraId).getMaxNumOutputStreamsProcessedChecked();
                 if (maxNumStreamsProc < 3) {
                     continue;
                 }
-                textureViewPreview(NumberOfPreview.TWO, /*testImagerReader*/true);
+                List<TextureView> views = Arrays.asList(mTextureView[0], mTextureView[1]);
+                textureViewPreview(cameraId, views, /*withImagerReader*/true);
             } finally {
-                closeCamera();
+                closeCamera(cameraId);
+            }
+        }
+    }
+
+    public void testDualCameraPreview() throws Exception {
+        final int NUM_CAMERAS_TESTED = 2;
+        if (mCameraIds.length < NUM_CAMERAS_TESTED) {
+            return;
+        }
+
+        try {
+            for (int i = 0; i < NUM_CAMERAS_TESTED; i++) {
+                openCamera(mCameraIds[i]);
+                List<TextureView> views = Arrays.asList(mTextureView[i]);
+                startTextureViewPreview(mCameraIds[i], views, /*withImagerReader*/false);
+            }
+            // TODO: check the framerate is correct
+            SystemClock.sleep(PREVIEW_TIME_MS);
+            for (int i = 0; i < NUM_CAMERAS_TESTED; i++) {
+                stopPreview(mCameraIds[i]);
+            }
+        } catch (BlockingOpenException e) {
+            // The only error accepted is ERROR_MAX_CAMERAS_IN_USE, which means HAL doesn't support
+            // concurrent camera streaming
+            assertEquals("Camera device open failed",
+                    CameraDevice.StateListener.ERROR_MAX_CAMERAS_IN_USE, e.getCode());
+            Log.i(TAG, "Camera HAL does not support dual camera preview. Skip the test");
+        } finally {
+            for (int i = 0; i < NUM_CAMERAS_TESTED; i++) {
+                closeCamera(mCameraIds[i]);
             }
         }
     }
 
     /**
-     * Test camera Preview using one texture view
+     * Start camera preview using input texture views and/or one image reader
      */
-    private void textureViewPreview(NumberOfPreview n, boolean testImagerReader)
+    private void startTextureViewPreview(
+            String cameraId, List<TextureView> views, boolean withImagerReader)
             throws Exception {
-        int numPreview = n.getValue();
-        Size previewSize = mOrderedPreviewSizes.get(0);
+        int numPreview = views.size();
+        Size previewSize = getOrderedPreviewSizes(cameraId).get(0);
         CameraPreviewListener[] previewListener =
                 new CameraPreviewListener[numPreview];
         SurfaceTexture[] previewTexture = new SurfaceTexture[numPreview];
         List<Surface> surfaces = new ArrayList<Surface>();
 
         // Prepare preview surface.
-        for (int i = 0; i < numPreview; i++) {
-            TextureView view = mTextureView[i];
+        int i = 0;
+        for (TextureView view : views) {
             previewListener[i] = new CameraPreviewListener();
             view.setSurfaceTextureListener(previewListener[i]);
             previewTexture[i] = getAvailableSurfaceTexture(WAIT_FOR_COMMAND_TO_COMPLETE, view);
@@ -123,9 +159,10 @@
             // Correct the preview display rotation.
             updatePreviewDisplayRotation(previewSize, view);
             surfaces.add(new Surface(previewTexture[i]));
+            i++;
         }
 
-        if (testImagerReader) {
+        if (withImagerReader) {
             ImageVerifierListener yuvListener =
                     new ImageVerifierListener(previewSize, ImageFormat.YUV_420_888);
             ImageReader yuvReader = makeImageReader(previewSize,
@@ -133,19 +170,29 @@
             surfaces.add(yuvReader.getSurface());
         }
 
-        startPreview(surfaces, null);
+        startPreview(cameraId, surfaces, null);
 
-        for (int i = 0; i < numPreview; i++) {
-            TextureView view = mTextureView[i];
+        i = 0;
+        for (TextureView view : views) {
             boolean previewDone =
                     previewListener[i].waitForPreviewDone(WAIT_FOR_COMMAND_TO_COMPLETE);
             assertTrue("Unable to start preview " + i, previewDone);
             view.setSurfaceTextureListener(null);
+            i++;
         }
+    }
+
+    /**
+     * Test camera preview using input texture views and/or one image reader
+     */
+    private void textureViewPreview(
+            String cameraId, List<TextureView> views, boolean testImagerReader)
+            throws Exception {
+        startTextureViewPreview(cameraId, views, testImagerReader);
 
         // TODO: check the framerate is correct
         SystemClock.sleep(PREVIEW_TIME_MS);
 
-        stopPreview();
+        stopPreview(cameraId);
     }
 }
diff --git a/tests/tests/hardware/src/android/hardware/camera2/cts/RecordingTest.java b/tests/tests/hardware/src/android/hardware/camera2/cts/RecordingTest.java
index 652462c..05b6243 100644
--- a/tests/tests/hardware/src/android/hardware/camera2/cts/RecordingTest.java
+++ b/tests/tests/hardware/src/android/hardware/camera2/cts/RecordingTest.java
@@ -56,7 +56,6 @@
     private static final String TAG = "RecordingTest";
     private static final boolean VERBOSE = Log.isLoggable(TAG, Log.VERBOSE);
     private static final boolean DEBUG_DUMP = false;
-    private static final Size VIDEO_SIZE_BOUND = new Size(1920, 1080);
     private static final int RECORDING_DURATION_MS = 2000;
     private static final int DURATION_MARGIN_MS = 400;
     private static final int FRAME_DURATION_ERROR_TOLERANCE_MS = 3;
@@ -116,8 +115,8 @@
                 // Re-use the MediaRecorder object for the same camera device.
                 mMediaRecorder = new MediaRecorder();
                 openDevice(mCameraIds[i]);
-                mSupportedVideoSizes = getSupportedVideoSizes(mCamera.getId(), mCameraManager,
-                        VIDEO_SIZE_BOUND);
+
+                initSupportedVideoSize(mCameraIds[i]);
 
                 basicRecordingTestByCamera();
             } finally {
@@ -145,8 +144,7 @@
                 mMediaRecorder = new MediaRecorder();
                 openDevice(mCameraIds[i]);
 
-                mSupportedVideoSizes = getSupportedVideoSizes(mCamera.getId(), mCameraManager,
-                        VIDEO_SIZE_BOUND);
+                initSupportedVideoSize(mCameraIds[i]);
 
                 recordingSizeTestByCamera();
             } finally {
@@ -470,6 +468,18 @@
     }
 
     /**
+     * Initialize the supported video sizes.
+     */
+    private void initSupportedVideoSize(String cameraId)  throws Exception {
+        Size maxVideoSize = SIZE_BOUND_1080P;
+        if (CamcorderProfile.hasProfile(CamcorderProfile.QUALITY_2160P)) {
+            maxVideoSize = SIZE_BOUND_2160P;
+        }
+        mSupportedVideoSizes =
+                getSupportedVideoSizes(cameraId, mCameraManager, maxVideoSize);
+    }
+
+    /**
      * Simple wrapper to wrap normal/burst video snapshot tests
      */
     private void videoSnapshotHelper(boolean burstTest) throws Exception {
@@ -478,9 +488,10 @@
                     Log.i(TAG, "Testing video snapshot for camera " + id);
                     // Re-use the MediaRecorder object for the same camera device.
                     mMediaRecorder = new MediaRecorder();
+
                     openDevice(id);
-                    mSupportedVideoSizes =
-                            getSupportedVideoSizes(id, mCameraManager, VIDEO_SIZE_BOUND);
+
+                    initSupportedVideoSize(id);
 
                     videoSnapshotTestByCamera(burstTest);
                 } finally {
diff --git a/tests/tests/hardware/src/android/hardware/camera2/cts/testcases/Camera2MultiViewTestCase.java b/tests/tests/hardware/src/android/hardware/camera2/cts/testcases/Camera2MultiViewTestCase.java
index d91efbb..f7d743e 100644
--- a/tests/tests/hardware/src/android/hardware/camera2/cts/testcases/Camera2MultiViewTestCase.java
+++ b/tests/tests/hardware/src/android/hardware/camera2/cts/testcases/Camera2MultiViewTestCase.java
@@ -48,7 +48,10 @@
 import com.android.ex.camera2.blocking.BlockingSessionListener;
 import com.android.ex.camera2.blocking.BlockingStateListener;
 
+import junit.framework.Assert;
+
 import java.util.List;
+import java.util.HashMap;
 
 /**
  * Camera2 test case base class by using mixed SurfaceView and TextureView as rendering target.
@@ -62,23 +65,18 @@
     private static final long SHORT_SLEEP_WAIT_TIME_MS = 100;
     // Default timeouts for reaching various camera states
     private static final int CAMERA_CLOSE_TIMEOUT_MS = 2000;
-    private static final int CAMERA_IDLE_TIMEOUT_MS = 2000;
-    private static final int CAMERA_BUSY_TIMEOUT_MS = 500;
 
     protected TextureView[] mTextureView = new TextureView[2];
-    protected Context mContext;
-    protected CameraManager mCameraManager;
     protected String[] mCameraIds;
-    protected HandlerThread mHandlerThread;
     protected Handler mHandler;
-    protected BlockingStateListener mCameraListener;
 
-    // Per device fields:
-    protected BlockingSessionListener mSessionListener;
-    protected CameraCaptureSession mSession;
-    protected CameraDevice mCamera;
-    protected StaticMetadata mStaticInfo;
-    protected List<Size> mOrderedPreviewSizes;
+    private CameraManager mCameraManager;
+    private BlockingStateListener mCameraListener;
+    private HandlerThread mHandlerThread;
+    private Context mContext;
+
+    private CameraHolder[] mCameraHolders;
+    private HashMap<String, Integer> mCameraIdMap;
 
     public Camera2MultiViewTestCase() {
         super(Camera2MultiViewStubActivity.class);
@@ -97,10 +95,17 @@
         mHandlerThread.start();
         mHandler = new Handler(mHandlerThread.getLooper());
         mCameraListener = new BlockingStateListener();
-        Camera2MultiViewStubActivity activity = (Camera2MultiViewStubActivity)mContext;
+        Camera2MultiViewStubActivity activity = (Camera2MultiViewStubActivity) mContext;
         mTextureView[0] = activity.getTextureView(0);
         mTextureView[1] = activity.getTextureView(1);
         assertNotNull("Unable to get texture view", mTextureView);
+        mCameraIdMap = new HashMap<String, Integer>();
+        int numCameras = mCameraIds.length;
+        mCameraHolders = new CameraHolder[numCameras];
+        for (int i = 0; i < numCameras; i++) {
+            mCameraHolders[i] = new CameraHolder(mCameraIds[i]);
+            mCameraIdMap.put(mCameraIds[i], i);
+        }
     }
 
     @Override
@@ -108,9 +113,11 @@
         mHandlerThread.quitSafely();
         mHandler = null;
         mCameraListener = null;
-        if (mCamera != null) {
-            mCamera.close();
-            mCamera = null;
+        for (CameraHolder camera : mCameraHolders) {
+            if (camera.isOpenned()) {
+                camera.close();
+                camera = null;
+            }
         }
         super.tearDown();
     }
@@ -210,46 +217,41 @@
     }
 
     protected void openCamera(String cameraId) throws Exception {
-        assertNull("Camera is already opened", mCamera);
-        mCamera = (new BlockingCameraManager(mCameraManager)).openCamera(
-                cameraId, mCameraListener, mHandler);
-        mStaticInfo = new StaticMetadata(mCameraManager.getCameraCharacteristics(cameraId),
-                CheckLevel.ASSERT, /*collector*/null);
-        mOrderedPreviewSizes = getSupportedPreviewSizes(cameraId, mCameraManager, PREVIEW_SIZE_BOUND);
-        assertNotNull(String.format("Failed to open camera device ID: %s", cameraId), mCamera);
+        CameraHolder camera = getCameraHolder(cameraId);
+        assertFalse("Camera has already opened", camera.isOpenned());
+        camera.open();
+        return;
     }
 
-    protected void closeCamera() throws Exception {
-        assertNotNull("Camera is already closed!", mCamera);
-        mCamera.close();
-        mCameraListener.waitForState(STATE_CLOSED, CAMERA_CLOSE_TIMEOUT_MS);
-        mCamera = null;
-        mSession = null;
-        mStaticInfo = null;
-        mOrderedPreviewSizes = null;
+    protected void closeCamera(String cameraId) throws Exception {
+        CameraHolder camera = getCameraHolder(cameraId);
+        camera.close();
     }
 
-    protected void startPreview(List<Surface> outputSurfaces, CaptureListener listener)
+    protected void startPreview(
+            String cameraId, List<Surface> outputSurfaces, CaptureListener listener)
             throws Exception {
-        mSessionListener = new BlockingSessionListener();
-        mSession = configureCameraSession(mCamera, outputSurfaces, mSessionListener, mHandler);
-
-        // TODO: vary the different settings like crop region to cover more cases.
-        CaptureRequest.Builder captureBuilder =
-                mCamera.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
-
-        for (Surface surface : outputSurfaces) {
-            captureBuilder.addTarget(surface);
-        }
-        mSession.setRepeatingRequest(captureBuilder.build(), listener, mHandler);
+        CameraHolder camera = getCameraHolder(cameraId);
+        assertTrue("Camera " + cameraId + " is not openned", camera.isOpenned());
+        camera.startPreview(outputSurfaces, listener);
     }
 
-    protected void stopPreview() throws Exception {
-        if (VERBOSE) Log.v(TAG, "Stopping preview and waiting for idle");
-        // Stop repeat, wait for captures to complete, and disconnect from surfaces
-        mSession.close();
-        mSessionListener.getStateWaiter().waitForState(SESSION_CLOSED, SESSION_CLOSE_TIMEOUT_MS);
-        mSessionListener = null;
+    protected void stopPreview(String cameraId) throws Exception {
+        CameraHolder camera = getCameraHolder(cameraId);
+        assertTrue("Camera " + cameraId + " preview is not running", camera.isPreviewStarted());
+        camera.stopPreview();
+    }
+
+    protected StaticMetadata getStaticInfo(String cameraId) {
+        CameraHolder camera = getCameraHolder(cameraId);
+        assertTrue("Camera is not openned", camera.isOpenned());
+        return camera.getStaticInfo();
+    }
+
+    protected List<Size> getOrderedPreviewSizes(String cameraId) {
+        CameraHolder camera = getCameraHolder(cameraId);
+        assertTrue("Camera is not openned", camera.isOpenned());
+        return camera.getOrderedPreviewSizes();
     }
 
     /**
@@ -321,5 +323,91 @@
             return true;
         }
     }
+
+    private CameraHolder getCameraHolder(String cameraId) {
+        Integer cameraIdx = mCameraIdMap.get(cameraId);
+        if (cameraIdx == null) {
+            Assert.fail("Unknown camera Id");
+        }
+        return mCameraHolders[cameraIdx];
+    }
+
+    // Per device fields
+    private class CameraHolder {
+        private String mCameraId;
+        private CameraCaptureSession mSession;
+        private CameraDevice mCamera;
+        private StaticMetadata mStaticInfo;
+        private List<Size> mOrderedPreviewSizes;
+        private BlockingSessionListener mSessionListener;
+
+        public CameraHolder(String id){
+            mCameraId = id;
+        }
+
+        public StaticMetadata getStaticInfo() {
+            return mStaticInfo;
+        }
+
+        public List<Size> getOrderedPreviewSizes() {
+            return mOrderedPreviewSizes;
+        }
+
+        public void open() throws Exception {
+            assertNull("Camera is already opened", mCamera);
+            mCamera = (new BlockingCameraManager(mCameraManager)).openCamera(
+                    mCameraId, mCameraListener, mHandler);
+            mStaticInfo = new StaticMetadata(mCameraManager.getCameraCharacteristics(mCameraId),
+                    CheckLevel.ASSERT, /*collector*/null);
+            mOrderedPreviewSizes = getSupportedPreviewSizes(
+                    mCameraId, mCameraManager, PREVIEW_SIZE_BOUND);
+            assertNotNull(String.format("Failed to open camera device ID: %s", mCameraId), mCamera);
+        }
+
+        public boolean isOpenned() {
+            return (mCamera != null);
+        }
+
+        public void close() throws Exception {
+            if (!isOpenned()) {
+                return;
+            }
+            mCamera.close();
+            mCameraListener.waitForState(STATE_CLOSED, CAMERA_CLOSE_TIMEOUT_MS);
+            mCamera = null;
+            mSession = null;
+            mStaticInfo = null;
+            mOrderedPreviewSizes = null;
+        }
+
+        public void startPreview(List<Surface> outputSurfaces, CaptureListener listener)
+                throws Exception {
+            mSessionListener = new BlockingSessionListener();
+            mSession = configureCameraSession(mCamera, outputSurfaces, mSessionListener, mHandler);
+
+            // TODO: vary the different settings like crop region to cover more cases.
+            CaptureRequest.Builder captureBuilder =
+                    mCamera.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
+
+            for (Surface surface : outputSurfaces) {
+                captureBuilder.addTarget(surface);
+            }
+            mSession.setRepeatingRequest(captureBuilder.build(), listener, mHandler);
+        }
+
+        public boolean isPreviewStarted() {
+            return (mSession != null);
+        }
+
+        public void stopPreview() throws Exception {
+            if (VERBOSE) Log.v(TAG,
+                    "Stopping camera " + mCameraId +" preview and waiting for idle");
+            // Stop repeat, wait for captures to complete, and disconnect from surfaces
+            mSession.close();
+            mSessionListener.getStateWaiter().waitForState(
+                    SESSION_CLOSED, SESSION_CLOSE_TIMEOUT_MS);
+            mSessionListener = null;
+        }
+    }
 }
 
diff --git a/tests/tests/hardware/src/android/hardware/cts/helpers/SensorCtsHelper.java b/tests/tests/hardware/src/android/hardware/cts/helpers/SensorCtsHelper.java
index ed55b01..ed6560f 100644
--- a/tests/tests/hardware/src/android/hardware/cts/helpers/SensorCtsHelper.java
+++ b/tests/tests/hardware/src/android/hardware/cts/helpers/SensorCtsHelper.java
@@ -17,7 +17,10 @@
 
 import android.content.Context;
 import android.hardware.Sensor;
+import android.hardware.SensorEvent;
 import android.hardware.SensorManager;
+import android.media.AudioManager;
+import android.media.ToneGenerator;
 
 import java.util.ArrayList;
 import java.util.Collection;
@@ -248,4 +251,45 @@
         }
         return sensorManager;
     }
+
+    public static void beep(int tone) {
+	ToneGenerator mToneGenerator = new ToneGenerator(AudioManager.STREAM_NOTIFICATION, 100);
+	mToneGenerator.startTone(tone);
+    }
+
+    private final static float mAccelerationThresholdForMoveDetection = 4.0f;
+    private static float[] mGravity = {0.0f, 0.0f, 0.0f};
+    public static boolean checkMovementDetection(SensorEvent event) {
+        // Alpha is calculated as t / (t + dT),
+        // where t is the low-pass filter's time-constant and
+        // dT is the event delivery rate.
+	boolean mMoveDetected = false;
+        final float alpha = 0.8f;
+        float[] linear_acceleration = {0.0f, 0.0f, 0.0f};
+
+        if (mGravity[0] == 0f && mGravity[2] == 0f) {
+            mGravity[0] = event.values[0];
+            mGravity[1] = event.values[1];
+            mGravity[2] = event.values[2];
+        } else {
+            // Isolate the force of gravity with the low-pass filter.
+            mGravity[0] = alpha * mGravity[0] + (1 - alpha) * event.values[0];
+            mGravity[1] = alpha * mGravity[1] + (1 - alpha) * event.values[1];
+            mGravity[2] = alpha * mGravity[2] + (1 - alpha) * event.values[2];
+        }
+
+        // Remove the gravity contribution with the high-pass filter.
+        linear_acceleration[0] = event.values[0] - mGravity[0];
+        linear_acceleration[1] = event.values[1] - mGravity[1];
+        linear_acceleration[2] = event.values[2] - mGravity[2];
+
+        float totalAcceleration = Math.abs(linear_acceleration[0])
+	    + Math.abs(linear_acceleration[1])
+	    + Math.abs(linear_acceleration[2]);
+
+        if (totalAcceleration > mAccelerationThresholdForMoveDetection) {
+            mMoveDetected = true;
+        }
+	return mMoveDetected;
+    }
 }
diff --git a/tests/tests/media/src/android/media/cts/AudioManagerTest.java b/tests/tests/media/src/android/media/cts/AudioManagerTest.java
index 13e5fd9..3eaafd4 100644
--- a/tests/tests/media/src/android/media/cts/AudioManagerTest.java
+++ b/tests/tests/media/src/android/media/cts/AudioManagerTest.java
@@ -110,6 +110,7 @@
 
     public void testMusicActive() throws Exception {
         MediaPlayer mp = MediaPlayer.create(mContext, MP3_TO_PLAY);
+        assertNotNull(mp);
         mp.setAudioStreamType(AudioManager.STREAM_MUSIC);
         mp.start();
         Thread.sleep(TIME_TO_PLAY);
@@ -393,6 +394,7 @@
         mAudioManager.adjustVolume(ADJUST_RAISE, 0);
 
         MediaPlayer mp = MediaPlayer.create(mContext, MP3_TO_PLAY);
+        assertNotNull(mp);
         mp.setAudioStreamType(STREAM_MUSIC);
         mp.setLooping(true);
         mp.start();
diff --git a/tests/tests/os/src/android/os/cts/BuildVersionTest.java b/tests/tests/os/src/android/os/cts/BuildVersionTest.java
index e47bf1f..3cb0181 100644
--- a/tests/tests/os/src/android/os/cts/BuildVersionTest.java
+++ b/tests/tests/os/src/android/os/cts/BuildVersionTest.java
@@ -29,7 +29,7 @@
 
     private static final String LOG_TAG = "BuildVersionTest";
     private static final Set<String> EXPECTED_RELEASES =
-            new HashSet<String>(Arrays.asList("4.4W", "4.4", "4.4.1", "4.4.2", "4.4.3"));
+        new HashSet<String>(Arrays.asList("4.4W.1", "4.4W", "4.4", "4.4.1", "4.4.2", "4.4.3"));
     private static final int EXPECTED_SDK = 20;
     private static final Set<String> EXPECTED_BUILD_VARIANTS =
             new HashSet<String>(Arrays.asList("user", "userdebug", "eng"));
diff --git a/tests/tests/provider/src/android/provider/cts/ContactsContract_PhoneLookup.java b/tests/tests/provider/src/android/provider/cts/ContactsContract_PhoneLookup.java
new file mode 100644
index 0000000..434ac20
--- /dev/null
+++ b/tests/tests/provider/src/android/provider/cts/ContactsContract_PhoneLookup.java
@@ -0,0 +1,195 @@
+/*
+ * Copyright (C) 2014 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 android.provider.cts;
+
+import android.content.ContentProviderClient;
+import android.content.ContentResolver;
+import android.content.ContentValues;
+import android.net.Uri;
+import android.provider.ContactsContract;
+import android.provider.ContactsContract.CommonDataKinds.Email;
+import android.provider.ContactsContract.CommonDataKinds.Phone;
+import android.provider.ContactsContract.CommonDataKinds.StructuredName;
+import android.provider.ContactsContract.Contacts;
+import android.provider.ContactsContract.PhoneLookup;
+import android.provider.ContactsContract.RawContacts;
+import android.provider.cts.ContactsContract_TestDataBuilder.TestContact;
+import android.provider.cts.ContactsContract_TestDataBuilder.TestRawContact;
+import android.provider.cts.contacts.DatabaseAsserts;
+import android.test.AndroidTestCase;
+
+/**
+ * Test for {@link android.provider.ContactsContract.PhoneLookup}.
+ * <p>
+ * This covers {@link PhoneLookup#CONTENT_FILTER_URI} and
+ * {@link PhoneLookup#ENTERPRISE_CONTENT_FILTER_URI}.
+ *
+ * TODO We don't yet have tests to cover cross-user provider access for the later, since multi-user
+ * cases aren't well supported in CTS yet.  Tracking in internal bug/16462089 .
+ */
+public class ContactsContract_PhoneLookup extends AndroidTestCase {
+    private ContentResolver mResolver;
+    private ContactsContract_TestDataBuilder mBuilder;
+
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+        mResolver = getContext().getContentResolver();
+        ContentProviderClient provider =
+                mResolver.acquireContentProviderClient(ContactsContract.AUTHORITY);
+        mBuilder = new ContactsContract_TestDataBuilder(provider);
+    }
+
+    @Override
+    protected void tearDown() throws Exception {
+        super.tearDown();
+        mBuilder.cleanup();
+    }
+
+    private long[] setupTestData() throws Exception {
+        TestRawContact rawContact = mBuilder.newRawContact()
+                .with(RawContacts.ACCOUNT_TYPE, "test_account")
+                .with(RawContacts.ACCOUNT_NAME, "test_name")
+                .insert();
+        rawContact.newDataRow(StructuredName.CONTENT_ITEM_TYPE)
+                .with(StructuredName.DISPLAY_NAME, "Hot Tamale")
+                .insert();
+        rawContact.newDataRow(Phone.CONTENT_ITEM_TYPE)
+                .with(Phone.DATA, "1111222333444")
+                .with(Email.TYPE, Phone.TYPE_HOME)
+                .insert().load().getId();
+        rawContact.load();
+        TestContact contact = rawContact.getContact().load();
+
+        TestRawContact rawContact2 = mBuilder.newRawContact()
+                .with(RawContacts.ACCOUNT_TYPE, "test_account")
+                .with(RawContacts.ACCOUNT_NAME, "test_name")
+                .insert();
+        rawContact2.newDataRow(StructuredName.CONTENT_ITEM_TYPE)
+                .with(StructuredName.DISPLAY_NAME, "Cold Tamago")
+                .insert();
+        rawContact2.newDataRow(Phone.CONTENT_ITEM_TYPE)
+                .with(Phone.DATA, "2111222333444")
+                .with(Phone.TYPE, Phone.TYPE_OTHER)
+                .insert().load();
+
+        rawContact2.load();
+        TestContact contact2 = rawContact2.getContact().load();
+
+        return new long[] {
+                contact.getId(), contact2.getId()
+        };
+    }
+
+    /**
+     * Test for {@link android.provider.ContactsContract.PhoneLookup#CONTENT_FILTER_URI}.
+     */
+    public void testPhoneLookup_nomatch() throws Exception {
+        long[] ids = setupTestData();
+        final Uri uri = PhoneLookup.CONTENT_FILTER_URI.buildUpon()
+                .appendPath("no-such-phone-number").build();
+
+        assertCursorStoredValuesWithContactsFilter(uri, ids /*, empty */);
+    }
+
+    /**
+     * Test for {@link android.provider.ContactsContract.PhoneLookup#CONTENT_FILTER_URI}.
+     */
+    public void testPhoneLookup_found1() throws Exception {
+        long[] ids = setupTestData();
+        final Uri uri = PhoneLookup.CONTENT_FILTER_URI.buildUpon()
+                .appendPath("1111222333444").build();
+
+        final ContentValues expected = new ContentValues();
+        expected.put(PhoneLookup._ID, ids[0]);
+        expected.put(PhoneLookup.NUMBER, "1111222333444");
+
+        assertCursorStoredValuesWithContactsFilter(uri, ids, expected);
+    }
+
+    /**
+     * Test for {@link android.provider.ContactsContract.PhoneLookup#CONTENT_FILTER_URI}.
+     */
+    public void testPhoneLookup_found2() throws Exception {
+        long[] ids = setupTestData();
+        final Uri uri = PhoneLookup.CONTENT_FILTER_URI.buildUpon()
+                .appendPath("2111222333444").build();
+
+        final ContentValues expected = new ContentValues();
+        expected.put(PhoneLookup._ID, ids[1]);
+        expected.put(PhoneLookup.NUMBER, "2111222333444");
+
+        assertCursorStoredValuesWithContactsFilter(uri, ids, expected);
+    }
+
+    /**
+     * Test for {@link android.provider.ContactsContract.PhoneLookup#ENTERPRISE_CONTENT_FILTER_URI}.
+     */
+    public void testPhoneLookupEnterprise_nomatch() throws Exception {
+        long[] ids = setupTestData();
+        final Uri uri = PhoneLookup.ENTERPRISE_CONTENT_FILTER_URI.buildUpon()
+                .appendPath("no-such-phone-number").build();
+
+        assertCursorStoredValuesWithContactsFilter(uri, ids /*, empty */);
+    }
+
+    /**
+     * Test for {@link android.provider.ContactsContract.PhoneLookup#ENTERPRISE_CONTENT_FILTER_URI}.
+     */
+    public void testPhoneLookupEnterprise_found1() throws Exception {
+        long[] ids = setupTestData();
+        final Uri uri = PhoneLookup.ENTERPRISE_CONTENT_FILTER_URI.buildUpon()
+                .appendPath("1111222333444").build();
+
+        final ContentValues expected = new ContentValues();
+        expected.put(PhoneLookup._ID, ids[0]);
+        expected.put(PhoneLookup.NUMBER, "1111222333444");
+
+        assertCursorStoredValuesWithContactsFilter(uri, ids, expected);
+    }
+
+    /**
+     * Test for {@link android.provider.ContactsContract.PhoneLookup#ENTERPRISE_CONTENT_FILTER_URI}.
+     */
+    public void testPhoneLookupEnterprise_found2() throws Exception {
+        long[] ids = setupTestData();
+        final Uri uri = PhoneLookup.ENTERPRISE_CONTENT_FILTER_URI.buildUpon()
+                .appendPath("2111222333444").build();
+
+        final ContentValues expected = new ContentValues();
+        expected.put(PhoneLookup._ID, ids[1]);
+        expected.put(PhoneLookup.NUMBER, "2111222333444");
+
+        assertCursorStoredValuesWithContactsFilter(uri, ids, expected);
+    }
+
+    private void assertCursorStoredValuesWithContactsFilter(Uri uri, long[] contactsId,
+            ContentValues... expected) {
+        // We need this helper function to add a filter for specific contacts because
+        // otherwise tests will fail if performed on a device with existing contacts data
+        StringBuilder sb = new StringBuilder();
+        sb.append(Contacts._ID + " in ");
+        sb.append("(");
+        for (int i = 0; i < contactsId.length; i++) {
+            if (i != 0) sb.append(",");
+            sb.append(contactsId[i]);
+        }
+        sb.append(")");
+        DatabaseAsserts.assertStoredValuesInUriMatchExactly(mResolver, uri, null,
+                sb.toString(), null, null, false, expected);
+    }
+}
diff --git a/tests/tests/security/src/android/security/cts/CertificateData.java b/tests/tests/security/src/android/security/cts/CertificateData.java
index 8a4d603..2912b19 100644
--- a/tests/tests/security/src/android/security/cts/CertificateData.java
+++ b/tests/tests/security/src/android/security/cts/CertificateData.java
@@ -27,6 +27,7 @@
   static final String[] CERTIFICATE_DATA = {
       "91:C6:D6:EE:3E:8A:C8:63:84:E5:48:C2:99:29:5C:75:6C:81:7B:81",
       "4A:65:D5:F4:1D:EF:39:B8:B8:90:4A:4A:D3:64:81:33:CF:C7:A1:D1",
+      "16:32:47:8D:89:F9:21:3A:92:00:85:63:F5:A4:A7:D3:12:40:8A:D6",
       "4D:23:78:EC:91:95:39:B5:00:7F:75:8F:03:3B:21:1E:C5:4D:8B:CF",
       "E7:B4:F6:9D:61:EC:90:69:DB:7E:90:A7:40:1A:3C:F4:7D:4F:E8:EE",
       "DD:E1:D2:A9:01:80:2E:1D:87:5E:84:B3:80:7E:4B:B1:FD:99:41:34",
@@ -38,19 +39,18 @@
       "4F:99:AA:93:FB:2B:D1:37:26:A1:99:4A:CE:7F:F0:05:F2:93:5D:1E",
       "74:20:74:41:72:9C:DD:92:EC:79:31:D8:23:10:8D:C2:81:92:E2:BB",
       "40:54:DA:6F:1C:3F:40:74:AC:ED:0F:EC:CD:DB:79:D1:53:FB:90:1D",
-      "43:F9:B1:10:D5:BA:FD:48:22:52:31:B0:D0:08:2B:37:2F:EF:9A:54",
       "F4:8B:11:BF:DE:AB:BE:94:54:20:71:E6:41:DE:6B:BE:88:2B:40:B9",
       "58:E8:AB:B0:36:15:33:FB:80:F7:9B:1B:6D:29:D3:FF:8D:5F:00:F0",
       "55:A6:72:3E:CB:F2:EC:CD:C3:23:74:70:19:9D:2A:BE:11:E3:81:D1",
       "D6:9B:56:11:48:F0:1C:77:C5:45:78:C1:09:26:DF:5B:85:69:76:AD",
       "78:6A:74:AC:76:AB:14:7F:9C:6A:30:50:BA:9E:A8:7E:FE:9A:CE:3C",
+      "09:3C:61:F3:8B:8B:DC:7D:55:DF:75:38:02:05:00:E1:25:F5:C8:36",
       "8E:1C:74:F8:A6:20:B9:E5:8A:F4:61:FA:EC:2B:47:56:51:1A:52:C6",
       "27:96:BA:E6:3F:18:01:E2:77:26:1B:A0:D7:77:70:02:8F:20:EE:E4",
       "AD:7E:1C:28:B0:64:EF:8F:60:03:40:20:14:C3:D0:E3:37:0E:B5:8A",
       "8D:17:84:D5:37:F3:03:7D:EC:70:FE:57:8B:51:9A:99:E6:10:D7:B0",
       "AE:50:83:ED:7C:F4:5C:BC:8F:61:C6:21:FE:68:5D:79:42:21:15:6E",
       "DA:FA:F7:FA:66:84:EC:06:8F:14:50:BD:C7:C2:81:A5:BC:A9:64:57",
-      "5F:4E:1F:CF:31:B7:91:3B:85:0B:54:F6:E5:FF:50:1A:2B:6F:C6:CF",
       "74:F8:A3:C3:EF:E7:B3:90:06:4B:83:90:3C:21:64:60:20:E5:DF:CE",
       "85:B5:FF:67:9B:0C:79:96:1F:C8:6E:44:22:00:46:13:DB:17:92:84",
       "3E:2B:F7:F2:03:1B:96:F3:8C:E6:C4:D8:A8:5D:3E:2D:58:47:6A:0F",
@@ -68,42 +68,36 @@
       "1B:4B:39:61:26:27:6B:64:91:A2:68:6D:D7:02:43:21:2D:1F:1D:96",
       "77:47:4F:C6:30:E4:0F:4C:47:64:3F:84:BA:B8:C6:95:4A:8A:41:EC",
       "8C:F4:27:FD:79:0C:3A:D1:66:06:8D:E8:1E:57:EF:BB:93:22:72:D4",
-      "02:72:68:29:3E:5F:5D:17:AA:A4:B3:C3:E6:36:1E:1F:92:57:5E:AA",
       "2F:78:3D:25:52:18:A7:4A:65:39:71:B5:2C:A2:9C:45:15:6F:E9:19",
-      "97:81:79:50:D8:1C:96:70:CC:34:D8:09:CF:79:44:31:36:7E:F4:74",
       "85:A4:08:C0:9C:19:3E:5D:51:58:7D:CD:D6:13:30:FD:8C:DE:37:BF",
       "58:11:9F:0E:12:82:87:EA:50:FD:D9:87:45:6F:4F:78:DC:FA:D6:D4",
       "6B:2F:34:AD:89:58:BE:62:FD:B0:6B:5C:CE:BB:9D:D9:4F:4E:39:F3",
       "9B:AA:E5:9F:56:EE:21:CB:43:5A:BE:25:93:DF:A7:F0:40:D1:1D:CB",
       "36:79:CA:35:66:87:72:30:4D:30:A5:FB:87:3B:0F:A7:7B:B7:0D:54",
+      "1B:8E:EA:57:96:29:1A:C9:39:EA:B8:0A:81:1A:73:73:C0:93:79:67",
       "B4:35:D4:E1:11:9D:1C:66:90:A7:49:EB:B3:94:BD:63:7B:A7:82:B7",
       "A9:E9:78:08:14:37:58:88:F2:05:19:B0:6D:2B:0D:2B:60:16:90:7D",
       "60:D6:89:74:B5:C2:65:9E:8A:0F:C1:88:7C:88:D2:46:69:1B:18:2C",
       "D2:32:09:AD:23:D3:14:23:21:74:E4:0D:7F:9D:62:13:97:86:63:3A",
       "66:31:BF:9E:F7:4F:9E:B6:C9:D5:A6:0C:BA:6A:BE:D1:F7:BD:EF:7B",
-      "87:9F:4B:EE:05:DF:98:58:3B:E3:60:D6:33:E7:0D:3F:FE:98:71:AF",
       "DE:3F:40:BD:50:93:D3:9B:6C:60:F6:DA:BC:07:62:01:00:89:76:C9",
       "22:D5:D8:DF:8F:02:31:D1:8D:F7:9D:B7:CF:8A:2D:64:C9:3F:6C:3A",
       "F3:73:B3:87:06:5A:28:84:8A:F2:F3:4A:CE:19:2B:DD:C7:8E:9C:AC",
       "06:08:3F:59:3F:15:A1:04:A0:69:A4:6B:A9:03:D0:06:B7:97:09:91",
-      "E3:92:51:2F:0A:CF:F5:05:DF:F6:DE:06:7F:75:37:E1:65:EA:57:4B",
       "43:13:BB:96:F1:D5:86:9B:C1:4E:6A:92:F6:CF:F6:34:69:87:82:37",
       "F1:8B:53:8D:1B:E9:03:B6:A6:F0:56:43:5B:17:15:89:CA:F3:6B:F2",
       "05:63:B8:63:0D:62:D7:5A:BB:C8:AB:1E:4B:DF:B5:A8:99:B2:4D:43",
-      "93:E6:AB:22:03:03:B5:23:28:DC:DA:56:9E:BA:E4:D1:D1:CC:FB:65",
       "62:52:DC:40:F7:11:43:A2:2F:DE:9E:F7:34:8E:06:42:51:B1:81:18",
       "70:17:9B:86:8C:00:A4:FA:60:91:52:22:3F:9F:3E:32:BD:E0:05:62",
       "A0:A1:AB:90:C9:FC:84:7B:3B:12:61:E8:97:7D:5F:D3:22:61:D3:CC",
       "85:37:1C:A6:E5:50:14:3D:CE:28:03:47:1B:DE:3A:09:E8:F8:77:0F",
       "7E:78:4A:10:1C:82:65:CC:2D:E1:F1:6D:47:B4:40:CA:D9:0A:19:45",
       "D1:EB:23:A4:6D:17:D6:8F:D9:25:64:C2:F1:F1:60:17:64:D8:E3:49",
-      "A1:DB:63:93:91:6F:17:E4:18:55:09:40:04:15:C7:02:40:B0:AE:6B",
       "B8:01:86:D1:EB:9C:86:A5:41:04:CF:30:54:F3:4C:52:B7:E5:58:C6",
       "2E:14:DA:EC:28:F0:FA:1E:8E:38:9A:4E:AB:EB:26:C0:0A:D3:83:C3",
       "DE:28:F4:A4:FF:E5:B9:2F:A3:C5:03:D1:A3:49:A7:F9:96:2A:82:12",
       "80:25:EF:F4:6E:70:C8:D4:72:24:65:84:FE:40:3B:8A:8D:6A:DB:F5",
       "CA:3A:FB:CF:12:40:36:4B:44:B2:16:20:88:80:48:39:19:93:7C:F7",
-      "69:BD:8C:F4:9C:D3:00:FB:59:2E:17:93:CA:55:6A:F3:EC:AA:35:FB",
       "13:2D:0D:45:53:4B:69:97:CD:B2:D5:C3:39:E2:55:76:60:9B:5C:C6",
       "5F:B7:EE:06:33:E2:59:DB:AD:0C:4C:9A:E6:D3:8F:1A:61:C7:DC:25",
       "49:0A:75:74:DE:87:0A:47:FE:58:EE:F6:C7:6B:EB:C6:0B:12:40:99",
@@ -115,10 +109,12 @@
       "FA:B7:EE:36:97:26:62:FB:2D:B0:2A:F6:BF:03:FD:E8:7C:4B:2F:9B",
       "8B:AF:4C:9B:1D:F0:2A:92:F7:DA:12:8E:B9:1B:AC:F4:98:60:4B:6F",
       "9F:74:4E:9F:2B:4D:BA:EC:0F:31:2C:50:B6:56:3B:8E:2D:93:C3:11",
+      "A1:4B:48:D9:43:EE:0A:0E:40:90:4F:3C:E0:A4:C0:91:93:51:5D:3F",
       "C9:A8:B9:E7:55:80:5E:58:E3:53:77:A7:25:EB:AF:C3:7B:27:CC:D7",
       "1F:49:14:F7:D8:74:95:1D:DD:AE:02:C0:BE:FD:3A:2D:82:75:51:85",
       "B5:61:EB:EA:A4:DE:E4:25:4B:69:1A:98:A5:57:47:C2:34:C7:D9:71",
       "07:E0:32:E0:20:B7:2C:3F:19:2F:06:28:A2:59:3A:19:A7:0F:06:9E",
+      "B9:42:94:BF:91:EA:8F:B6:4B:E6:10:97:C7:FB:00:13:59:B6:76:CB",
       "D6:DA:A8:20:8D:09:D2:15:4D:24:B5:2F:CB:34:6E:B2:58:B2:8A:58",
       "32:3C:11:8E:1B:F7:B8:B6:52:54:E2:E2:10:0D:D6:02:90:37:F0:96",
       "E7:A1:90:29:D3:D5:52:DC:0D:0F:C6:92:D3:EA:88:0D:15:2E:1A:6B",
@@ -126,6 +122,7 @@
       "FE:B8:C4:32:DC:F9:76:9A:CE:AE:3D:D8:90:8F:FD:28:86:65:64:7D",
       "4A:BD:EE:EC:95:0D:35:9C:89:AE:C7:52:A1:2C:5B:29:F6:D6:AA:0C",
       "33:9B:6B:14:50:24:9B:55:7A:01:87:72:84:D9:E0:2F:C3:D2:D8:E9",
+      "DD:FB:16:CD:49:31:C9:73:A2:03:7D:3F:C8:3A:4D:7D:77:5D:05:E4",
       "2A:B6:28:48:5E:78:FB:F3:AD:9E:79:10:DD:6B:DF:99:72:2C:96:E5",
       "36:B1:2B:49:F9:81:9E:D7:4C:9E:BC:38:0F:C6:56:8F:5D:AC:B2:F7",
       "37:F7:6D:E6:07:7C:90:C5:B1:3E:93:1A:B7:41:10:B4:F2:E4:9A:27",
@@ -139,22 +136,22 @@
       "CF:9E:87:6D:D3:EB:FC:42:26:97:A3:B5:A3:7A:A0:76:A9:06:23:48",
       "2B:B1:F5:3E:55:0C:1D:C5:F1:D4:E6:B7:6A:46:4B:55:06:02:AC:21",
       "47:BE:AB:C9:22:EA:E8:0E:78:78:34:62:A7:9F:45:C2:54:FD:E6:8B",
-      "31:7A:2A:D0:7F:2B:33:5E:F5:A1:C3:4E:4B:57:E8:B7:D8:F1:FC:A6",
       "39:21:C1:15:C1:5D:0E:CA:5C:CB:5B:C4:F0:7D:21:D8:05:0B:56:6A",
       "3A:44:73:5A:E5:81:90:1F:24:86:61:46:1E:3B:9C:C4:5F:F5:3A:1B",
       "B3:1E:B1:B7:40:E3:6C:84:02:DA:DC:37:D4:4D:F5:D4:67:49:52:F9",
       "E0:AB:05:94:20:72:54:93:05:60:62:02:36:70:F7:CD:2E:FC:66:66",
       "D3:C0:63:F2:19:ED:07:3E:34:AD:5D:75:0B:32:76:29:FF:D5:9A:F2",
+      "F5:17:A2:4F:9A:48:C6:C9:F8:A2:00:26:9F:DC:0F:48:2C:AB:30:89",
       "3B:C0:38:0B:33:C3:F6:A6:0C:86:15:22:93:D9:DF:F5:4B:81:C0:04",
       "C8:EC:8C:87:92:69:CB:4B:AB:39:E9:8D:7E:57:67:F3:14:95:73:9D",
       "03:9E:ED:B8:0B:E7:A0:3C:69:53:89:3B:20:D2:D9:32:3A:4C:2A:FD",
-      "CB:A1:C5:F8:B0:E3:5E:B8:B9:45:12:D3:F9:34:A2:E9:06:10:D3:36",
+      "DF:3C:24:F9:BF:D6:66:76:1B:26:80:73:FE:06:D1:CC:8D:4F:82:A4",
       "51:C6:E7:08:49:06:6E:F3:92:D4:5C:A0:0D:6D:A3:62:8F:C3:52:39",
       "B8:23:6B:00:2F:1D:16:86:53:01:55:6C:11:A4:37:CA:EB:FF:C3:BB",
       "10:1D:FA:3F:D5:0B:CB:BB:9B:B5:60:0C:19:55:A4:1A:F4:73:3A:04",
       "87:82:C6:C3:04:35:3B:CF:D2:96:92:D2:59:3E:7D:44:D9:34:FF:11",
+      "59:0D:2D:7D:88:4F:40:2E:61:7E:A5:62:32:17:65:CF:17:D8:94:E9",
       "AE:C5:FB:3F:C8:E1:BF:C4:E5:4F:03:07:5A:9A:E8:00:B7:F7:B6:FA",
-      "21:FC:BD:8E:7F:6C:AF:05:1B:D1:B3:43:EC:A8:E7:61:47:F2:0F:8A",
       "5F:3B:8C:F2:F8:10:B3:7D:78:B4:CE:EC:19:19:C3:73:34:B9:C7:74",
       "2A:C8:D5:8B:57:CE:BF:2F:49:AF:F2:FC:76:8F:51:14:62:90:7A:41",
       "F1:7F:6F:B6:31:DC:99:E3:A3:C8:7F:FE:1C:F1:81:10:88:D9:60:33",
@@ -162,6 +159,7 @@
       "D8:A6:33:2C:E0:03:6F:B1:85:F6:63:4F:7D:6A:06:65:26:32:28:27",
       "9F:AD:91:A6:CE:6A:C6:C5:00:47:C4:4E:C9:D4:A5:0D:92:D8:49:79",
       "CC:AB:0E:A0:4C:23:01:D6:69:7B:DD:37:9F:CD:12:EB:24:E3:94:9D",
+      "48:12:BD:92:3C:A8:C4:39:06:E7:30:6D:27:96:E6:A4:CF:22:2E:7D",
       "F9:B5:B6:32:45:5F:9C:BE:EC:57:5F:80:DC:E9:6E:2C:C7:B2:78:B7",
       "5F:3A:FC:0A:8B:64:F6:86:67:34:74:DF:7E:A9:A2:FE:F9:FA:7A:51",
       "E6:21:F3:35:43:79:05:9A:4B:68:30:9D:8A:2F:74:22:15:87:EC:79",
@@ -169,10 +167,9 @@
       "89:DF:74:FE:5C:F4:0F:4A:80:F9:E3:37:7D:54:DA:91:E1:01:31:8E",
       "E0:B4:32:2E:B2:F6:A5:68:B6:54:53:84:48:18:4A:50:36:87:43:84",
       "61:57:3A:11:DF:0E:D8:7E:D5:92:65:22:EA:D0:56:D7:44:B3:23:71",
-      "99:A6:9B:E6:1A:FE:88:6B:4D:2B:82:00:7C:B8:54:FC:31:7E:15:39",
+      "7E:04:DE:89:6A:3E:66:6D:00:E6:87:D3:3F:FA:D9:3B:E8:3D:34:9E",
       "6E:3A:55:A4:19:0C:19:5C:93:84:3C:C0:DB:72:2E:31:30:61:F0:B1",
       "31:F1:FD:68:22:63:20:EE:C6:3B:3F:9D:EA:4A:3E:53:7C:7C:39:17",
-      "E5:DF:74:3C:B6:01:C4:9B:98:43:DC:AB:8C:E8:6A:81:10:9F:E4:8E",
       "F9:CD:0E:2C:DA:76:24:C1:8F:BD:F0:F0:AB:B6:45:B8:F7:FE:D5:7A",
       "23:88:C9:D3:71:CC:9E:96:3D:FF:7D:3C:A7:CE:FC:D6:25:EC:19:0D",
       "8C:96:BA:EB:DD:2B:07:07:48:EE:30:32:66:A0:F3:98:6E:7C:AE:58",
diff --git a/tests/tests/tv/Android.mk b/tests/tests/tv/Android.mk
index 5df50ca..2ffe166 100644
--- a/tests/tests/tv/Android.mk
+++ b/tests/tests/tv/Android.mk
@@ -24,8 +24,12 @@
 
 LOCAL_PACKAGE_NAME := CtsTvTestCases
 
+LOCAL_JAVA_LIBRARIES := android.test.runner
+
 LOCAL_STATIC_JAVA_LIBRARIES := ctsdeviceutil ctstestrunner
 
+LOCAL_INSTRUMENTATION_FOR := CtsTestStubs
+
 LOCAL_SDK_VERSION := current
 
 include $(BUILD_CTS_PACKAGE)
diff --git a/tests/tests/tv/AndroidManifest.xml b/tests/tests/tv/AndroidManifest.xml
index 66f4ac0..fb84509 100644
--- a/tests/tests/tv/AndroidManifest.xml
+++ b/tests/tests/tv/AndroidManifest.xml
@@ -21,8 +21,8 @@
 
     <uses-permission android:name="android.permission.DISABLE_KEYGUARD" />
 
-    <uses-permission android:name="android.permission.READ_EPG_DATA" />
-    <uses-permission android:name="android.permission.WRITE_EPG_DATA" />
+    <uses-permission android:name="com.android.providers.tv.permission.READ_EPG_DATA" />
+    <uses-permission android:name="com.android.providers.tv.permission.WRITE_EPG_DATA" />
 
     <application>
         <uses-library android:name="android.test.runner" />
diff --git a/tests/tests/tv/src/android/media/tv/cts/StubTunerTvInputService.java b/tests/tests/tv/src/android/media/tv/cts/StubTunerTvInputService.java
index 661a8d7..c2c9a1b 100644
--- a/tests/tests/tv/src/android/media/tv/cts/StubTunerTvInputService.java
+++ b/tests/tests/tv/src/android/media/tv/cts/StubTunerTvInputService.java
@@ -31,9 +31,20 @@
 import android.view.Surface;
 
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.List;
 
 public class StubTunerTvInputService extends TvInputService {
+    private static final List<TvTrackInfo> mTrackList = new ArrayList<>();
+
+    public static void clearTracks() {
+        mTrackList.clear();
+    }
+
+    public static void injectTrack(TvTrackInfo... tracks) {
+        mTrackList.addAll(Arrays.asList(tracks));
+    }
+
     public static void insertChannels(ContentResolver resolver, TvInputInfo info) {
         if (!info.getServiceInfo().name.equals(StubTunerTvInputService.class.getName())) {
             throw new IllegalArgumentException("info mismatch");
@@ -69,41 +80,16 @@
         return new StubSessionImpl(this);
     }
 
-    private static class StubSessionImpl extends Session {
+    static class StubSessionImpl extends Session {
         private static final int[] COLORS = { Color.RED, Color.GREEN, Color.BLUE };
         private Surface mSurface;
         private Object mLock = new Object();
         private int mCurrentIndex = -1;
         private Context mContext;
-        private final List<TvTrackInfo> mTrackList = new ArrayList<>();
-        private final TvTrackInfo mVideoTrack1;
-        private final TvTrackInfo mVideoTrack2;
-        private final TvTrackInfo mAudioTrack1;
-        private final TvTrackInfo mAudioTrack2;
-        private final TvTrackInfo mSubtitleTrack1;
-        private final TvTrackInfo mSubtitleTrack2;
 
         StubSessionImpl(Context context) {
             super(context);
             mContext = context;
-            mVideoTrack1 = new TvTrackInfo.Builder(TvTrackInfo.TYPE_VIDEO, "video-HD")
-                    .setVideoHeight(1920).setVideoWidth(1080).build();
-            mVideoTrack2 = new TvTrackInfo.Builder(TvTrackInfo.TYPE_VIDEO, "video-SD")
-                    .setVideoHeight(640).setVideoWidth(360).build();
-            mAudioTrack1 = new TvTrackInfo.Builder(TvTrackInfo.TYPE_AUDIO, "audio-stereo-eng")
-                    .setLanguage("eng").setAudioChannelCount(2).setAudioSampleRate(48000).build();
-            mAudioTrack2 = new TvTrackInfo.Builder(TvTrackInfo.TYPE_AUDIO, "audio-mono-esp")
-                    .setLanguage("esp").setAudioChannelCount(1).setAudioSampleRate(48000).build();
-            mSubtitleTrack1 = new TvTrackInfo.Builder(TvTrackInfo.TYPE_SUBTITLE, "subtitle-eng")
-                    .setLanguage("eng").build();
-            mSubtitleTrack2 = new TvTrackInfo.Builder(TvTrackInfo.TYPE_SUBTITLE, "subtitle-esp")
-                    .setLanguage("esp").build();
-            mTrackList.add(mVideoTrack1);
-            mTrackList.add(mVideoTrack2);
-            mTrackList.add(mAudioTrack1);
-            mTrackList.add(mAudioTrack2);
-            mTrackList.add(mSubtitleTrack1);
-            mTrackList.add(mSubtitleTrack2);
         }
 
         @Override
@@ -112,9 +98,13 @@
 
         private void updateSurfaceLocked() {
             if (mCurrentIndex >= 0 && mSurface != null) {
-                Canvas c = mSurface.lockCanvas(null);
-                c.drawColor(COLORS[mCurrentIndex]);
-                mSurface.unlockCanvasAndPost(c);
+                try {
+                    Canvas c = mSurface.lockCanvas(null);
+                    c.drawColor(COLORS[mCurrentIndex]);
+                    mSurface.unlockCanvasAndPost(c);
+                } catch (IllegalArgumentException e) {
+                    mSurface = null;
+                }
             }
         }
 
@@ -133,8 +123,8 @@
 
         @Override
         public boolean onTune(Uri channelUri) {
+            notifyVideoUnavailable(TvInputManager.VIDEO_UNAVAILABLE_REASON_TUNING);
             synchronized (mLock) {
-                notifyVideoUnavailable(TvInputManager.VIDEO_UNAVAILABLE_REASON_TUNING);
                 String[] projection = { TvContract.Channels.COLUMN_INTERNAL_PROVIDER_DATA };
                 Cursor cursor = mContext.getContentResolver().query(
                         channelUri, projection, null, null, null);
@@ -153,13 +143,22 @@
                 // Notify tracks
                 if (mCurrentIndex == 0) {
                     notifyTracksChanged(mTrackList);
-                    notifyTrackSelected(TvTrackInfo.TYPE_VIDEO, mVideoTrack1.getId());
-                    notifyTrackSelected(TvTrackInfo.TYPE_AUDIO, mAudioTrack1.getId());
-                    notifyTrackSelected(TvTrackInfo.TYPE_SUBTITLE, null);
+                    for (TvTrackInfo track : mTrackList) {
+                        if (track.getType() == TvTrackInfo.TYPE_VIDEO) {
+                            notifyTrackSelected(TvTrackInfo.TYPE_VIDEO, track.getId());
+                            break;
+                        }
+                    }
+                    for (TvTrackInfo track : mTrackList) {
+                        if (track.getType() == TvTrackInfo.TYPE_AUDIO) {
+                            notifyTrackSelected(TvTrackInfo.TYPE_AUDIO, track.getId());
+                            break;
+                        }
+                    }
                 }
-                notifyVideoAvailable();
-                return true;
             }
+            notifyVideoAvailable();
+            return true;
         }
 
         @Override
diff --git a/tests/tests/tv/src/android/media/tv/cts/TvContentRatingTest.java b/tests/tests/tv/src/android/media/tv/cts/TvContentRatingTest.java
new file mode 100644
index 0000000..5e6478b
--- /dev/null
+++ b/tests/tests/tv/src/android/media/tv/cts/TvContentRatingTest.java
@@ -0,0 +1,71 @@
+/*
+ * Copyright (C) 2014 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 android.media.tv.cts;
+
+import android.media.tv.TvContentRating;
+
+import java.util.List;
+
+import junit.framework.TestCase;
+
+/**
+ * Test for {@link android.media.tv.TvContentRating}.
+ */
+public class TvContentRatingTest extends TestCase {
+
+    public void testCreateRating() throws Exception {
+        final String DOMAIN = "android.media.tv";
+        final String RATING_SYSTEM = "US_TVPG";
+        final String MAIN_RATING = "US_TVPG_TV_MA";
+        final String SUB_RATING_1 = "US_TVPG_TV_S";
+        final String SUB_RATING_2 = "US_TVPG_TV_V";
+
+        TvContentRating rating = TvContentRating.createRating(DOMAIN, RATING_SYSTEM, MAIN_RATING,
+                SUB_RATING_1, SUB_RATING_2);
+        assertEquals(DOMAIN, rating.getDomain());
+        assertEquals(RATING_SYSTEM, rating.getRatingSystem());
+        assertEquals(MAIN_RATING, rating.getMainRating());
+        List<String> subRatings = rating.getSubRatings();
+        assertEquals(2, subRatings.size());
+        assertTrue("Sub-ratings does not contain " + SUB_RATING_1,
+                subRatings.contains(SUB_RATING_1));
+        assertTrue("Sub-ratings does not contain " + SUB_RATING_2,
+                subRatings.contains(SUB_RATING_2));
+    }
+
+    public void testFlattenAndUnflatten() throws Exception {
+        final String DOMAIN = "android.media.tv";
+        final String RATING_SYSTEM = "US_TVPG";
+        final String MAIN_RATING = "US_TVPG_TV_MA";
+        final String SUB_RATING_1 = "US_TVPG_TV_S";
+        final String SUB_RATING_2 = "US_TVPG_TV_V";
+
+        String flattened = TvContentRating.createRating(DOMAIN, RATING_SYSTEM, MAIN_RATING,
+                SUB_RATING_1, SUB_RATING_2).flattenToString();
+        TvContentRating rating = TvContentRating.unflattenFromString(flattened);
+
+        assertEquals(DOMAIN, rating.getDomain());
+        assertEquals(RATING_SYSTEM, rating.getRatingSystem());
+        assertEquals(MAIN_RATING, rating.getMainRating());
+        List<String> subRatings = rating.getSubRatings();
+        assertEquals(2, subRatings.size());
+        assertTrue("Sub-ratings does not contain " + SUB_RATING_1,
+                subRatings.contains(SUB_RATING_1));
+        assertTrue("Sub-ratings does not contain " + SUB_RATING_2,
+                subRatings.contains(SUB_RATING_2));
+    }
+}
diff --git a/tests/tests/tv/src/android/media/tv/cts/TvContractTest.java b/tests/tests/tv/src/android/media/tv/cts/TvContractTest.java
index e9f7423..6d31a80 100644
--- a/tests/tests/tv/src/android/media/tv/cts/TvContractTest.java
+++ b/tests/tests/tv/src/android/media/tv/cts/TvContractTest.java
@@ -259,10 +259,20 @@
         }
     }
 
+    private void verifyLogoIsReadable(Uri logoUri) throws Exception {
+        try (AssetFileDescriptor fd = mContentResolver.openAssetFileDescriptor(logoUri, "r")) {
+            try (InputStream is = fd.createInputStream()) {
+                // Assure that the stream is decodable as a Bitmap.
+                BitmapFactory.decodeStream(is);
+            }
+        }
+    }
+
     public void testChannelLogo() throws Exception {
         // Set-up: add a channel.
         ContentValues values = createDummyChannelValues(mInputId);
-        Uri logoUri = TvContract.buildChannelLogoUri(mContentResolver.insert(mChannelsUri, values));
+        Uri channelUri = mContentResolver.insert(mChannelsUri, values);
+        Uri logoUri = TvContract.buildChannelLogoUri(channelUri);
         Bitmap logo = BitmapFactory.decodeResource(getContext().getResources(), R.drawable.robot);
 
         // Write
@@ -276,22 +286,15 @@
         Thread.sleep(OPERATION_TIME);
 
         // Read and verify
-        try (AssetFileDescriptor fd = mContentResolver.openAssetFileDescriptor(logoUri, "r")) {
-            try (InputStream is = fd.createInputStream()) {
-                // Assure that the stream is decodable as a Bitmap.
-                BitmapFactory.decodeStream(is);
-            }
-        }
+        verifyLogoIsReadable(logoUri);
+
+        // Read and verify using alternative logo URI.
+        verifyLogoIsReadable(TvContract.buildChannelLogoUri(ContentUris.parseId(channelUri)));
     }
 
-    public void testProgramsTable() throws Exception {
-        // Set-up: add a channel.
-        ContentValues values = createDummyChannelValues(mInputId);
-        long channelId = ContentUris.parseId(mContentResolver.insert(mChannelsUri, values));
-        Uri programsUri = TvContract.buildProgramsUriForChannel(channelId);
-
+    public void verifyProgramsTable(Uri programsUri, long channelId) {
         // Test: insert
-        values = createDummyProgramValues(channelId);
+        ContentValues values = createDummyProgramValues(channelId);
 
         Uri rowUri = mContentResolver.insert(programsUri, values);
         long programId = ContentUris.parseId(rowUri);
@@ -314,56 +317,62 @@
         }
     }
 
+    public void testProgramsTable() throws Exception {
+        // Set-up: add a channel.
+        ContentValues values = createDummyChannelValues(mInputId);
+        Uri channelUri = mContentResolver.insert(mChannelsUri, values);
+        long channelId = ContentUris.parseId(channelUri);
+
+        verifyProgramsTable(TvContract.buildProgramsUriForChannel(channelId), channelId);
+        verifyProgramsTable(TvContract.buildProgramsUriForChannel(channelUri), channelId);
+    }
+
+    private void verifyOverlap(long startMillis, long endMillis, int expectedCount,
+            long channelId, Uri channelUri) {
+        try (Cursor cursor = mContentResolver.query(TvContract.buildProgramsUriForChannel(
+                channelId, startMillis, endMillis), PROGRAMS_PROJECTION, null, null, null)) {
+            assertEquals(expectedCount, cursor.getCount());
+        }
+        try (Cursor cursor = mContentResolver.query(TvContract.buildProgramsUriForChannel(
+                channelUri, startMillis, endMillis), PROGRAMS_PROJECTION, null, null, null)) {
+            assertEquals(expectedCount, cursor.getCount());
+        }
+    }
+
     public void testProgramsScheduleOverlap() throws Exception {
-        final long startMillis = 1403712000000l;  // Jun 25 2014 16:00 UTC
-        final long endMillis = 1403719200000l;  // Jun 25 2014 18:00 UTC
+        final long programStartMillis = 1403712000000l;  // Jun 25 2014 16:00 UTC
+        final long programEndMillis = 1403719200000l;  // Jun 25 2014 18:00 UTC
         final long hour = 3600000l;
 
         // Set-up: add a channel and program.
         ContentValues values = createDummyChannelValues(mInputId);
-        long channelId = ContentUris.parseId(mContentResolver.insert(mChannelsUri, values));
+        Uri channelUri = mContentResolver.insert(mChannelsUri, values);
+        long channelId = ContentUris.parseId(channelUri);
         Uri programsUri = TvContract.buildProgramsUriForChannel(channelId);
         values = createDummyProgramValues(channelId);
-        values.put(TvContract.Programs.COLUMN_START_TIME_UTC_MILLIS, startMillis);
-        values.put(TvContract.Programs.COLUMN_END_TIME_UTC_MILLIS, endMillis);
+        values.put(TvContract.Programs.COLUMN_START_TIME_UTC_MILLIS, programStartMillis);
+        values.put(TvContract.Programs.COLUMN_END_TIME_UTC_MILLIS, programEndMillis);
         mContentResolver.insert(programsUri, values);
 
         // Overlap 1: starts early, ends early.
-        try (Cursor cursor = mContentResolver.query(TvContract.buildProgramsUriForChannel(channelId,
-                startMillis - hour, endMillis - hour), PROGRAMS_PROJECTION, null, null, null)) {
-            assertEquals(1, cursor.getCount());
-        }
+        verifyOverlap(programStartMillis - hour, programEndMillis - hour, 1, channelId, channelUri);
 
         // Overlap 2: starts early, ends late.
-        try (Cursor cursor = mContentResolver.query(TvContract.buildProgramsUriForChannel(channelId,
-                startMillis - hour, endMillis + hour), PROGRAMS_PROJECTION, null, null, null)) {
-            assertEquals(1, cursor.getCount());
-        }
+        verifyOverlap(programStartMillis - hour, programEndMillis + hour, 1, channelId, channelUri);
 
         // Overlap 3: starts early, ends late.
-        try (Cursor cursor = mContentResolver.query(TvContract.buildProgramsUriForChannel(channelId,
-                startMillis + hour / 2, endMillis - hour / 2), PROGRAMS_PROJECTION,
-                null, null, null)) {
-            assertEquals(1, cursor.getCount());
-        }
+        verifyOverlap(programStartMillis + hour / 2, programEndMillis - hour / 2, 1,
+                channelId, channelUri);
 
         // Overlap 4: starts late, ends late.
-        try (Cursor cursor = mContentResolver.query(TvContract.buildProgramsUriForChannel(channelId,
-                startMillis + hour, endMillis + hour), PROGRAMS_PROJECTION, null, null, null)) {
-            assertEquals(1, cursor.getCount());
-        }
+        verifyOverlap(programStartMillis + hour, programEndMillis + hour, 1, channelId, channelUri);
 
         // Non-overlap 1: ends too early.
-        try (Cursor cursor = mContentResolver.query(TvContract.buildProgramsUriForChannel(channelId,
-                startMillis - hour, startMillis - hour / 2), PROGRAMS_PROJECTION,
-                null, null, null)) {
-            assertEquals(0, cursor.getCount());
-        }
+        verifyOverlap(programStartMillis - hour, programStartMillis - hour / 2, 0,
+                channelId, channelUri);
 
         // Non-overlap 2: starts too late
-        try (Cursor cursor = mContentResolver.query(TvContract.buildProgramsUriForChannel(channelId,
-                endMillis + hour, endMillis + hour * 2), PROGRAMS_PROJECTION, null, null, null)) {
-            assertEquals(0, cursor.getCount());
-        }
+        verifyOverlap(programEndMillis + hour, programEndMillis + hour * 2, 0,
+                channelId, channelUri);
     }
 }
diff --git a/tests/tests/tv/src/android/media/tv/cts/TvViewTest.java b/tests/tests/tv/src/android/media/tv/cts/TvViewTest.java
index 6de70f4..960aef7 100644
--- a/tests/tests/tv/src/android/media/tv/cts/TvViewTest.java
+++ b/tests/tests/tv/src/android/media/tv/cts/TvViewTest.java
@@ -32,9 +32,13 @@
 import android.net.Uri;
 import android.util.ArrayMap;
 import android.util.SparseIntArray;
+import android.view.InputEvent;
+import android.view.KeyEvent;
 
 import com.android.cts.tv.R;
 
+import java.util.ArrayList;
+import java.util.Collections;
 import java.util.List;
 import java.util.Map;
 
@@ -61,7 +65,7 @@
         public boolean isVideoAvailable(String inputId) {
             synchronized (mLock) {
                 Boolean available = mVideoAvailableMap.get(inputId);
-                return available == null ? true : available.booleanValue();
+                return available == null ? false : available.booleanValue();
             }
         }
 
@@ -158,6 +162,24 @@
         mTvView.setTvInputListener(mListener);
     }
 
+    @Override
+    protected void tearDown() throws Exception {
+        StubTunerTvInputService.deleteChannels(mActivity.getContentResolver(), mStubInfo);
+        StubTunerTvInputService.clearTracks();
+        try {
+            runTestOnUiThread(new Runnable() {
+                @Override
+                public void run() {
+                    mTvView.reset();
+                }
+            });
+        } catch (Throwable t) {
+            throw new RuntimeException(t);
+        }
+        mInstrumentation.waitForIdleSync();
+        super.tearDown();
+    }
+
     public void testConstructor() throws Exception {
         new TvView(mActivity);
 
@@ -171,9 +193,8 @@
 
         Uri uri = TvContract.buildChannelsUriForInput(mStubInfo.getId());
         String[] projection = { TvContract.Channels._ID };
-        Cursor cursor = mActivity.getContentResolver().query(
-                uri, projection, null, null, null);
-        try {
+        try (Cursor cursor = mActivity.getContentResolver().query(
+                uri, projection, null, null, null)) {
             while (cursor != null && cursor.moveToNext()) {
                 long channelId = cursor.getLong(0);
                 Uri channelUri = TvContract.buildChannelUri(channelId);
@@ -190,20 +211,7 @@
                     runOnEachChannel.run();
                 }
             }
-        } finally {
-            if (cursor != null) {
-                cursor.close();
-            }
         }
-        runTestOnUiThread(new Runnable() {
-            @Override
-            public void run() {
-                mTvView.reset();
-            }
-        });
-        mInstrumentation.waitForIdleSync();
-
-        StubTunerTvInputService.deleteChannels(mActivity.getContentResolver(), mStubInfo);
     }
 
     public void testSimpleTune() throws Throwable {
@@ -213,7 +221,8 @@
         tryTuneAllChannels(null);
     }
 
-    private void selectTrackAndVerify(final int type, final TvTrackInfo track) {
+    private void selectTrackAndVerify(final int type, final TvTrackInfo track,
+            List<TvTrackInfo> tracks) {
         final int previousGeneration = mListener.getSelectedTrackGeneration(
                 mStubInfo.getId(), type);
         mTvView.selectTrack(type, track == null ? null : track.getId());
@@ -224,13 +233,65 @@
                         mStubInfo.getId(), type) > previousGeneration;
             }
         }.run();
-        assertEquals(mTvView.getSelectedTrack(type), track == null ? null : track.getId());
+        String selectedTrackId = mTvView.getSelectedTrack(type);
+        assertEquals(selectedTrackId, track == null ? null : track.getId());
+        if (selectedTrackId != null) {
+            TvTrackInfo selectedTrack = null;
+            for (TvTrackInfo item : tracks) {
+                if (item.getId().equals(selectedTrackId)) {
+                    selectedTrack = item;
+                    break;
+                }
+            }
+            assertNotNull(selectedTrack);
+            assertEquals(track.getType(), selectedTrack.getType());
+            assertEquals(track.getExtra(), selectedTrack.getExtra());
+            switch (track.getType()) {
+                case TvTrackInfo.TYPE_VIDEO:
+                    assertEquals(track.getVideoHeight(), selectedTrack.getVideoHeight());
+                    assertEquals(track.getVideoWidth(), selectedTrack.getVideoWidth());
+                    break;
+                case TvTrackInfo.TYPE_AUDIO:
+                    assertEquals(track.getAudioChannelCount(),
+                            selectedTrack.getAudioChannelCount());
+                    assertEquals(track.getAudioSampleRate(), selectedTrack.getAudioSampleRate());
+                    assertEquals(track.getLanguage(), selectedTrack.getLanguage());
+                    break;
+                case TvTrackInfo.TYPE_SUBTITLE:
+                    assertEquals(track.getLanguage(), selectedTrack.getLanguage());
+                    break;
+                default:
+                    fail("Unrecognized type: " + track.getType());
+            }
+        }
     }
 
     public void testTrackChange() throws Throwable {
         if (!Utils.hasTvInputFramework(getActivity())) {
             return;
         }
+        TvTrackInfo videoTrack1 = new TvTrackInfo.Builder(TvTrackInfo.TYPE_VIDEO, "video-HD")
+                .setVideoHeight(1920).setVideoWidth(1080).build();
+        TvTrackInfo videoTrack2 = new TvTrackInfo.Builder(TvTrackInfo.TYPE_VIDEO, "video-SD")
+                .setVideoHeight(640).setVideoWidth(360).build();
+        TvTrackInfo audioTrack1 =
+                new TvTrackInfo.Builder(TvTrackInfo.TYPE_AUDIO, "audio-stereo-eng")
+                .setLanguage("eng").setAudioChannelCount(2).setAudioSampleRate(48000).build();
+        TvTrackInfo audioTrack2 = new TvTrackInfo.Builder(TvTrackInfo.TYPE_AUDIO, "audio-mono-esp")
+                .setLanguage("esp").setAudioChannelCount(1).setAudioSampleRate(48000).build();
+        TvTrackInfo subtitleTrack1 =
+                new TvTrackInfo.Builder(TvTrackInfo.TYPE_SUBTITLE, "subtitle-eng")
+                .setLanguage("eng").build();
+        TvTrackInfo subtitleTrack2 =
+                new TvTrackInfo.Builder(TvTrackInfo.TYPE_SUBTITLE, "subtitle-esp")
+                .setLanguage("esp").build();
+
+        StubTunerTvInputService.injectTrack(videoTrack1, videoTrack2, audioTrack1, audioTrack2,
+                subtitleTrack1, subtitleTrack2);
+
+        final List<TvTrackInfo> tracks = new ArrayList<TvTrackInfo>();
+        Collections.addAll(tracks, videoTrack1, videoTrack2, audioTrack1, audioTrack2,
+                subtitleTrack1, subtitleTrack2);
         tryTuneAllChannels(new Runnable() {
             @Override
             public void run() {
@@ -245,11 +306,70 @@
                 for (int type : types) {
                     final int typeF = type;
                     for (TvTrackInfo track : mTvView.getTracks(type)) {
-                        selectTrackAndVerify(type, track);
+                        selectTrackAndVerify(type, track, tracks);
                     }
-                    selectTrackAndVerify(TvTrackInfo.TYPE_SUBTITLE, null);
+                    selectTrackAndVerify(TvTrackInfo.TYPE_SUBTITLE, null, tracks);
                 }
             }
         });
     }
+
+    private void verifyKeyEvent(final KeyEvent keyEvent, final InputEvent[] unhandledEvent) {
+        unhandledEvent[0] = null;
+        mInstrumentation.sendKeySync(keyEvent);
+        mInstrumentation.waitForIdleSync();
+        new PollingCheck(TIME_OUT) {
+            @Override
+            protected boolean check() {
+                return unhandledEvent[0] != null;
+            }
+        }.run();
+        assertTrue(unhandledEvent[0] instanceof KeyEvent);
+        KeyEvent unhandled = (KeyEvent) unhandledEvent[0];
+        assertEquals(unhandled.getAction(), keyEvent.getAction());
+        assertEquals(unhandled.getKeyCode(), keyEvent.getKeyCode());
+    }
+
+    public void testOnUnhandledInputEventListener() throws Throwable {
+        final InputEvent[] unhandledEvent = { null };
+        mTvView.setOnUnhandledInputEventListener(new TvView.OnUnhandledInputEventListener() {
+            @Override
+            public boolean onUnhandledInputEvent(InputEvent event) {
+                unhandledEvent[0] = event;
+                return true;
+            }
+        });
+
+        StubTunerTvInputService.insertChannels(mActivity.getContentResolver(), mStubInfo);
+
+        Uri uri = TvContract.buildChannelsUriForInput(mStubInfo.getId());
+        String[] projection = { TvContract.Channels._ID };
+        try (Cursor cursor = mActivity.getContentResolver().query(
+                uri, projection, null, null, null)) {
+            assertNotNull(cursor);
+            assertTrue(cursor.moveToNext());
+            long channelId = cursor.getLong(0);
+            Uri channelUri = TvContract.buildChannelUri(channelId);
+            mTvView.tune(mStubInfo.getId(), channelUri);
+            mInstrumentation.waitForIdleSync();
+            new PollingCheck(TIME_OUT) {
+                @Override
+                protected boolean check() {
+                    return mListener.isVideoAvailable(mStubInfo.getId());
+                }
+            }.run();
+        }
+        runTestOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                mTvView.setFocusable(true);
+                mTvView.requestFocus();
+            }
+        });
+        mInstrumentation.waitForIdleSync();
+        assertTrue(mTvView.isFocused());
+
+        verifyKeyEvent(new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_GUIDE), unhandledEvent);
+        verifyKeyEvent(new KeyEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_GUIDE), unhandledEvent);
+    }
 }
diff --git a/tests/tests/util/src/android/util/cts/ArrayMapTest.java b/tests/tests/util/src/android/util/cts/ArrayMapTest.java
new file mode 100644
index 0000000..7fdd0da
--- /dev/null
+++ b/tests/tests/util/src/android/util/cts/ArrayMapTest.java
@@ -0,0 +1,500 @@
+/*
+ * Copyright (C) 2014 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 android.util.cts;
+
+import android.os.Bundle;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.test.AndroidTestCase;
+import android.util.ArrayMap;
+import android.util.Log;
+
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.Set;
+
+public class ArrayMapTest extends AndroidTestCase {
+    static final boolean DEBUG = false;
+
+    static final int OP_ADD = 1;
+    static final int OP_REM = 2;
+
+    static int[] OPS = new int[] {
+            OP_ADD, OP_ADD, OP_ADD, OP_ADD, OP_ADD, OP_ADD, OP_ADD, OP_ADD, OP_ADD,
+            OP_ADD, OP_ADD, OP_ADD, OP_ADD, OP_ADD, OP_ADD, OP_ADD, OP_ADD, OP_ADD,
+            OP_REM, OP_REM, OP_REM, OP_REM, OP_REM, OP_REM, OP_REM, OP_REM, OP_REM,
+            OP_REM, OP_REM, OP_REM, OP_REM, OP_REM, OP_REM, OP_REM, OP_REM, OP_REM,
+
+            OP_ADD, OP_ADD, OP_ADD, OP_ADD, OP_ADD, OP_ADD, OP_ADD, OP_ADD, OP_ADD,
+            OP_REM, OP_REM, OP_REM, OP_REM, OP_REM, OP_REM, OP_REM, OP_REM, OP_REM,
+
+            OP_ADD, OP_ADD, OP_ADD, OP_ADD, OP_ADD, OP_ADD, OP_ADD, OP_ADD, OP_ADD,
+            OP_REM, OP_REM, OP_REM, OP_REM, OP_REM, OP_REM, OP_REM, OP_REM, OP_REM,
+
+            OP_ADD, OP_ADD, OP_ADD, OP_ADD, OP_ADD, OP_ADD, OP_ADD, OP_ADD, OP_ADD,
+            OP_REM, OP_REM, OP_REM, OP_REM, OP_REM, OP_REM, OP_REM, OP_REM, OP_REM,
+
+            OP_ADD, OP_ADD, OP_ADD, OP_ADD, OP_ADD, OP_ADD, OP_ADD, OP_ADD, OP_ADD,
+            OP_ADD, OP_ADD, OP_ADD, OP_ADD, OP_ADD, OP_ADD, OP_ADD, OP_ADD, OP_ADD,
+            OP_ADD, OP_ADD, OP_ADD,
+            OP_REM, OP_REM, OP_REM, OP_REM, OP_REM, OP_REM, OP_REM, OP_REM, OP_REM,
+            OP_REM, OP_REM, OP_REM,
+            OP_REM, OP_REM, OP_REM, OP_REM, OP_REM, OP_REM, OP_REM, OP_REM, OP_REM,
+    };
+
+    static int[] KEYS = new int[] {
+            // General adding and removing.
+              -1,   1900,    600,    200,   1200,   1500,   1800,    100,   1900,
+            2100,    300,    800,    600,   1100,   1300,   2000,   1000,   1400,
+             600,     -1,   1900,    600,    300,   2100,    200,    800,    800,
+            1800,   1500,   1300,   1100,   2000,   1400,   1000,   1200,   1900,
+
+            // Shrink when removing item from end.
+             100,    200,    300,    400,    500,    600,    700,    800,    900,
+             900,    800,    700,    600,    500,    400,    300,    200,    100,
+
+            // Shrink when removing item from middle.
+             100,    200,    300,    400,    500,    600,    700,    800,    900,
+             900,    800,    700,    600,    500,    400,    200,    300,    100,
+
+            // Shrink when removing item from front.
+             100,    200,    300,    400,    500,    600,    700,    800,    900,
+             900,    800,    700,    600,    500,    400,    100,    200,    300,
+
+            // Test hash collisions.
+             105,    106,    108,    104,    102,    102,    107,      5,    205,
+               4,    202,    203,      3,      5,    101,    109,    200,    201,
+               0,     -1,    100,
+             106,    108,    104,    102,    103,    105,    107,    101,    109,
+              -1,    100,      0,
+               4,      5,      3,      5,    200,    203,    202,    201,    205,
+    };
+
+    public static class ControlledHash implements Parcelable {
+        final int mValue;
+
+        ControlledHash(int value) {
+            mValue = value;
+        }
+
+        @Override
+        public final boolean equals(Object o) {
+            if (o == null) {
+                return false;
+            }
+            return mValue == ((ControlledHash)o).mValue;
+        }
+
+        @Override
+        public final int hashCode() {
+            return mValue/100;
+        }
+
+        @Override
+        public final String toString() {
+            return Integer.toString(mValue);
+        }
+
+        @Override
+        public int describeContents() {
+            return 0;
+        }
+
+        @Override
+        public void writeToParcel(Parcel dest, int flags) {
+            dest.writeInt(mValue);
+        }
+
+        public static final Parcelable.Creator<ControlledHash> CREATOR
+                = new Parcelable.Creator<ControlledHash>() {
+            public ControlledHash createFromParcel(Parcel in) {
+                return new ControlledHash(in.readInt());
+            }
+
+            public ControlledHash[] newArray(int size) {
+                return new ControlledHash[size];
+            }
+        };
+    }
+
+    private static boolean compare(Object v1, Object v2) {
+        if (v1 == null) {
+            return v2 == null;
+        }
+        if (v2 == null) {
+            return false;
+        }
+        return v1.equals(v2);
+    }
+
+    private static void compareMaps(HashMap map, ArrayMap array) {
+        if (map.size() != array.size()) {
+            fail("Bad size: expected " + map.size() + ", got " + array.size());
+        }
+
+        Set<Map.Entry> mapSet = map.entrySet();
+        for (Map.Entry entry : mapSet) {
+            Object expValue = entry.getValue();
+            Object gotValue = array.get(entry.getKey());
+            if (!compare(expValue, gotValue)) {
+                fail("Bad value: expected " + expValue + ", got " + gotValue
+                        + " at key " + entry.getKey());
+            }
+        }
+
+        for (int i=0; i<array.size(); i++) {
+            Object gotValue = array.valueAt(i);
+            Object key = array.keyAt(i);
+            Object expValue = map.get(key);
+            if (!compare(expValue, gotValue)) {
+                fail("Bad value: expected " + expValue + ", got " + gotValue
+                        + " at key " + key);
+            }
+        }
+
+        if (map.entrySet().hashCode() != array.entrySet().hashCode()) {
+            fail("Entry set hash codes differ: map=0x"
+                    + Integer.toHexString(map.entrySet().hashCode()) + " array=0x"
+                    + Integer.toHexString(array.entrySet().hashCode()));
+        }
+
+        if (!map.entrySet().equals(array.entrySet())) {
+            fail("Failed calling equals on map entry set against array set");
+        }
+
+        if (!array.entrySet().equals(map.entrySet())) {
+            fail("Failed calling equals on array entry set against map set");
+        }
+
+        if (map.keySet().hashCode() != array.keySet().hashCode()) {
+            fail("Key set hash codes differ: map=0x"
+                    + Integer.toHexString(map.keySet().hashCode()) + " array=0x"
+                    + Integer.toHexString(array.keySet().hashCode()));
+        }
+
+        if (!map.keySet().equals(array.keySet())) {
+            fail("Failed calling equals on map key set against array set");
+        }
+
+        if (!array.keySet().equals(map.keySet())) {
+            fail("Failed calling equals on array key set against map set");
+        }
+
+        if (!map.keySet().containsAll(array.keySet())) {
+            fail("Failed map key set contains all of array key set");
+        }
+
+        if (!array.keySet().containsAll(map.keySet())) {
+            fail("Failed array key set contains all of map key set");
+        }
+
+        if (!array.containsAll(map.keySet())) {
+            fail("Failed array contains all of map key set");
+        }
+
+        if (!map.entrySet().containsAll(array.entrySet())) {
+            fail("Failed map entry set contains all of array entry set");
+        }
+
+        if (!array.entrySet().containsAll(map.entrySet())) {
+            fail("Failed array entry set contains all of map entry set");
+        }
+    }
+
+    private static void validateArrayMap(ArrayMap array) {
+        Set<Map.Entry> entrySet = array.entrySet();
+        int index=0;
+        Iterator<Map.Entry> entryIt = entrySet.iterator();
+        while (entryIt.hasNext()) {
+            Map.Entry entry = entryIt.next();
+            Object value = entry.getKey();
+            Object realValue = array.keyAt(index);
+            if (!compare(realValue, value)) {
+                fail("Bad array map entry set: expected key " + realValue
+                        + ", got " + value + " at index " + index);
+            }
+            value = entry.getValue();
+            realValue = array.valueAt(index);
+            if (!compare(realValue, value)) {
+                fail("Bad array map entry set: expected value " + realValue
+                        + ", got " + value + " at index " + index);
+            }
+            index++;
+        }
+
+        index = 0;
+        Set keySet = array.keySet();
+        Iterator keyIt = keySet.iterator();
+        while (keyIt.hasNext()) {
+            Object value = keyIt.next();
+            Object realValue = array.keyAt(index);
+            if (!compare(realValue, value)) {
+                fail("Bad array map key set: expected key " + realValue
+                        + ", got " + value + " at index " + index);
+            }
+            index++;
+        }
+
+        index = 0;
+        Collection valueCol = array.values();
+        Iterator valueIt = valueCol.iterator();
+        while (valueIt.hasNext()) {
+            Object value = valueIt.next();
+            Object realValue = array.valueAt(index);
+            if (!compare(realValue, value)) {
+                fail("Bad array map value col: expected value " + realValue
+                        + ", got " + value + " at index " + index);
+            }
+            index++;
+        }
+    }
+
+    private static void compareBundles(Bundle bundle1, Bundle bundle2) {
+        Set<String> keySet1 = bundle1.keySet();
+        Iterator<String> iterator1 = keySet1.iterator();
+        while (iterator1.hasNext()) {
+            String key = iterator1.next();
+            int value1 = bundle1.getInt(key);
+            if (bundle2.get(key) == null) {
+                fail("Bad Bundle: bundle2 didn't have expected key " + key);
+            }
+            int value2 = bundle2.getInt(key);
+            if (value1 != value2) {
+                fail("Bad Bundle: at key key " + key + " expected " + value1 + ", got " + value2);
+            }
+        }
+        Set<String> keySet2 = bundle2.keySet();
+        Iterator<String> iterator2 = keySet2.iterator();
+        while (iterator2.hasNext()) {
+            String key = iterator2.next();
+            if (bundle1.get(key) == null) {
+                fail("Bad Bundle: bundle1 didn't have expected key " + key);
+            }
+            int value1 = bundle1.getInt(key);
+            int value2 = bundle2.getInt(key);
+            if (value1 != value2) {
+                fail("Bad Bundle: at key key " + key + " expected " + value1 + ", got " + value2);
+            }
+        }
+    }
+
+    private static void dump(Map map, ArrayMap array) {
+        Log.e("test", "HashMap of " + map.size() + " entries:");
+        Set<Map.Entry> mapSet = map.entrySet();
+        for (Map.Entry entry : mapSet) {
+            Log.e("test", "    " + entry.getKey() + " -> " + entry.getValue());
+        }
+        Log.e("test", "ArrayMap of " + array.size() + " entries:");
+        for (int i=0; i<array.size(); i++) {
+            Log.e("test", "    " + array.keyAt(i) + " -> " + array.valueAt(i));
+        }
+    }
+
+    private static void dump(ArrayMap map1, ArrayMap map2) {
+        Log.e("test", "ArrayMap of " + map1.size() + " entries:");
+        for (int i=0; i<map2.size(); i++) {
+            Log.e("test", "    " + map1.keyAt(i) + " -> " + map1.valueAt(i));
+        }
+        Log.e("test", "ArrayMap of " + map2.size() + " entries:");
+        for (int i=0; i<map2.size(); i++) {
+            Log.e("test", "    " + map2.keyAt(i) + " -> " + map2.valueAt(i));
+        }
+    }
+
+    private static void dump(Bundle bundle1, Bundle bundle2) {
+        Log.e("test", "First Bundle of " + bundle1.size() + " entries:");
+        Set<String> keys1 = bundle1.keySet();
+        for (String key : keys1) {
+            Log.e("test", "    " + key + " -> " + bundle1.get(key));
+        }
+        Log.e("test", "Second Bundle of " + bundle2.size() + " entries:");
+        Set<String> keys2 = bundle2.keySet();
+        for (String key : keys2) {
+            Log.e("test", "    " + key + " -> " + bundle2.get(key));
+        }
+    }
+
+    public void testBasicArrayMap() {
+        HashMap<ControlledHash, Integer> hashMap = new HashMap<ControlledHash, Integer>();
+        ArrayMap<ControlledHash, Integer> arrayMap = new ArrayMap<ControlledHash, Integer>();
+        Bundle bundle = new Bundle();
+
+        for (int i=0; i<OPS.length; i++) {
+            Integer oldHash;
+            Integer oldArray;
+            ControlledHash key = KEYS[i] < 0 ? null : new ControlledHash(KEYS[i]);
+            String strKey = KEYS[i] < 0 ? null : Integer.toString(KEYS[i]);
+            switch (OPS[i]) {
+                case OP_ADD:
+                    if (DEBUG) Log.i("test", "Adding key: " + key);
+                    oldHash = hashMap.put(key, i);
+                    oldArray = arrayMap.put(key, i);
+                    bundle.putInt(strKey, i);
+                    break;
+                case OP_REM:
+                    if (DEBUG) Log.i("test", "Removing key: " + key);
+                    oldHash = hashMap.remove(key);
+                    oldArray = arrayMap.remove(key);
+                    bundle.remove(strKey);
+                    break;
+                default:
+                    fail("Bad operation " + OPS[i] + " @ " + i);
+                    return;
+            }
+            if (!compare(oldHash, oldArray)) {
+                String msg = "Bad result: expected " + oldHash + ", got " + oldArray;
+                Log.e("test", msg);
+                dump(hashMap, arrayMap);
+                fail(msg);
+            }
+            try {
+                validateArrayMap(arrayMap);
+            } catch (Throwable e) {
+                Log.e("test", e.getMessage());
+                dump(hashMap, arrayMap);
+                throw e;
+            }
+            try {
+                compareMaps(hashMap, arrayMap);
+            } catch (Throwable e) {
+                Log.e("test", e.getMessage());
+                dump(hashMap, arrayMap);
+                throw e;
+            }
+            Parcel parcel = Parcel.obtain();
+            bundle.writeToParcel(parcel, 0);
+            parcel.setDataPosition(0);
+            Bundle bundle2 = parcel.readBundle();
+            try {
+                compareBundles(bundle, bundle2);
+            } catch (Throwable e) {
+                Log.e("test", e.getMessage());
+                dump(bundle, bundle2);
+                throw e;
+            }
+        }
+
+        arrayMap.put(new ControlledHash(50000), 100);
+        ControlledHash lookup = new ControlledHash(50000);
+        Iterator<ControlledHash> it = arrayMap.keySet().iterator();
+        while (it.hasNext()) {
+            if (it.next().equals(lookup)) {
+                it.remove();
+            }
+        }
+        if (arrayMap.containsKey(lookup)) {
+            String msg = "Bad map iterator: didn't remove test key";
+            Log.e("test", msg);
+            dump(hashMap, arrayMap);
+            fail(msg);
+        }
+
+        //Log.e("test", "Test successful; printing final map.");
+        //dump(hashMap, arrayMap);
+    }
+
+    public void testCopyArrayMap() {
+        // map copy constructor test
+        ArrayMap newMap = new ArrayMap<Integer, String>();
+        for (int i = 0; i < 10; ++i) {
+            newMap.put(i, String.valueOf(i));
+        }
+        ArrayMap mapCopy = new ArrayMap(newMap);
+        if (!compare(mapCopy, newMap)) {
+            String msg = "ArrayMap copy constructor failure: expected " +
+                    newMap + ", got " + mapCopy;
+            Log.e("test", msg);
+            dump(newMap, mapCopy);
+            fail(msg);
+            return;
+        }
+    }
+
+    public void testEqualsArrayMap() {
+        ArrayMap<Integer, String> map1 = new ArrayMap<Integer, String>();
+        ArrayMap<Integer, String> map2 = new ArrayMap<Integer, String>();
+        HashMap<Integer, String> map3 = new HashMap<Integer, String>();
+        if (!compare(map1, map2) || !compare(map1, map3) || !compare(map3, map2)) {
+            fail("ArrayMap equals failure for empty maps " + map1 + ", " +
+                    map2 + ", " + map3);
+        }
+
+        for (int i = 0; i < 10; ++i) {
+            String value = String.valueOf(i);
+            map1.put(i, value);
+            map2.put(i, value);
+            map3.put(i, value);
+        }
+        if (!compare(map1, map2) || !compare(map1, map3) || !compare(map3, map2)) {
+            fail("ArrayMap equals failure for populated maps " + map1 + ", " +
+                    map2 + ", " + map3);
+        }
+
+        map1.remove(0);
+        if (compare(map1, map2) || compare(map1, map3) || compare(map3, map1)) {
+            fail("ArrayMap equals failure for map size " + map1 + ", " +
+                    map2 + ", " + map3);
+        }
+
+        map1.put(0, "-1");
+        if (compare(map1, map2) || compare(map1, map3) || compare(map3, map1)) {
+            fail("ArrayMap equals failure for map contents " + map1 + ", " +
+                    map2 + ", " + map3);
+        }
+    }
+
+    /**
+     * Test creating a malformed array map with duplicated keys and that we will catch this
+     * when unparcelling.
+     */
+    public void testDuplicateKeys() throws NoSuchMethodException,
+            InvocationTargetException, IllegalAccessException, NoSuchFieldException {
+        ArrayMap<String, Object> map1 = new ArrayMap(2);
+
+        Method appendMethod = ArrayMap.class.getMethod("append", Object.class, Object.class);
+        appendMethod.invoke(map1, Integer.toString(100000), "foo");
+        appendMethod.invoke(map1, Integer.toString(100000), "bar");
+
+        // Now parcel/unparcel, and verify we get the expected error.
+        Parcel parcel = Parcel.obtain();
+        Method writeArrayMapMethod = Parcel.class.getMethod("writeArrayMap", ArrayMap.class);
+        writeArrayMapMethod.invoke(parcel, map1);
+        parcel.setDataPosition(0);
+        ArrayMap<String, Object> map2 = new ArrayMap(2);
+
+        try {
+            Parcel.class.getMethod("readArrayMap", ArrayMap.class, ClassLoader.class).invoke(
+                    parcel, map2, null);
+        } catch (InvocationTargetException e) {
+            Throwable cause = e.getCause();
+            if (cause instanceof IllegalArgumentException) {
+                // Good!
+                return;
+            }
+            throw e;
+        }
+
+        String msg = "Didn't throw expected IllegalArgumentException";
+        Log.e("test", msg);
+        dump(map1, map2);
+        fail(msg);
+    }
+}
diff --git a/tests/tests/widget/src/android/widget/cts/GridViewTest.java b/tests/tests/widget/src/android/widget/cts/GridViewTest.java
index 1dd4764..bd42e35 100644
--- a/tests/tests/widget/src/android/widget/cts/GridViewTest.java
+++ b/tests/tests/widget/src/android/widget/cts/GridViewTest.java
@@ -234,7 +234,21 @@
     }
 
     public void testSetHorizontalSpacing() {
+        testSetHorizontalSpacing(View.LAYOUT_DIRECTION_LTR);
+    }
+
+    public void testSetHorizontalSpacingRTL() {
+        testSetHorizontalSpacing(View.LAYOUT_DIRECTION_RTL);
+    }
+
+    public void testSetHorizontalSpacing(final int layoutDir) {
         mGridView = findGridViewById(R.id.gridview);
+        mActivity.runOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                mGridView.setLayoutDirection(layoutDir);
+            }
+        });
         mGridView.setStretchMode(GridView.NO_STRETCH);
         // Number of columns should be big enough, otherwise the
         // horizontal spacing cannot be correctly verified.
@@ -252,7 +266,11 @@
 
         View child0 = mGridView.getChildAt(0);
         View child1 = mGridView.getChildAt(1);
-        assertEquals(0, child1.getLeft() - child0.getRight());
+        if (layoutDir == View.LAYOUT_DIRECTION_LTR) {
+            assertEquals(0, child1.getLeft() - child0.getRight());
+        } else {
+            assertEquals(0, child0.getLeft() - child1.getRight());
+        }
 
         mActivity.runOnUiThread(new Runnable() {
             public void run() {
@@ -263,7 +281,11 @@
 
         child0 = mGridView.getChildAt(0);
         child1 = mGridView.getChildAt(1);
-        assertEquals(5, child1.getLeft() - child0.getRight());
+        if (layoutDir == View.LAYOUT_DIRECTION_LTR) {
+            assertEquals(5, child1.getLeft() - child0.getRight());
+        } else {
+            assertEquals(5, child0.getLeft() - child1.getRight());
+        }
     }
 
     public void testSetVerticalSpacing() {
diff --git a/tools/vm-tests-tf/src/dot/junit/opcodes/invoke_direct/Test_invoke_direct.java b/tools/vm-tests-tf/src/dot/junit/opcodes/invoke_direct/Test_invoke_direct.java
index 1772f22..c5402b3 100644
--- a/tools/vm-tests-tf/src/dot/junit/opcodes/invoke_direct/Test_invoke_direct.java
+++ b/tools/vm-tests-tf/src/dot/junit/opcodes/invoke_direct/Test_invoke_direct.java
@@ -111,8 +111,9 @@
         //@uses dot.junit.opcodes.invoke_direct.TSuper
         try {
             new T_invoke_direct_6();
-            fail("expected NoSuchMethodError");
-        } catch (NoSuchMethodError t) {
+            fail("expected a verification exception");
+        } catch (Throwable t) {
+            DxUtil.checkVerifyException(t);
         }
     }
 
@@ -241,14 +242,22 @@
     /**
      * @constraint n/a
      * @title Attempt to invoke static method. Java throws IncompatibleClassChangeError
-     * on first access but Dalvik throws VerifyError on class loading.
+     * on first access. Dalvik threw VerifyError on class loading.
      */
     public void testVFE15() {
         try {
-            Class.forName("dot.junit.opcodes.invoke_direct.d.T_invoke_direct_11");
-            fail("expected a verification exception");
+            Class<?> c = Class.forName("dot.junit.opcodes.invoke_direct.d.T_invoke_direct_11");
+            // Attempt to instantiate and run.
+            Object o = c.newInstance();
+            java.lang.reflect.Method m = c.getDeclaredMethod("run");
+            m.invoke(o);
+            fail("expected an invocation target exception with an incompatible class change error");
+        } catch (java.lang.reflect.InvocationTargetException ite) {
+            if (!(ite.getCause() instanceof IncompatibleClassChangeError)) {
+                fail("expected an incompatible class change error");
+            }
         } catch (Throwable t) {
-            DxUtil.checkVerifyException(t);
+            throw new RuntimeException(t);
         }
     }
 
@@ -277,8 +286,7 @@
         //@uses dot.junit.opcodes.invoke_direct.TAbstract
         try {
             new T_invoke_direct_13().run();
-            fail("expected NoSuchMethodError or verification exception");
-        } catch (NoSuchMethodError t) {
+            fail("expected a verification exception");
         } catch (Throwable t) {
             DxUtil.checkVerifyException(t);
         }
diff --git a/tools/vm-tests-tf/src/dot/junit/opcodes/invoke_direct/d/T_invoke_direct_7.d b/tools/vm-tests-tf/src/dot/junit/opcodes/invoke_direct/d/T_invoke_direct_7.d
index 2c1b99b..13432bd 100644
--- a/tools/vm-tests-tf/src/dot/junit/opcodes/invoke_direct/d/T_invoke_direct_7.d
+++ b/tools/vm-tests-tf/src/dot/junit/opcodes/invoke_direct/d/T_invoke_direct_7.d
@@ -27,7 +27,7 @@
 .method public run()I
 .limit regs 3
 
-       invoke-direct {v1}, dot/junit/opcodes/invoke_direct/d/T_invoke_direct_7/toInt()I
+       invoke-direct {v2}, dot/junit/opcodes/invoke_direct/d/T_invoke_direct_7/toInt()I
        const v0, 0
        return v0
 .end method
diff --git a/tools/vm-tests-tf/src/dot/junit/opcodes/invoke_direct_range/Test_invoke_direct_range.java b/tools/vm-tests-tf/src/dot/junit/opcodes/invoke_direct_range/Test_invoke_direct_range.java
index d4f194b..1fe95c2 100644
--- a/tools/vm-tests-tf/src/dot/junit/opcodes/invoke_direct_range/Test_invoke_direct_range.java
+++ b/tools/vm-tests-tf/src/dot/junit/opcodes/invoke_direct_range/Test_invoke_direct_range.java
@@ -111,8 +111,9 @@
         //@uses dot.junit.opcodes.invoke_direct_range.TSuper
         try {
             new T_invoke_direct_range_6();
-            fail("expected NoSuchMethodError");
-        } catch (NoSuchMethodError t) {
+            fail("expected a verification exception");
+        } catch (Throwable t) {
+            DxUtil.checkVerifyException(t);
         }
     }
 
@@ -241,14 +242,22 @@
     /**
      * @constraint n/a
      * @title Attempt to invoke static method. Java throws IncompatibleClassChangeError
-     * on first access but Dalvik throws VerifyError on class loading.
+     * on first access. Dalvik threw VerifyError on class loading.
      */
     public void testVFE15() {
         try {
-            Class.forName("dot.junit.opcodes.invoke_direct_range.d.T_invoke_direct_range_11");
-            fail("expected a verification exception");
+            Class<?> c = Class.forName("dot.junit.opcodes.invoke_direct_range.d.T_invoke_direct_range_11");
+            // Attempt to instantiate and run.
+            Object o = c.newInstance();
+            java.lang.reflect.Method m = c.getDeclaredMethod("run");
+            m.invoke(o);
+            fail("expected an invocation target exception with an incompatible class change error");
+        } catch (java.lang.reflect.InvocationTargetException ite) {
+            if (!(ite.getCause() instanceof IncompatibleClassChangeError)) {
+                fail("expected an incompatible class change error");
+            }
         } catch (Throwable t) {
-            DxUtil.checkVerifyException(t);
+            throw new RuntimeException(t);
         }
     }
 
@@ -278,8 +287,7 @@
         //@uses dot.junit.opcodes.invoke_direct_range.TAbstract
         try {
             new T_invoke_direct_range_13().run();
-            fail("expected NoSuchMethodError or verification exception");
-        } catch (NoSuchMethodError t) {
+            fail("expected a verification exception");
         } catch (Throwable t) {
             DxUtil.checkVerifyException(t);
         }
diff --git a/tools/vm-tests-tf/src/dot/junit/opcodes/invoke_direct_range/d/T_invoke_direct_range_7.d b/tools/vm-tests-tf/src/dot/junit/opcodes/invoke_direct_range/d/T_invoke_direct_range_7.d
index f0254f4..0e7aab0 100644
--- a/tools/vm-tests-tf/src/dot/junit/opcodes/invoke_direct_range/d/T_invoke_direct_range_7.d
+++ b/tools/vm-tests-tf/src/dot/junit/opcodes/invoke_direct_range/d/T_invoke_direct_range_7.d
@@ -27,7 +27,7 @@
 .method public run()I
 .limit regs 3
 
-       invoke-direct/range {v1}, dot/junit/opcodes/invoke_direct_range/d/T_invoke_direct_range_7/toInt()I
+       invoke-direct/range {v2}, dot/junit/opcodes/invoke_direct_range/d/T_invoke_direct_range_7/toInt()I
        const v0, 0
        return v0
 .end method
diff --git a/tools/vm-tests-tf/src/dot/junit/opcodes/invoke_interface/Test_invoke_interface.java b/tools/vm-tests-tf/src/dot/junit/opcodes/invoke_interface/Test_invoke_interface.java
index f846374..69c54e1 100644
--- a/tools/vm-tests-tf/src/dot/junit/opcodes/invoke_interface/Test_invoke_interface.java
+++ b/tools/vm-tests-tf/src/dot/junit/opcodes/invoke_interface/Test_invoke_interface.java
@@ -166,8 +166,7 @@
     public void testVFE2() {
         try {
             new T_invoke_interface_4().run();
-            fail("expected NoSuchMethodError or IncompatibleClassChangeError");
-        } catch (NoSuchMethodError t) {
+            fail("expected an IncompatibleClassChangeError");
         } catch (IncompatibleClassChangeError e) {
         }
     }
@@ -220,8 +219,7 @@
         //@uses dot.junit.opcodes.invoke_interface.d.T_invoke_interface_18
         try {
             new T_invoke_interface_18().run(new ITestImpl());
-            fail("expected NoSuchMethodError or verification exception");
-        } catch (NoSuchMethodError t) {
+            fail("expected a verification exception");
         } catch (Throwable t) {
             DxUtil.checkVerifyException(t);
         }
@@ -236,8 +234,7 @@
         //@uses dot.junit.opcodes.invoke_interface.ITestImpl
         try {
             new T_invoke_interface_20().run(new ITestImpl());
-            fail("expected NoSuchMethodError or verification exception");
-        } catch (NoSuchMethodError t) {
+            fail("expected a verification exception");
         } catch (Throwable t) {
             DxUtil.checkVerifyException(t);
         }
diff --git a/tools/vm-tests-tf/src/dot/junit/opcodes/invoke_interface_range/Test_invoke_interface_range.java b/tools/vm-tests-tf/src/dot/junit/opcodes/invoke_interface_range/Test_invoke_interface_range.java
index 9f98d4c..5af318a 100644
--- a/tools/vm-tests-tf/src/dot/junit/opcodes/invoke_interface_range/Test_invoke_interface_range.java
+++ b/tools/vm-tests-tf/src/dot/junit/opcodes/invoke_interface_range/Test_invoke_interface_range.java
@@ -143,8 +143,7 @@
     public void testVFE2() {
         try {
             new T_invoke_interface_range_4().run();
-            fail("expected NoSuchMethodError or IncompatibleClassChangeError");
-        } catch (NoSuchMethodError t) {
+            fail("expected an IncompatibleClassChangeError");
         } catch (IncompatibleClassChangeError e) {
         }
     }
@@ -197,8 +196,7 @@
         //@uses dot.junit.opcodes.invoke_interface_range.d.T_invoke_interface_range_18
         try {
             new T_invoke_interface_range_18().run(new ITestImpl());
-            fail("expected NoSuchMethodError or verification exception");
-        } catch (NoSuchMethodError t) {
+            fail("expected a verification exception");
         } catch (Throwable t) {
             DxUtil.checkVerifyException(t);
         }
@@ -213,8 +211,7 @@
         //@uses dot.junit.opcodes.invoke_interface_range.ITestImpl
         try {
             new T_invoke_interface_range_20().run(new ITestImpl());
-            fail("expected NoSuchMethodError or verification exception");
-        } catch (NoSuchMethodError t) {
+            fail("expected a verification exception");
         } catch (Throwable t) {
             DxUtil.checkVerifyException(t);
         }