Merge "Fixing null pointer error in CTS AudioManagerTest" into lmp-dev
diff --git a/apps/CtsVerifier/Android.mk b/apps/CtsVerifier/Android.mk
index c227665a..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..5708a50 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. -->
@@ -927,6 +927,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 +967,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/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..86729ad 100644
--- a/apps/CtsVerifier/res/values/strings.xml
+++ b/apps/CtsVerifier/res/values/strings.xml
@@ -442,6 +442,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 +961,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 +971,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/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/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/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 7485abe1..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/CaptureRequestTest.java b/tests/tests/hardware/src/android/hardware/camera2/cts/CaptureRequestTest.java
index d275197..ec79151 100644
--- a/tests/tests/hardware/src/android/hardware/camera2/cts/CaptureRequestTest.java
+++ b/tests/tests/hardware/src/android/hardware/camera2/cts/CaptureRequestTest.java
@@ -1227,8 +1227,6 @@
      * Test face detection for a camera.
      */
     private void faceDetectionTestByCamera() throws Exception {
-        // Can only test full capability because test relies on per frame control
-        // and synchronization.
         int[] faceDetectModes = mStaticInfo.getAvailableFaceDetectModesChecked();
 
         SimpleCaptureListener listener;
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 c36c937..652462c 100644
--- a/tests/tests/hardware/src/android/hardware/camera2/cts/RecordingTest.java
+++ b/tests/tests/hardware/src/android/hardware/camera2/cts/RecordingTest.java
@@ -481,19 +481,11 @@
                     openDevice(id);
                     mSupportedVideoSizes =
                             getSupportedVideoSizes(id, mCameraManager, VIDEO_SIZE_BOUND);
-                    // Use largest still size for video snapshot
-                    Size videoSnapshotSz = mOrderedStillSizes.get(0);
-                    // Image reader is shared for all tested profile, but listener is different
-                    // per profile and will be set later
-                    createImageReader(
-                            videoSnapshotSz, ImageFormat.JPEG,
-                            MAX_VIDEO_SNAPSHOT_IMAGES, /*listener*/null);
 
-                    videoSnapshotTestByCamera(videoSnapshotSz, burstTest);
+                    videoSnapshotTestByCamera(burstTest);
                 } finally {
                     closeDevice();
                     releaseRecorder();
-                    closeImageReader();
                 }
             }
     }
@@ -529,11 +521,10 @@
      * is not checked.
      * </p>
      *
-     * @param videoSnapshotSz The size of video snapshot image
      * @param burstTest Perform burst capture or single capture. For burst capture
      *                  {@value #BURST_VIDEO_SNAPSHOT_NUM} capture requests will be sent.
      */
-    private void videoSnapshotTestByCamera(Size videoSnapshotSz, boolean burstTest)
+    private void videoSnapshotTestByCamera(boolean burstTest)
             throws Exception {
         for (int profileId : mCamcorderProfileList) {
             int cameraId = Integer.valueOf(mCamera.getId());
@@ -548,6 +539,13 @@
                             " must be one of the camera device supported video size!",
                             mSupportedVideoSizes.contains(videoSz));
 
+            Size videoSnapshotSz = mStaticInfo.isHardwareLevelFull() ?
+                    mOrderedStillSizes.get(0) : // Full device tests largest jpeg size
+                    videoSz;                    // Non-full device tests video size
+            createImageReader(
+                    videoSnapshotSz, ImageFormat.JPEG,
+                    MAX_VIDEO_SNAPSHOT_IMAGES, /*listener*/null);
+
             if (VERBOSE) {
                 Log.v(TAG, "Testing camera recording with video size " + videoSz.toString());
             }
@@ -619,6 +617,8 @@
 
                 image.close();
             }
+
+            closeImageReader();
         }
     }
 
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/AudioTrackTest.java b/tests/tests/media/src/android/media/cts/AudioTrackTest.java
index 026647a..5d92966 100644
--- a/tests/tests/media/src/android/media/cts/AudioTrackTest.java
+++ b/tests/tests/media/src/android/media/cts/AudioTrackTest.java
@@ -1338,7 +1338,7 @@
     public void testPlayStreamData() throws Exception {
         // constants for test
         final String TEST_NAME = "testPlayStreamData";
-        final int TEST_FORMAT_ARRAY[] = {  // should hear 8 increasing frequency tones, 3 times
+        final int TEST_FORMAT_ARRAY[] = {  // should hear 28 increasing frequency tones, 3 times
                 AudioFormat.ENCODING_PCM_8BIT,
                 AudioFormat.ENCODING_PCM_16BIT,
                 AudioFormat.ENCODING_PCM_FLOAT,
@@ -1350,8 +1350,16 @@
                 48000,
         };
         final int TEST_CONF_ARRAY[] = {
-                AudioFormat.CHANNEL_OUT_MONO,
-                AudioFormat.CHANNEL_OUT_STEREO,
+                AudioFormat.CHANNEL_OUT_MONO,    // 1.0
+                AudioFormat.CHANNEL_OUT_STEREO,  // 2.0
+                AudioFormat.CHANNEL_OUT_STEREO | AudioFormat.CHANNEL_OUT_FRONT_CENTER, // 3.0
+                AudioFormat.CHANNEL_OUT_QUAD,    // 4.0
+                AudioFormat.CHANNEL_OUT_QUAD | AudioFormat.CHANNEL_OUT_FRONT_CENTER,   // 5.0
+                AudioFormat.CHANNEL_OUT_5POINT1, // 5.1
+                AudioFormat.CHANNEL_OUT_5POINT1 | AudioFormat.CHANNEL_OUT_BACK_CENTER, // 6.1
+                // unsupported masks
+                // AudioFormat.CHANNEL_OUT_7POINT1_SURROUND, // 7.1
+                // AudioFormat.CHANNEL_OUT_7POINT1_SURROUND | AudioFormat.CHANNEL_OUT_BACK_CENTER,
         };
         final int TEST_MODE = AudioTrack.MODE_STREAM;
         final int TEST_STREAM_TYPE = AudioManager.STREAM_MUSIC;
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/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..960aef73 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);
+    }
 }