add kitchensink test app

bug: 27443255
Change-Id: I763de96c7463d37b3e68413994b470083b5f5ba6
diff --git a/tests/EmbeddedKitchenSinkApp/Android.mk b/tests/EmbeddedKitchenSinkApp/Android.mk
new file mode 100644
index 0000000..e160e03
--- /dev/null
+++ b/tests/EmbeddedKitchenSinkApp/Android.mk
@@ -0,0 +1,39 @@
+# Copyright (C) 2015 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.
+#
+#
+
+LOCAL_PATH:= $(call my-dir)
+
+include $(CLEAR_VARS)
+
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
+
+LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/res
+
+LOCAL_PACKAGE_NAME := EmbeddedKitchenSinkApp
+
+LOCAL_AAPT_FLAGS := --auto-add-overlay
+
+LOCAL_MODULE_TAGS := optional
+
+LOCAL_PRIVILEGED_MODULE := true
+
+LOCAL_PROGUARD_ENABLED := disabled
+
+LOCAL_STATIC_JAVA_LIBRARIES += car-systemtest android.support.car
+
+LOCAL_JAVA_LIBRARIES += android.car
+
+include $(BUILD_PACKAGE)
diff --git a/tests/EmbeddedKitchenSinkApp/AndroidManifest.xml b/tests/EmbeddedKitchenSinkApp/AndroidManifest.xml
new file mode 100644
index 0000000..aed497f
--- /dev/null
+++ b/tests/EmbeddedKitchenSinkApp/AndroidManifest.xml
@@ -0,0 +1,51 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2015 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.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+        xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
+        package="com.google.android.car.kitchensink" >
+    <uses-sdk
+        android:minSdkVersion="22"
+        android:targetSdkVersion='23'/>
+    <uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" />
+    <uses-permission android:name="android.car.permission.CAR_SPEED" />
+    <uses-permission android:name="android.car.permission.CAR_HVAC" />
+    <uses-permission android:name="android.car.permission.CAR_MOCK_VEHICLE_HAL" />
+    <uses-permission android:name="android.car.permission.CAR_CAMERA" />
+    <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
+    <application android:label="@string/app_title"
+        android:icon="@drawable/ic_launcher">
+
+        <!-- This is for embedded mode. -->
+        <activity android:name=".KitchenSinkProxyActivity"
+            android:theme="@android:style/Theme.NoTitleBar"
+            android:label="@string/app_title">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.intent.category.LAUNCHER" />
+            </intent-filter>
+        </activity>
+
+        <meta-data
+            android:name="android.car.application"
+            android:resource="@xml/automotive_app_desc" />
+
+        <service android:name=".job.DishService"
+            android:permission="android.permission.BIND_JOB_SERVICE">
+        </service>
+
+    </application>
+</manifest>
diff --git a/tests/EmbeddedKitchenSinkApp/res/drawable-hdpi/ic_launcher.png b/tests/EmbeddedKitchenSinkApp/res/drawable-hdpi/ic_launcher.png
new file mode 100644
index 0000000..af3d210
--- /dev/null
+++ b/tests/EmbeddedKitchenSinkApp/res/drawable-hdpi/ic_launcher.png
Binary files differ
diff --git a/tests/EmbeddedKitchenSinkApp/res/drawable-nodpi/app_bg.png b/tests/EmbeddedKitchenSinkApp/res/drawable-nodpi/app_bg.png
new file mode 100644
index 0000000..6981e62
--- /dev/null
+++ b/tests/EmbeddedKitchenSinkApp/res/drawable-nodpi/app_bg.png
Binary files differ
diff --git a/tests/EmbeddedKitchenSinkApp/res/layout/audio.xml b/tests/EmbeddedKitchenSinkApp/res/layout/audio.xml
new file mode 100644
index 0000000..8ca23b8
--- /dev/null
+++ b/tests/EmbeddedKitchenSinkApp/res/layout/audio.xml
@@ -0,0 +1,207 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2015 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="match_parent"
+    android:layout_height="match_parent"
+    android:orientation="vertical" >
+    <!--  dummy one for top area -->
+    <LinearLayout
+        android:layout_width="match_parent"
+        android:layout_height="50dp"
+        android:orientation="vertical"
+        android:layout_weight="1" />
+    <LinearLayout
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:orientation="horizontal"
+        android:layout_weight="1" >
+        <ToggleButton
+            android:id="@+id/button_mock_audio"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:textOn="@string/mock_audio_on"
+            android:textOff="@string/mock_audio_off" />
+        <ToggleButton
+            android:id="@+id/button_reject_audio_focus"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:textOn="@string/reject_audio_focus_on"
+            android:textOff="@string/reject_audio_focus_off" />
+    </LinearLayout>
+    <LinearLayout
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:orientation="vertical"
+        android:layout_weight="1" >
+        <LinearLayout
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:orientation="horizontal" >
+            <TextView
+                android:id="@+id/audio_focus_title"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:text="@string/audio_focus" />
+            <Button
+                android:id="@+id/button_audio_focus_request"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:text="@string/request" />
+            <TextView
+                android:id="@+id/text_audio_focus_state"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:text="@string/empty" />
+        </LinearLayout>
+        <RadioGroup
+            android:id="@+id/button_focus_request_selection"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:orientation="horizontal" >
+            <RadioButton
+                android:id="@+id/focus_gain"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:text="@string/focus_gain" />
+            <RadioButton
+                android:id="@+id/focus_gain_transient_duck"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:text="@string/focus_gain_transient_duck" />
+            <RadioButton
+                android:id="@+id/focus_release"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:text="@string/focus_release" />
+        </RadioGroup>
+    </LinearLayout>
+    <LinearLayout
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:orientation="horizontal"
+        android:layout_weight="1" >
+        <TextView
+            android:id="@+id/nav_play_title"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:text="@string/nav_play" />
+        <Button
+            android:id="@+id/button_nav_play_once"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:text="@string/play_pcm_once" />
+        <TextView
+            android:id="@+id/vr_play_title"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:text="@string/vr_play" />
+        <Button
+            android:id="@+id/button_vr_play_once"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:text="@string/play_pcm_once" />
+        <TextView
+            android:id="@+id/system_play_title"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:text="@string/system_play" />
+        <Button
+            android:id="@+id/button_system_play_once"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:text="@string/play_pcm_once" />
+    </LinearLayout>
+
+    <LinearLayout
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:orientation="horizontal"
+        android:layout_weight="1" >
+        <TextView
+            android:id="@+id/media_play_title"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:text="@string/media_play" />
+        <Button
+            android:id="@+id/button_media_play_start"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:text="@string/play" />
+        <Button
+            android:id="@+id/button_media_play_once"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:text="@string/play_pcm_once" />
+        <Button
+            android:id="@+id/button_media_play_stop"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:text="@string/stop" />
+        <Button
+            android:id="@+id/button_speaker_phone_on"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:text="@string/speaker_phone_on" />
+        <Button
+            android:id="@+id/button_speaker_phone_off"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:text="@string/speaker_phone_off" />
+        <Button
+            android:id="@+id/button_microphone_on"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:text="@string/microphone_on" />
+        <Button
+            android:id="@+id/button_microphone_off"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:text="@string/microphone_off" />
+    </LinearLayout>
+
+    <TextView
+        android:id="@+id/system_play_message"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:text="@string/empty"
+        android:layout_weight="1" />
+    <LinearLayout
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:orientation="horizontal"
+        android:layout_weight="1" >
+        <Button
+            android:id="@+id/button_nav_start"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:text="@string/nav_focus_start" />
+        <Button
+            android:id="@+id/button_nav_end"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:text="@string/nav_focus_end" />
+        <Button
+            android:id="@+id/button_vr_start"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:text="@string/vr_start" />
+        <Button
+            android:id="@+id/button_vr_end"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:text="@string/vr_end" />
+    </LinearLayout>
+</LinearLayout>
diff --git a/tests/EmbeddedKitchenSinkApp/res/layout/camera_test.xml b/tests/EmbeddedKitchenSinkApp/res/layout/camera_test.xml
new file mode 100644
index 0000000..ed167a5
--- /dev/null
+++ b/tests/EmbeddedKitchenSinkApp/res/layout/camera_test.xml
@@ -0,0 +1,148 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2016 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:gravity="center"
+    android:layout_height="match_parent"
+    android:layout_width="match_parent"
+    android:layout_marginTop="100dp"
+    android:orientation="vertical">
+
+    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+        android:layout_height="wrap_content"
+        android:layout_width="match_parent"
+        android:orientation="horizontal">
+        <Button
+            android:id="@+id/btnGetCap"
+            android:layout_height="@dimen/rvcBtnHeight"
+            android:layout_width ="@dimen/rvcBtnWidth"
+            android:text="@string/rvc_getCapabilities"
+            android:textSize="@dimen/rvcTextSize"/>
+        <TextView
+            android:gravity="center"
+            android:id="@+id/tvCap"
+            android:layout_height="@dimen/rvcBtnHeight"
+            android:layout_width ="@dimen/rvcBtnWidth"
+            android:textSize="@dimen/rvcTextSize"/>
+    </LinearLayout>
+
+    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+        android:layout_height="wrap_content"
+        android:layout_width="match_parent"
+        android:orientation="horizontal">
+        <Button
+            android:id="@+id/btnGetRvcCrop"
+            android:layout_height="@dimen/rvcBtnHeight"
+            android:layout_width ="@dimen/rvcBtnWidth"
+            android:text="@string/rvc_getRvcCrop"
+            android:textSize="@dimen/rvcTextSize"/>
+        <TextView
+            android:gravity="center"
+            android:id="@+id/tvRvcCrop"
+            android:layout_height="@dimen/rvcBtnHeight"
+            android:layout_width ="@dimen/rvcBtnWidth"
+            android:textSize="@dimen/rvcTextSize"/>
+    </LinearLayout>
+
+    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+        android:layout_height="wrap_content"
+        android:layout_width="match_parent"
+        android:orientation="horizontal">
+        <Button
+            android:id="@+id/btnGetRvcPos"
+            android:layout_height="@dimen/rvcBtnHeight"
+            android:layout_width ="@dimen/rvcBtnWidth"
+            android:text="@string/rvc_getRvcPos"
+            android:textSize="@dimen/rvcTextSize"/>
+        <TextView
+            android:gravity="center"
+            android:id="@+id/tvRvcPos"
+            android:layout_height="@dimen/rvcBtnHeight"
+            android:layout_width ="@dimen/rvcBtnWidth"
+            android:textSize="@dimen/rvcTextSize"/>
+    </LinearLayout>
+
+    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+        android:layout_height="wrap_content"
+        android:layout_width="match_parent"
+        android:orientation="horizontal">
+        <Button
+            android:id="@+id/btnGetCameraState"
+            android:layout_height="@dimen/rvcBtnHeight"
+            android:layout_width ="@dimen/rvcBtnWidth"
+            android:text="@string/rvc_getCameraState"
+            android:textSize="@dimen/rvcTextSize"/>
+        <TextView
+            android:gravity="center"
+            android:id="@+id/tvCameraState"
+            android:layout_height="@dimen/rvcBtnHeight"
+            android:layout_width ="@dimen/rvcBtnWidth"
+            android:textSize="@dimen/rvcTextSize"/>
+    </LinearLayout>
+
+    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+        android:layout_height="wrap_content"
+        android:layout_width="match_parent"
+        android:orientation="horizontal">
+        <Button
+            android:id="@+id/btnSetRvcCrop"
+            android:layout_height="@dimen/rvcBtnHeight"
+            android:layout_width ="@dimen/rvcBtnWidth"
+            android:text="@string/rvc_setRvcCrop"
+            android:textSize="@dimen/rvcTextSize"/>
+        <Button
+            android:id="@+id/btnSetRvcCrop2"
+            android:layout_height="@dimen/rvcBtnHeight"
+            android:layout_width ="@dimen/rvcBtnWidth"
+            android:text="@string/rvc_setRvcCrop"
+            android:textSize="@dimen/rvcTextSize"/>
+    </LinearLayout>
+
+    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+        android:layout_height="wrap_content"
+        android:layout_width="match_parent"
+        android:orientation="horizontal">
+        <Button
+            android:id="@+id/btnSetRvcPos"
+            android:layout_height="@dimen/rvcBtnHeight"
+            android:layout_width ="@dimen/rvcBtnWidth"
+            android:text="@string/rvc_setRvcPos"
+            android:textSize="@dimen/rvcTextSize"/>
+        <Button
+            android:id="@+id/btnSetRvcPos2"
+            android:layout_height="@dimen/rvcBtnHeight"
+            android:layout_width ="@dimen/rvcBtnWidth"
+            android:text="@string/rvc_setRvcPos"
+            android:textSize="@dimen/rvcTextSize"/>
+        <Button
+            android:id="@+id/btnSetRvcPos3"
+            android:layout_height="@dimen/rvcBtnHeight"
+            android:layout_width ="@dimen/rvcBtnWidth"
+            android:text="@string/rvc_setRvcPos"
+            android:textSize="@dimen/rvcTextSize"/>
+    </LinearLayout>
+
+    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+        android:layout_height="wrap_content"
+        android:layout_width="match_parent"
+        android:orientation="horizontal">
+        <ToggleButton
+            android:id="@+id/btnRvcState"
+            android:layout_height="@dimen/rvcBtnHeight"
+            android:layout_width ="@dimen/rvcBtnWidth"
+            android:text="@string/rvc_state"
+            android:textSize="@dimen/rvcTextSize"/>
+    </LinearLayout>
+</LinearLayout>
diff --git a/tests/EmbeddedKitchenSinkApp/res/layout/hvac_test.xml b/tests/EmbeddedKitchenSinkApp/res/layout/hvac_test.xml
new file mode 100644
index 0000000..6cfe237
--- /dev/null
+++ b/tests/EmbeddedKitchenSinkApp/res/layout/hvac_test.xml
@@ -0,0 +1,180 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2015 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:gravity="center"
+    android:layout_height="match_parent"
+    android:layout_width="match_parent"
+    android:orientation="vertical">
+
+    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+        android:layout_height="wrap_content"
+        android:layout_width="match_parent"
+        android:orientation="horizontal">
+
+        <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+            android:orientation="vertical"
+            android:gravity="center"
+            android:layout_height="match_parent"
+            android:layout_weight="1"
+            android:layout_width = "wrap_content">
+
+            <Button
+                android:clickable="false"
+                android:id="@+id/btnDTempUp"
+                android:layout_height="100dp"
+                android:layout_width ="250dp"
+                android:text="@string/hvac_tempUp"
+                android:textSize="32sp"/>
+
+            <TextView
+                android:gravity="center"
+                android:id="@+id/tvDTemp"
+                android:layout_height="50dp"
+                android:layout_width ="250dp"
+                android:textSize="32sp"/>
+
+            <Button
+                android:clickable="false"
+                android:id="@+id/btnDTempDn"
+                android:layout_height="100dp"
+                android:layout_width ="250dp"
+                android:text="@string/hvac_tempDn"
+                android:textSize="32sp"/>
+        </LinearLayout>
+
+        <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+            android:gravity="center"
+            android:layout_height="match_parent"
+            android:layout_weight="1"
+            android:layout_width = "wrap_content"
+            android:orientation="vertical">
+
+            <Button
+                android:clickable="false"
+                android:id="@+id/btnFanSpeedUp"
+                android:layout_height="100dp"
+                android:layout_width ="250dp"
+                android:text="@string/hvac_fanSpeedUp"
+                android:textSize="32sp"/>
+
+            <TextView
+                android:gravity="center"
+                android:id="@+id/tvFanSpeed"
+                android:layout_height="50dp"
+                android:layout_width ="250dp"
+                android:textSize="32sp"/>
+
+            <Button
+                android:clickable="false"
+                android:id="@+id/btnFanSpeedDn"
+                android:layout_height="100dp"
+                android:layout_width ="250dp"
+                android:text="@string/hvac_fanSpeedDn"
+                android:textSize="32sp"/>
+        </LinearLayout>
+
+        <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+            android:gravity="center"
+            android:layout_height="match_parent"
+            android:layout_weight="1"
+            android:layout_width = "wrap_content"
+            android:orientation="vertical">
+
+            <Button
+                android:clickable="false"
+                android:id="@+id/btnPTempUp"
+                android:layout_height="100dp"
+                android:layout_width ="250dp"
+                android:text="@string/hvac_tempUp"
+                android:textSize="32sp"/>
+
+            <TextView
+                android:gravity="center"
+                android:id="@+id/tvPTemp"
+                android:layout_height="50dp"
+                android:layout_width ="250dp"
+                android:textSize="32sp"/>
+
+            <Button
+                android:clickable="false"
+                android:id="@+id/btnPTempDn"
+                android:layout_height="100dp"
+                android:layout_width ="250dp"
+                android:text="@string/hvac_tempDn"
+                android:textSize="32sp"/>
+        </LinearLayout>
+    </LinearLayout>
+    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+        android:layout_height="wrap_content"
+        android:layout_width="match_parent"
+        android:layout_marginTop="25dp"
+        android:orientation="horizontal">
+
+        <ToggleButton
+            android:clickable="false"
+            android:id="@+id/tbDefrostFront"
+            android:layout_height="100dp"
+            android:layout_weight="1"
+            android:layout_width ="150dp"
+            android:textOff="@string/hvac_defrostFrontOff"
+            android:textOn="@string/hvac_defrostFrontOn"
+            android:textSize="32sp"/>
+
+        <RadioGroup xmlns:android="http://schemas.android.com/apk/res/android"
+            android:clickable="false"
+            android:id="@+id/rgFanPosition"
+            android:layout_height="wrap_content"
+            android:layout_weight="1"
+            android:layout_width="wrap_content"
+            android:orientation="vertical">
+            <RadioButton
+                android:clickable="false"
+                android:id="@+id/rbPositionFace"
+                android:text="@string/hvac_positionFace"
+                android:textSize="32sp"/>
+            <RadioButton
+                android:clickable="false"
+                android:id="@+id/rbPositionFloor"
+                android:text="@string/hvac_positionFloor"
+                android:textSize="32sp"/>
+            <RadioButton
+                android:clickable="false"
+                android:id="@+id/rbPositionFaceAndFloor"
+                android:text="@string/hvac_positionFaceAndFloor"
+                android:textSize="32sp"/>
+        </RadioGroup>
+
+        <ToggleButton
+            android:clickable="false"
+            android:id="@+id/tbAc"
+            android:layout_height="100dp"
+            android:layout_weight="1"
+            android:layout_width="150dp"
+            android:textOff="@string/hvac_acOff"
+            android:textOn="@string/hvac_acOn"
+            android:textSize="32sp"/>
+
+        <ToggleButton
+            android:clickable="false"
+            android:id="@+id/tbDefrostRear"
+            android:layout_height="100dp"
+            android:layout_weight="1"
+            android:layout_width ="150dp"
+            android:textOff="@string/hvac_defrostRearOff"
+            android:textOn="@string/hvac_defrostRearOn"
+            android:textSize="32sp"/>
+    </LinearLayout>
+</LinearLayout>
diff --git a/tests/EmbeddedKitchenSinkApp/res/layout/instrument_cluster.xml b/tests/EmbeddedKitchenSinkApp/res/layout/instrument_cluster.xml
new file mode 100644
index 0000000..aebb27d
--- /dev/null
+++ b/tests/EmbeddedKitchenSinkApp/res/layout/instrument_cluster.xml
@@ -0,0 +1,50 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2016 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+              android:orientation="vertical"
+              android:layout_width="match_parent"
+              android:layout_height="match_parent"
+              android:layout_marginTop="40dp"
+              android:layout_marginLeft="40dp">
+
+    <LinearLayout
+            android:orientation="vertical"
+            android:layout_width="match_parent"
+            android:layout_height="match_parent">
+        <LinearLayout
+                android:orientation="horizontal"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content">
+            <Button android:layout_width="wrap_content" android:layout_height="wrap_content"
+                    android:text="@string/cluster_start" android:id="@+id/cluster_start_button"
+                    android:layout_column="0" android:textSize="32sp"/>
+            <Button android:layout_width="wrap_content" android:layout_height="wrap_content"
+                    android:text="@string/cluster_turn_left" android:id="@+id/cluster_turn_left_button"
+                    android:layout_column="0" android:textSize="32sp"/>
+            <Button android:layout_width="wrap_content" android:layout_height="wrap_content"
+                    android:text="@string/cluster_stop" android:id="@+id/cluster_stop_button"
+                    android:layout_column="0" android:textSize="32sp"/>
+        </LinearLayout>
+        <EditText
+                android:layout_width="match_parent"
+                android:layout_height="fill_parent"
+                android:inputType="textMultiLine"
+                android:ems="10"
+                android:id="@+id/cluster_log"
+                android:scrollIndicators="right" android:gravity="top"
+                android:editable="false"/>
+    </LinearLayout>
+</LinearLayout>
\ No newline at end of file
diff --git a/tests/EmbeddedKitchenSinkApp/res/layout/job_scheduler.xml b/tests/EmbeddedKitchenSinkApp/res/layout/job_scheduler.xml
new file mode 100644
index 0000000..f291f04
--- /dev/null
+++ b/tests/EmbeddedKitchenSinkApp/res/layout/job_scheduler.xml
@@ -0,0 +1,119 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2015 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:orientation="horizontal" android:layout_width="match_parent"
+    android:layout_height="match_parent" android:layout_marginTop="90dp"
+    android:layout_marginLeft="90dp">
+
+    <LinearLayout
+        android:layout_width="0dp"
+        android:layout_height="match_parent"
+        android:layout_weight="1"
+        android:orientation="vertical">
+        <LinearLayout
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content">
+            <Button
+                android:id="@+id/refresh_button"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:text="@string/refresh_button" />
+            <Button
+                android:id="@+id/cancel_button"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:text="@string/cancel_button" />
+        </LinearLayout>
+        <TextView
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:text="@string/job_info"
+            android:textColor="@android:color/holo_red_dark" />
+        <TextView
+            android:id="@+id/current_jobs"
+            android:layout_width="100dp"
+            android:layout_height="wrap_content" />
+    </LinearLayout>
+
+    <LinearLayout
+        android:layout_width="0dp"
+        android:layout_height="match_parent"
+        android:layout_weight="1"
+        android:orientation="vertical">
+        <Button
+            android:id="@+id/schedule_button"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:text="@string/schedule_button"/>
+        <CheckBox
+            android:id="@+id/require_idle"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:text="@string/require_idle"/>
+        <CheckBox
+            android:id="@+id/require_charging"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:text="@string/require_charging"/>
+        <CheckBox
+            android:id="@+id/require_persisted"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:text="@string/require_persisted"/>
+        <TextView
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:text="@string/require_network"/>
+        <RadioGroup
+            android:id="@+id/network_group"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content">
+
+            <RadioButton android:id="@+id/network_any"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:text="@string/network_any"/>
+
+            <RadioButton android:id="@+id/network_none"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:text="@string/network_none"/>
+
+            <RadioButton android:id="@+id/network_unmetered"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:text="@string/network_unmetered"/>
+        </RadioGroup>
+
+        <LinearLayout
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content">
+            <TextView
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:text="@string/dish_num"/>
+
+            <android.support.car.input.CarRestrictedEditText
+                android:id="@+id/dish_num"
+                android:layout_width="100dp"
+                android:layout_height="40dp"
+                android:inputType="number"
+                android:text="@string/default_dish_num" />
+        </LinearLayout>
+
+    </LinearLayout>
+
+</LinearLayout>
\ No newline at end of file
diff --git a/tests/EmbeddedKitchenSinkApp/res/layout/keyboard_end_view.xml b/tests/EmbeddedKitchenSinkApp/res/layout/keyboard_end_view.xml
new file mode 100644
index 0000000..d60f63d
--- /dev/null
+++ b/tests/EmbeddedKitchenSinkApp/res/layout/keyboard_end_view.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2015 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:orientation="vertical" android:layout_width="match_parent"
+    android:layout_height="match_parent">
+    <ProgressBar
+        android:layout_width="50dp"
+        android:layout_height="50dp" />
+</LinearLayout>
\ No newline at end of file
diff --git a/tests/EmbeddedKitchenSinkApp/res/layout/keyboard_test.xml b/tests/EmbeddedKitchenSinkApp/res/layout/keyboard_test.xml
new file mode 100644
index 0000000..c617094
--- /dev/null
+++ b/tests/EmbeddedKitchenSinkApp/res/layout/keyboard_test.xml
@@ -0,0 +1,53 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2015 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:orientation="vertical" android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:layout_marginTop="96dp"
+    android:layout_marginLeft="96dp">
+
+    <TextView
+        android:id="@+id/search_text"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:text="@string/on_search"
+        android:textSize="20dp" />
+
+    <TextView
+        android:id="@+id/edit_text"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:text="@string/on_edit"
+        android:textSize="20dp" />
+
+    <Button
+        android:id="@+id/ime_button"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:text="@string/open_ime_button"/>
+
+    <Button
+        android:id="@+id/stop_ime_button"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:text="@string/close_ime_button"/>
+
+    <Button
+        android:id="@+id/ime_button2"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:text="@string/open_kb_button"/>
+</LinearLayout>
\ No newline at end of file
diff --git a/tests/EmbeddedKitchenSinkApp/res/layout/kitchen_sink_activity.xml b/tests/EmbeddedKitchenSinkApp/res/layout/kitchen_sink_activity.xml
new file mode 100644
index 0000000..2801daa
--- /dev/null
+++ b/tests/EmbeddedKitchenSinkApp/res/layout/kitchen_sink_activity.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2015 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"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:layout_marginTop="100dp"
+    android:orientation="vertical" >
+</LinearLayout>
diff --git a/tests/EmbeddedKitchenSinkApp/res/raw/john_harrison_with_the_wichita_state_university_chamber_players_05_summer_mvt_2_adagio.mp3 b/tests/EmbeddedKitchenSinkApp/res/raw/john_harrison_with_the_wichita_state_university_chamber_players_05_summer_mvt_2_adagio.mp3
new file mode 100644
index 0000000..25e6be6
--- /dev/null
+++ b/tests/EmbeddedKitchenSinkApp/res/raw/john_harrison_with_the_wichita_state_university_chamber_players_05_summer_mvt_2_adagio.mp3
Binary files differ
diff --git a/tests/EmbeddedKitchenSinkApp/res/raw/one2six.mp3 b/tests/EmbeddedKitchenSinkApp/res/raw/one2six.mp3
new file mode 100644
index 0000000..1e2dc01
--- /dev/null
+++ b/tests/EmbeddedKitchenSinkApp/res/raw/one2six.mp3
Binary files differ
diff --git a/tests/EmbeddedKitchenSinkApp/res/raw/ring_classic_01.ogg b/tests/EmbeddedKitchenSinkApp/res/raw/ring_classic_01.ogg
new file mode 100644
index 0000000..b5e6077
--- /dev/null
+++ b/tests/EmbeddedKitchenSinkApp/res/raw/ring_classic_01.ogg
Binary files differ
diff --git a/tests/EmbeddedKitchenSinkApp/res/raw/turnright.mp3 b/tests/EmbeddedKitchenSinkApp/res/raw/turnright.mp3
new file mode 100644
index 0000000..6ef127e
--- /dev/null
+++ b/tests/EmbeddedKitchenSinkApp/res/raw/turnright.mp3
Binary files differ
diff --git a/tests/EmbeddedKitchenSinkApp/res/values/dimens.xml b/tests/EmbeddedKitchenSinkApp/res/values/dimens.xml
new file mode 100644
index 0000000..e35ee3d
--- /dev/null
+++ b/tests/EmbeddedKitchenSinkApp/res/values/dimens.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2016 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.
+-->
+<resources>
+    <dimen name="rvcBtnHeight">40dp</dimen>
+    <dimen name="rvcBtnWidth">150dp</dimen>
+    <dimen name="rvcTextSize">10dp</dimen>
+    <dimen name="rvcTvHeight">80dp</dimen>
+</resources>
\ No newline at end of file
diff --git a/tests/EmbeddedKitchenSinkApp/res/values/strings.xml b/tests/EmbeddedKitchenSinkApp/res/values/strings.xml
new file mode 100644
index 0000000..e99c226
--- /dev/null
+++ b/tests/EmbeddedKitchenSinkApp/res/values/strings.xml
@@ -0,0 +1,102 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2015 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.
+-->
+<resources>
+    <string name="app_title">Kitchen Sink</string>
+    <!-- hvac -->
+    <string name="hvac_acOff">AC OFF</string>
+    <string name="hvac_acOn">AC ON</string>
+    <string name="hvac_defrostFrontOff">Front Defrost OFF</string>
+    <string name="hvac_defrostFrontOn">Front Defrost ON</string>
+    <string name="hvac_defrostRearOff">Rear Defrost OFF</string>
+    <string name="hvac_defrostRearOn">Rear Defrost ON</string>
+    <string name="hvac_positionFace">Face</string>
+    <string name="hvac_positionFloor">Floor</string>
+    <string name="hvac_positionFaceAndFloor">Face and Floor</string>
+    <string name="hvac_directionFaceAndDefrost">Face and Defrost</string>
+    <string name="hvac_fanSpeedDn">Fan Speed Down</string>
+    <string name="hvac_fanSpeedUp">Fan Speed Up</string>
+    <string name="hvac_tempDn">Temp Down</string>
+    <string name="hvac_tempUp">Temp Up</string>
+    <!-- camera -->
+    <string name="rvc_getCapabilities">Get Capabilities</string>
+    <string name="rvc_getRvcCrop">Get RVC Crop</string>
+    <string name="rvc_setRvcCrop">Set RVC Crop</string>
+    <string name="rvc_getRvcPos">Get RVC Pos</string>
+    <string name="rvc_setRvcPos">Set RVC Pos</string>
+    <string name="rvc_getCameraState">Get State</string>
+    <string name="rvc_setCameraState">Set State</string>
+    <string name="rvc_state">RVC State</string>
+    <!--  audio -->
+    <string name="empty"></string>
+    <string name="play">Play</string>
+    <string name="play_pcm_once">Play Once</string>
+    <string name="stop">Stop</string>
+    <string name="audio_focus">Audio Focus</string>
+    <string name="focus_gain">Gain</string>
+    <string name="focus_gain_transient_duck">Gain,Transient,Duck</string>
+    <string name="focus_release">Release</string>
+    <string name="request">request</string>
+    <string name="release">release</string>
+    <string name="media_play">Media Play</string>
+    <string name="nav_play">Nav Play</string>
+    <string name="vr_play">VR Play</string>
+    <string name="system_play">System Play</string>
+    <string name="config_index">Config Index</string>
+    <string name="choose_config">Choose config</string>
+    <string name="audio_stress_title">Audio Stress Test</string>
+    <string name="nav_focus_start">Nav Focus Start</string>
+    <string name="nav_focus_end">Nav Focus End</string>
+    <string name="vr_start">VR Focus Start</string>
+    <string name="vr_end">VR Focus End</string>
+    <string name="speaker_phone_on">Speaker Phone On</string>
+    <string name="speaker_phone_off">Speaker Phone Off</string>
+    <string name="microphone_on">Mic On</string>
+    <string name="microphone_off">Mic Off</string>
+    <string name="mock_audio_on">Audio Mocking On</string>
+    <string name="mock_audio_off">Audio Mocking Off</string>
+    <string name="reject_audio_focus_on">Reject Audio Focus</string>
+    <string name="reject_audio_focus_off">Allow Audio Focus</string>
+
+    <!-- job scheduler -->
+    <string name="schedule_button">Schedule a  New Job</string>
+    <string name="refresh_button">Refresh pending jobs</string>
+    <string name="cancel_button">Cancel All Jobs</string>
+    <string name="job_info">Current Pending Jobs</string>
+    <string name="require_charging">Require Charging</string>
+    <string name="require_persisted">Persisted</string>
+    <string name="require_periodic">Periodic</string>
+    <string name="require_network">Network Type</string>
+    <string name="require_idle">Device Idle</string>
+    <string name="dish_num">Number of Plates</string>
+    <string name="default_dish_num">50</string>
+    <string name="network_any">ANY</string>
+    <string name="network_none">NONE</string>
+    <string name="network_unmetered">UNMETERED</string>
+
+    <!-- keyboard test fragment -->
+    <string name="keyboard_test_title">Keyboard Test</string>
+    <string name="on_search">OnSearch:</string>
+    <string name="on_edit">onEdit:</string>
+    <string name="open_ime_button">Open IME</string>
+    <string name="close_ime_button">Close IME</string>
+    <string name="open_kb_button">Hide/Show Input</string>
+
+    <!-- instrument cluster -->
+    <string name="cluster_start">Start Nav</string>
+    <string name="cluster_turn_left">Turn left</string>
+    <string name="cluster_stop">Stop Nav</string>
+    <string name="cluster_nav_app_context_loss">Navigation app context lost!</string>
+</resources>
diff --git a/tests/EmbeddedKitchenSinkApp/res/xml/automotive_app_desc.xml b/tests/EmbeddedKitchenSinkApp/res/xml/automotive_app_desc.xml
new file mode 100644
index 0000000..9bb6ae7
--- /dev/null
+++ b/tests/EmbeddedKitchenSinkApp/res/xml/automotive_app_desc.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2015 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.
+-->
+<automotiveApp>
+    <uses name="service" />
+    <uses name="projection" />
+    <uses name="activity" class="com.google.android.car.kitchensink.KitchenSinkProxyActivity" />
+</automotiveApp>
diff --git a/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/CarEmulator.java b/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/CarEmulator.java
new file mode 100644
index 0000000..29aad2c
--- /dev/null
+++ b/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/CarEmulator.java
@@ -0,0 +1,207 @@
+/*
+ * Copyright (C) 2015 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.google.android.car.kitchensink;
+
+import android.car.Car;
+import android.car.test.VehicleHalEmulator.VehicleHalPropertyHandler;
+import android.car.test.VehicleHalEmulator;
+import android.os.SystemClock;
+
+import com.android.car.vehiclenetwork.VehicleNetworkConsts;
+import com.android.car.vehiclenetwork.VehicleNetworkConsts.VehicleAudioExtFocusFlag;
+import com.android.car.vehiclenetwork.VehicleNetworkConsts.VehicleAudioFocusIndex;
+import com.android.car.vehiclenetwork.VehicleNetworkConsts.VehicleAudioFocusRequest;
+import com.android.car.vehiclenetwork.VehicleNetworkConsts.VehicleAudioFocusState;
+import com.android.car.vehiclenetwork.VehicleNetworkConsts.VehicleAudioStream;
+import com.android.car.vehiclenetwork.VehiclePropConfigUtil;
+import com.android.car.vehiclenetwork.VehiclePropValueUtil;
+import com.android.car.vehiclenetwork.VehicleNetworkConsts.VehiclePermissionModel;
+import com.android.car.vehiclenetwork.VehicleNetworkConsts.VehiclePropAccess;
+import com.android.car.vehiclenetwork.VehicleNetworkConsts.VehiclePropChangeMode;
+import com.android.car.vehiclenetwork.VehicleNetworkConsts.VehicleValueType;
+import com.android.car.vehiclenetwork.VehicleNetworkProto.VehiclePropValue;
+
+public class CarEmulator {
+
+    private final Car mCar;
+    private final VehicleHalEmulator mHalEmulator;
+
+    private final AudioFocusPropertyHandler mAudioFocusPropertyHandler =
+            new AudioFocusPropertyHandler();
+    private final AudioStreamStatePropertyHandler mAudioStreamStatePropertyHandler =
+            new AudioStreamStatePropertyHandler();
+
+    public CarEmulator(Car car) {
+        mCar = car;
+        mHalEmulator = new VehicleHalEmulator(car);
+        mHalEmulator.addProperty(
+                VehiclePropConfigUtil.getBuilder(
+                        VehicleNetworkConsts.VEHICLE_PROPERTY_AUDIO_FOCUS,
+                        VehiclePropAccess.VEHICLE_PROP_ACCESS_READ_WRITE,
+                        VehiclePropChangeMode.VEHICLE_PROP_CHANGE_MODE_ON_CHANGE,
+                        VehicleValueType.VEHICLE_VALUE_TYPE_INT32_VEC3,
+                        VehiclePermissionModel.VEHICLE_PERMISSION_SYSTEM_APP_ONLY,
+                        0 /*configFlags*/, 0 /*sampleRateMax*/, 0 /*sampleRateMin*/).build(),
+                        mAudioFocusPropertyHandler);
+        mHalEmulator.addProperty(
+                VehiclePropConfigUtil.getBuilder(
+                        VehicleNetworkConsts.VEHICLE_PROPERTY_INTERNAL_AUDIO_STREAM_STATE,
+                        VehiclePropAccess.VEHICLE_PROP_ACCESS_READ_WRITE,
+                        VehiclePropChangeMode.VEHICLE_PROP_CHANGE_MODE_ON_CHANGE,
+                        VehicleValueType.VEHICLE_VALUE_TYPE_INT32_VEC2,
+                        VehiclePermissionModel.VEHICLE_PERMISSION_SYSTEM_APP_ONLY,
+                        0 /*configFlags*/, 0 /*sampleRateMax*/, 0 /*sampleRateMin*/).build(),
+                        mAudioStreamStatePropertyHandler);
+    }
+
+    public void start() {
+        mHalEmulator.start();
+    }
+
+    public void stop() {
+        mHalEmulator.stop();
+    }
+
+    public void setAudioFocusControl(boolean reject) {
+        mAudioFocusPropertyHandler.setAudioFocusControl(reject);
+    }
+
+    private class AudioFocusPropertyHandler implements VehicleHalPropertyHandler {
+        private boolean mRejectFocus;
+        private int mCurrentFocusState = VehicleAudioFocusState.VEHICLE_AUDIO_FOCUS_STATE_LOSS;
+        private int mCurrentFocusStreams = 0;
+        private int mCurrentFocusExtState = 0;
+
+        public void setAudioFocusControl(boolean reject) {
+            VehiclePropValue injectValue = null;
+            synchronized (this) {
+                if (reject) {
+                    if (!mRejectFocus) {
+                        mCurrentFocusState =
+                                VehicleAudioFocusState.VEHICLE_AUDIO_FOCUS_STATE_LOSS_TRANSIENT_EXLCUSIVE;
+                        mCurrentFocusStreams = 0;
+                        mCurrentFocusExtState = 0;
+                        mRejectFocus = true;
+                        int[] values = { mCurrentFocusState, mCurrentFocusStreams,
+                                mCurrentFocusExtState };
+                        injectValue = VehiclePropValueUtil.createIntVectorValue(
+                                VehicleNetworkConsts.VEHICLE_PROPERTY_AUDIO_FOCUS, values,
+                                SystemClock.elapsedRealtimeNanos());
+                    }
+                } else {
+                    if (mRejectFocus) {
+                        mCurrentFocusState = VehicleAudioFocusState.VEHICLE_AUDIO_FOCUS_STATE_LOSS;
+                        mCurrentFocusStreams = 0;
+                        mCurrentFocusExtState = 0;
+                        int[] values = { mCurrentFocusState, mCurrentFocusStreams,
+                                mCurrentFocusExtState };
+                        injectValue = VehiclePropValueUtil.createIntVectorValue(
+                                VehicleNetworkConsts.VEHICLE_PROPERTY_AUDIO_FOCUS, values,
+                                SystemClock.elapsedRealtimeNanos());
+                    }
+                    mRejectFocus = false;
+                }
+            }
+            if (injectValue != null) {
+                mHalEmulator.injectEvent(injectValue);
+            }
+        }
+
+        @Override
+        public void onPropertySet(VehiclePropValue value) {
+            VehiclePropValue injectValue = null;
+            synchronized (this) {
+                if (mRejectFocus) {
+                    int[] values = { mCurrentFocusState, mCurrentFocusStreams,
+                            mCurrentFocusExtState };
+                    injectValue = VehiclePropValueUtil.createIntVectorValue(
+                            VehicleNetworkConsts.VEHICLE_PROPERTY_AUDIO_FOCUS, values,
+                            SystemClock.elapsedRealtimeNanos());
+                } else {
+                    int request = value.getInt32Values(
+                            VehicleAudioFocusIndex.VEHICLE_AUDIO_FOCUS_INDEX_FOCUS);
+                    int requestedStreams = value.getInt32Values(
+                            VehicleAudioFocusIndex.VEHICLE_AUDIO_FOCUS_INDEX_STREAMS);
+                    int requestedExtFocus = value.getInt32Values(
+                            VehicleAudioFocusIndex.VEHICLE_AUDIO_FOCUS_INDEX_EXTERNAL_FOCUS_STATE);
+                    int response = VehicleAudioFocusState.VEHICLE_AUDIO_FOCUS_STATE_LOSS;
+                    switch (request) {
+                        case VehicleAudioFocusRequest.VEHICLE_AUDIO_FOCUS_REQUEST_GAIN:
+                            response = VehicleAudioFocusState.VEHICLE_AUDIO_FOCUS_STATE_GAIN;
+                            break;
+                        case VehicleAudioFocusRequest.VEHICLE_AUDIO_FOCUS_REQUEST_GAIN_TRANSIENT:
+                        case VehicleAudioFocusRequest.VEHICLE_AUDIO_FOCUS_REQUEST_GAIN_TRANSIENT_MAY_DUCK:
+                            response =
+                                VehicleAudioFocusState.VEHICLE_AUDIO_FOCUS_STATE_GAIN_TRANSIENT;
+                            break;
+                        case VehicleAudioFocusRequest.VEHICLE_AUDIO_FOCUS_REQUEST_RELEASE:
+                            response = VehicleAudioFocusState.VEHICLE_AUDIO_FOCUS_STATE_LOSS;
+                            break;
+                    }
+                    mCurrentFocusState = response;
+                    mCurrentFocusStreams = requestedStreams;
+                    mCurrentFocusExtState = requestedExtFocus;
+                    int[] values = { mCurrentFocusState, mCurrentFocusStreams,
+                            mCurrentFocusExtState };
+                    injectValue = VehiclePropValueUtil.createIntVectorValue(
+                            VehicleNetworkConsts.VEHICLE_PROPERTY_AUDIO_FOCUS, values,
+                            SystemClock.elapsedRealtimeNanos());
+                }
+            }
+            if (injectValue != null) {
+                mHalEmulator.injectEvent(injectValue);
+            }
+        }
+
+        @Override
+        public synchronized VehiclePropValue onPropertyGet(VehiclePropValue value) {
+            int[] values = { mCurrentFocusState, mCurrentFocusStreams,
+                    mCurrentFocusExtState };
+            return VehiclePropValueUtil.createIntVectorValue(
+                    VehicleNetworkConsts.VEHICLE_PROPERTY_AUDIO_FOCUS, values,
+                    SystemClock.elapsedRealtimeNanos());
+        }
+
+        @Override
+        public void onPropertySubscribe(int property, float sampleRate, int zones) {
+        }
+
+        @Override
+        public void onPropertyUnsubscribe(int property) {
+        }
+    }
+
+    private class AudioStreamStatePropertyHandler implements VehicleHalPropertyHandler {
+        @Override
+        public void onPropertySet(VehiclePropValue value) {
+        }
+
+        @Override
+        public VehiclePropValue onPropertyGet(VehiclePropValue value) {
+            //ignore
+            return null;
+        }
+
+        @Override
+        public void onPropertySubscribe(int property, float sampleRate, int zones) {
+        }
+
+        @Override
+        public void onPropertyUnsubscribe(int property) {
+        }
+    }
+}
diff --git a/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/KitchenSinkActivity.java b/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/KitchenSinkActivity.java
new file mode 100644
index 0000000..d88bb60
--- /dev/null
+++ b/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/KitchenSinkActivity.java
@@ -0,0 +1,270 @@
+/*
+ * Copyright (C) 2015 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.google.android.car.kitchensink;
+
+import com.google.android.car.kitchensink.audio.AudioTestFragment;
+import com.google.android.car.kitchensink.camera.CameraTestFragment;
+import com.google.android.car.kitchensink.cluster.InstrumentClusterFragment;
+import com.google.android.car.kitchensink.hvac.HvacTestFragment;
+import com.google.android.car.kitchensink.job.JobSchedulerFragment;
+import com.google.android.car.kitchensink.keyboard.KeyboardFragment;
+
+import android.car.hardware.camera.CarCameraManager;
+import android.car.hardware.hvac.CarHvacManager;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.graphics.Color;
+import android.os.Bundle;
+import android.os.IBinder;
+import android.support.car.Car;
+import android.support.car.CarAppContextManager;
+import android.support.car.CarNotConnectedException;
+import android.support.car.ServiceConnectionListener;
+import android.support.car.app.menu.CarDrawerActivity;
+import android.support.car.app.menu.CarMenu;
+import android.support.car.app.menu.CarMenuCallbacks;
+import android.support.car.app.menu.RootMenu;
+import android.support.car.hardware.CarSensorEvent;
+import android.support.car.hardware.CarSensorManager;
+import android.support.car.navigation.CarNavigationManager;
+import android.util.Log;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class KitchenSinkActivity extends CarDrawerActivity {
+    private static final String TAG = "KitchenSinkActivity";
+
+    private static final String MENU_AUDIO = "audio";
+    private static final String MENU_CAMERA = "camera";
+    private static final String MENU_HVAC = "hvac";
+    private static final String MENU_QUIT = "quit";
+    private static final String MENU_JOB = "job_scheduler";
+    private static final String MENU_KEYBOARD = "keyboard";
+    private static final String MENU_CLUSTER = "inst cluster";
+
+    private Car mCarApi;
+    private CarCameraManager mCameraManager;
+    private CarHvacManager mHvacManager;
+    private CarSensorManager mCarSensorManager;
+    private CarNavigationManager mCarNavigationManager;
+    private CarAppContextManager mCarAppContextManager;
+
+
+    private AudioTestFragment mAudioTestFragment;
+    private CameraTestFragment mCameraTestFragment;
+    private HvacTestFragment mHvacTestFragment;
+    private JobSchedulerFragment mJobFragment;
+    private KeyboardFragment mKeyboardFragment;
+    private InstrumentClusterFragment mInstrumentClusterFragment;
+
+    private final CarSensorManager.CarSensorEventListener mListener =
+            new CarSensorManager.CarSensorEventListener() {
+        @Override
+        public void onSensorChanged(CarSensorEvent event) {
+            switch (event.sensorType) {
+                case CarSensorManager.SENSOR_TYPE_DRIVING_STATUS:
+                    Log.d(TAG, "driving status:" + event.intValues[0]);
+                    break;
+            }
+        }
+    };
+
+    public KitchenSinkActivity(Proxy proxy, Context context, Car car) {
+        super(proxy, context, car);
+    }
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        resetTitle();
+        setScrimColor(Color.LTGRAY);
+        setLightMode();
+        setCarMenuCallbacks(new MyCarMenuCallbacks());
+        setContentView(R.layout.kitchen_sink_activity);
+
+        // Connection to Car Service does not work for non-automotive yet.
+        if (getContext().getPackageManager().hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE)) {
+            mCarApi = Car.createCar(getContext(), mServiceConnectionListener);
+            mCarApi.connect();
+        }
+        Log.i(TAG, "onCreate");
+    }
+
+    @Override
+    protected void onStart() {
+        super.onStart();
+        Log.i(TAG, "onStart");
+    }
+
+    @Override
+    protected void onRestart() {
+        super.onRestart();
+        Log.i(TAG, "onRestart");
+    }
+
+    @Override
+    protected void onResume() {
+        super.onResume();
+        Log.i(TAG, "onResume");
+    }
+
+    @Override
+    protected void onPause() {
+        super.onPause();
+        Log.i(TAG, "onPause");
+    }
+
+    @Override
+    protected void onStop() {
+        super.onStop();
+        Log.i(TAG, "onStop");
+    }
+
+    @Override
+    protected void onDestroy() {
+        super.onDestroy();
+        if (mCarSensorManager != null) {
+            mCarSensorManager.unregisterListener(mListener);
+        }
+        if (mCarApi != null) {
+            mCarApi.disconnect();
+        }
+        Log.i(TAG, "onDestroy");
+    }
+
+    private void resetTitle() {
+        setTitle(getContext().getString(R.string.app_title));
+    }
+
+    private final ServiceConnectionListener mServiceConnectionListener =
+            new ServiceConnectionListener() {
+        @Override
+        public void onServiceConnected(ComponentName name, IBinder service) {
+            Log.d(TAG, "Connected to Car Service");
+            try {
+                mCameraManager = (CarCameraManager) mCarApi.getCarManager(android.car.Car
+                        .CAMERA_SERVICE);
+                mHvacManager = (CarHvacManager) mCarApi.getCarManager(android.car.Car.HVAC_SERVICE);
+                mCarNavigationManager = (CarNavigationManager) mCarApi.getCarManager(
+                        android.car.Car.CAR_NAVIGATION_SERVICE);
+                mCarSensorManager = (CarSensorManager) mCarApi.getCarManager(Car.SENSOR_SERVICE);
+                mCarSensorManager.registerListener(mListener,
+                        CarSensorManager.SENSOR_TYPE_DRIVING_STATUS,
+                        CarSensorManager.SENSOR_RATE_NORMAL);
+                mCarAppContextManager =
+                        (CarAppContextManager) mCarApi.getCarManager(Car.APP_CONTEXT_SERVICE);
+            } catch (CarNotConnectedException e) {
+                Log.e(TAG, "Car is not connected!");
+            }
+        }
+
+        @Override
+        public void onServiceDisconnected(ComponentName name) {
+            Log.d(TAG, "Disconnect from Car Service");
+        }
+
+        @Override
+        public void onServiceSuspended(int cause) {
+            Log.d(TAG, "Car Service connection suspended");
+        }
+
+        @Override
+        public void onServiceConnectionFailed(int cause) {
+            Log.d(TAG, "Car Service connection failed");
+        }
+    };
+
+    private final class MyCarMenuCallbacks extends CarMenuCallbacks {
+        /** Id for the root menu */
+        private static final String ROOT = "ROOT";
+
+        @Override
+        public RootMenu onGetRoot(Bundle hints) {
+            return new RootMenu(ROOT);
+        }
+
+        @Override
+        public void onLoadChildren(String parentId, CarMenu result) {
+            List<CarMenu.Item> items = new ArrayList<>();
+            if (parentId.equals(ROOT)) {
+                String[] allMenus = {
+                        MENU_AUDIO, MENU_CAMERA, MENU_HVAC, MENU_JOB, MENU_KEYBOARD, MENU_CLUSTER,
+                        MENU_QUIT
+                };
+                for (String menu : allMenus) {
+                    items.add(new CarMenu.Builder(menu).setText(menu).build());
+                }
+            }
+            result.sendResult(items);
+        }
+
+        @Override
+        public void onItemClicked(String id) {
+            Log.d(TAG, "onItemClicked id=" + id);
+            if (id.equals(MENU_AUDIO)) {
+                if (mAudioTestFragment == null) {
+                    mAudioTestFragment = new AudioTestFragment(getContext());
+                }
+                setContentFragment(mAudioTestFragment);
+            } else if (id.equals(MENU_CAMERA)) {
+                if (mCameraManager != null) {
+                    if (mCameraTestFragment == null) {
+                        mCameraTestFragment = new CameraTestFragment();
+                        mCameraTestFragment.setCameraManager(mCameraManager);
+                    }
+                    // Don't allow camera fragment to start if we don't have a manager.
+                    setContentFragment(mCameraTestFragment);
+                }
+            } else if (id.equals(MENU_HVAC)) {
+                if (mHvacManager != null) {
+                    if (mHvacTestFragment == null) {
+                        mHvacTestFragment = new HvacTestFragment();
+                        mHvacTestFragment.setHvacManager(mHvacManager);
+                    }
+                    // Don't allow HVAC fragment to start if we don't have a manager.
+                    setContentFragment(mHvacTestFragment);
+                }
+            } else if (id.equals(MENU_JOB)) {
+                if (mJobFragment == null) {
+                    mJobFragment = new JobSchedulerFragment();
+                }
+                setContentFragment(mJobFragment);
+            } else if (id.equals(MENU_KEYBOARD)) {
+                if (mKeyboardFragment == null) {
+                    mKeyboardFragment = new KeyboardFragment();
+                }
+                setContentFragment(mKeyboardFragment);
+            } else if (id.equals(MENU_CLUSTER)) {
+                if (mInstrumentClusterFragment == null) {
+                    mInstrumentClusterFragment = new InstrumentClusterFragment();
+                    mInstrumentClusterFragment.setCarNavigationManager(mCarNavigationManager);
+                    mInstrumentClusterFragment.setCarAppContextManager(mCarAppContextManager);
+                }
+                setContentFragment(mInstrumentClusterFragment);
+            } else if (id.equals(MENU_QUIT)) {
+                finish();
+            }
+        }
+
+        @Override
+        public void onCarMenuClosed() {
+            resetTitle();
+        }
+    }
+}
diff --git a/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/KitchenSinkProxyActivity.java b/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/KitchenSinkProxyActivity.java
new file mode 100644
index 0000000..40bebd9
--- /dev/null
+++ b/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/KitchenSinkProxyActivity.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2015 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.google.android.car.kitchensink;
+
+import android.support.car.app.CarProxyActivity;
+
+public class KitchenSinkProxyActivity extends CarProxyActivity {
+
+    public KitchenSinkProxyActivity() {
+        super(KitchenSinkActivity.class, true);
+    }
+}
diff --git a/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/audio/AudioPlayer.java b/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/audio/AudioPlayer.java
new file mode 100644
index 0000000..58a6772
--- /dev/null
+++ b/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/audio/AudioPlayer.java
@@ -0,0 +1,213 @@
+/*
+ * Copyright (C) 2015 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.google.android.car.kitchensink.audio;
+
+import android.content.Context;
+import android.content.res.AssetFileDescriptor;
+import android.media.AudioAttributes;
+import android.media.AudioManager;
+import android.media.MediaPlayer;
+import android.util.Log;
+
+import com.google.android.car.kitchensink.R;
+
+import java.io.IOException;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+/**
+ * Class for playing music.
+ *
+ * This is not thread safe and all public calls should be made from main thread.
+ *
+ * MP3 used is from http://freemusicarchive.org/music/John_Harrison_with_the_Wichita_State_University_Chamber_Players/The_Four_Seasons_Vivaldi/05_-_Vivaldi_Summer_mvt_2_Adagio_-_John_Harrison_violin
+ * from John Harrison with the Wichita State University Chamber Players
+ * Copyright under Create Commons license.
+ */
+public class AudioPlayer {
+
+    public interface PlayStateListener {
+        void onCompletion();
+    }
+
+    private static final String TAG = AudioPlayer.class.getSimpleName();
+
+    private final AudioManager.OnAudioFocusChangeListener mFocusListener =
+            new AudioManager.OnAudioFocusChangeListener() {
+
+        @Override
+        public void onAudioFocusChange(int focusChange) {
+            Log.i(TAG, "audio focus change " + focusChange);
+            if (mPlayer == null) {
+                return;
+            }
+            if (focusChange == AudioManager.AUDIOFOCUS_GAIN) {
+                mPlayer.setVolume(1.0f, 1.0f);
+                if (mRepeat && isPlaying()) {
+                    doResume();
+                }
+            } else if (focusChange == AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK) {
+                if (isPlaying()) {
+                    mPlayer.setVolume(0.5f, 0.5f);
+                }
+            } else if (focusChange == AudioManager.AUDIOFOCUS_LOSS_TRANSIENT && mRepeat) {
+                if (isPlaying()) {
+                    doPause();
+                }
+            } else {
+                if (isPlaying()) {
+                    doStop();
+                }
+            }
+        }
+    };
+
+    private AudioManager mAudioManager;
+    private MediaPlayer mPlayer;
+
+    private final Context mContext;
+    private final int mResourceId;
+    private final AudioAttributes mAttrib;
+
+    private final AtomicBoolean mPlaying = new AtomicBoolean(false);
+
+    private volatile boolean mHandleFocus;
+    private volatile boolean mRepeat;
+
+    private PlayStateListener mListener;
+
+    public AudioPlayer(Context context, int resourceId, AudioAttributes attrib) {
+        mContext = context;
+        mResourceId = resourceId;
+        mAttrib = attrib;
+    }
+
+    public void start(boolean handleFocus, boolean repeat) {
+        mHandleFocus = handleFocus;
+        mRepeat = repeat;
+        mAudioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
+        int ret = AudioManager.AUDIOFOCUS_REQUEST_GRANTED;
+        if (mHandleFocus) {
+            int focusRequest = repeat ? AudioManager.AUDIOFOCUS_GAIN :
+                AudioManager.AUDIOFOCUS_GAIN_TRANSIENT;
+            ret = mAudioManager.requestAudioFocus(mFocusListener, mAttrib,
+                    focusRequest, 0);
+        }
+        if (ret == AudioManager.AUDIOFOCUS_REQUEST_GRANTED) {
+            doStart();
+        } else {
+            Log.i(TAG, "no focus");
+        }
+    }
+
+    public void start(boolean handleFocus, boolean repeat, PlayStateListener listener) {
+        mListener = listener;
+        start(handleFocus, repeat);
+    }
+
+    private void doStart() {
+        if (mPlaying.getAndSet(true)) {
+            Log.i(TAG, "already playing");
+            return;
+        }
+        Log.i(TAG, "doStart audio");
+        mPlayer = new MediaPlayer();
+        mPlayer.setOnErrorListener(new MediaPlayer.OnErrorListener() {
+
+            @Override
+            public boolean onError(MediaPlayer mp, int what, int extra) {
+                Log.e(TAG, "audio error what " + what + " extra " + extra);
+                mPlaying.set(false);
+                if (!mRepeat && mHandleFocus) {
+                    mPlayer.stop();
+                    mPlayer.release();
+                    mPlayer = null;
+                    mAudioManager.abandonAudioFocus(mFocusListener);
+                }
+                return false;
+            }
+
+        });
+        mPlayer.setOnCompletionListener(new MediaPlayer.OnCompletionListener() {
+            @Override
+            public void onCompletion(MediaPlayer mp) {
+                mPlaying.set(false);
+                if (!mRepeat && mHandleFocus) {
+                    mPlayer.stop();
+                    mPlayer.release();
+                    mPlayer = null;
+                    mAudioManager.abandonAudioFocus(mFocusListener);
+                    if (mListener != null) {
+                        mListener.onCompletion();
+                    }
+                }
+            }
+        });
+        mPlayer.setAudioAttributes(mAttrib);
+        mPlayer.setLooping(mRepeat);
+        mPlayer.setVolume(1.0f, 1.0f);
+        try {
+            AssetFileDescriptor afd =
+                    mContext.getResources().openRawResourceFd(mResourceId);
+            if (afd == null) {
+                throw new RuntimeException("no res");
+            }
+            mPlayer.setDataSource(afd.getFileDescriptor(), afd.getStartOffset(),
+                    afd.getLength());
+            afd.close();
+            mPlayer.prepare();
+        } catch (IOException e) {
+            throw new RuntimeException(e);
+        }
+        mPlayer.start();
+    }
+
+    public void stop() {
+        doStop();
+        if (mHandleFocus) {
+            mAudioManager.abandonAudioFocus(mFocusListener);
+        }
+    }
+
+    public void release() {
+        if (isPlaying()) {
+            stop();
+        }
+    }
+
+    private void doStop() {
+        if (!mPlaying.getAndSet(false)) {
+            Log.i(TAG, "already stopped");
+            return;
+        }
+        Log.i(TAG, "doStop audio");
+        mPlayer.stop();
+        mPlayer.release();
+        mPlayer = null;
+    }
+
+    private void doPause() {
+        mPlayer.pause();
+    }
+
+    private void doResume() {
+        mPlayer.start();
+    }
+
+    public boolean isPlaying() {
+        return mPlaying.get();
+    }
+}
diff --git a/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/audio/AudioTestFragment.java b/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/audio/AudioTestFragment.java
new file mode 100644
index 0000000..7c15c89
--- /dev/null
+++ b/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/audio/AudioTestFragment.java
@@ -0,0 +1,451 @@
+/*
+ * Copyright (C) 2015 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.google.android.car.kitchensink.audio;
+
+import android.car.Car;
+import android.car.CarAppContextManager;
+import android.car.CarAppContextManager.AppContextChangeListener;
+import android.car.CarNotConnectedException;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.ServiceConnection;
+import android.media.AudioAttributes;
+import android.media.AudioManager;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Looper;
+import android.support.v4.app.Fragment;
+import android.util.Log;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.View.OnClickListener;
+import android.view.ViewGroup;
+import android.widget.Button;
+import android.widget.CompoundButton;
+import android.widget.CompoundButton.OnCheckedChangeListener;
+import android.widget.RadioGroup;
+import android.widget.TextView;
+import android.widget.ToggleButton;
+
+import com.google.android.car.kitchensink.CarEmulator;
+import com.google.android.car.kitchensink.R;
+import com.google.android.car.kitchensink.audio.AudioPlayer.PlayStateListener;
+
+public class AudioTestFragment extends Fragment {
+    private static final String TAG = "AudioTest";
+    private static final boolean DBG = true;
+
+    private AudioManager mAudioManager;
+    private FocusHandler mAudioFocusHandler;
+    private Button mNavPlayOnce;
+    private Button mVrPlayOnce;
+    private Button mSystemPlayOnce;
+    private Button mMediaPlay;
+    private Button mMediaPlayOnce;
+    private Button mMediaStop;
+    private Button mNavFocusStart;
+    private Button mNavFocusEnd;
+    private Button mVrFocusStart;
+    private Button mVrFocusEnd;
+    private Button mSpeakerPhoneOn;
+    private Button mSpeakerPhoneOff;
+    private Button mMicrophoneOn;
+    private Button mMicrophoneOff;
+    private ToggleButton mEnableMocking;
+    private ToggleButton mRejectFocus;
+
+    private final AudioPlayer mMusicPlayer;
+    private final AudioPlayer mMusicPlayerShort;
+    private final AudioPlayer mNavGuidancePlayer;
+    private final AudioPlayer mVrPlayer;
+    private final AudioPlayer mSystemPlayer;
+    private final AudioPlayer[] mAllPlayers;
+
+    private final Handler mHandler;
+    private final Context mContext;
+
+    private final Car mCar;
+    private CarAppContextManager mAppContextManager;
+    private CarEmulator mCarEmulator;
+
+    public AudioTestFragment(Context context) {
+        mContext = context;
+        mMusicPlayer = new AudioPlayer(mContext, R.raw.john_harrison_with_the_wichita_state_university_chamber_players_05_summer_mvt_2_adagio,
+                (new AudioAttributes.Builder()).
+                    setUsage(AudioAttributes.USAGE_MEDIA).
+                    setContentType(AudioAttributes.CONTENT_TYPE_MUSIC).build());
+        mMusicPlayerShort = new AudioPlayer(mContext, R.raw.ring_classic_01,
+                (new AudioAttributes.Builder()).
+                setUsage(AudioAttributes.USAGE_MEDIA).
+                setContentType(AudioAttributes.CONTENT_TYPE_MUSIC).build());
+        mNavGuidancePlayer = new AudioPlayer(mContext, R.raw.turnright,
+                (new AudioAttributes.Builder()).
+                    setUsage(AudioAttributes.USAGE_ASSISTANCE_NAVIGATION_GUIDANCE).
+                    setContentType(AudioAttributes.CONTENT_TYPE_SPEECH).build());
+        // no Usage for voice command yet.
+        mVrPlayer = new AudioPlayer(mContext, R.raw.one2six,
+                (new AudioAttributes.Builder()).
+                setUsage(AudioAttributes.USAGE_ASSISTANCE_SONIFICATION).
+                setContentType(AudioAttributes.CONTENT_TYPE_SPEECH).build());
+        mSystemPlayer = new AudioPlayer(mContext, R.raw.ring_classic_01,
+                (new AudioAttributes.Builder()).
+                setUsage(AudioAttributes.USAGE_ASSISTANCE_SONIFICATION).
+                setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION).build());
+        mAllPlayers = new AudioPlayer[] {
+                mMusicPlayer,
+                mMusicPlayerShort,
+                mNavGuidancePlayer,
+                mVrPlayer,
+                mSystemPlayer
+        };
+        mHandler = new Handler(Looper.getMainLooper());
+        mCar = Car.createCar(mContext, new ServiceConnection() {
+            @Override
+            public void onServiceConnected(ComponentName name, IBinder service) {
+                mAppContextManager =
+                        (CarAppContextManager) mCar.getCarManager(Car.APP_CONTEXT_SERVICE);
+                mAppContextManager.registerContextListener(new AppContextChangeListener() {
+
+                    @Override
+                    public void onAppContextOwnershipLoss(int context) {
+                    }
+
+                    @Override
+                    public void onAppContextChange(int activeContexts) {
+                    }
+                }, CarAppContextManager.APP_CONTEXT_NAVIGATION |
+                CarAppContextManager.APP_CONTEXT_VOICE_COMMAND);
+            }
+            @Override
+            public void onServiceDisconnected(ComponentName name) {
+            }
+        }, Looper.getMainLooper());
+        mCar.connect();
+    }
+
+    @Override
+    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle bundle) {
+        Log.i(TAG, "onCreateView");
+        View view = inflater.inflate(R.layout.audio, container, false);
+        mAudioManager = (AudioManager) mContext.getSystemService(
+                Context.AUDIO_SERVICE);
+        mAudioFocusHandler = new FocusHandler(
+                (RadioGroup) view.findViewById(R.id.button_focus_request_selection),
+                (Button) view.findViewById(R.id.button_audio_focus_request),
+                (TextView) view.findViewById(R.id.text_audio_focus_state));
+        mMediaPlay = (Button) view.findViewById(R.id.button_media_play_start);
+        mMediaPlay.setOnClickListener(new OnClickListener() {
+            @Override
+            public void onClick(View v) {
+                mMusicPlayer.start(false, true);
+            }
+        });
+        mMediaPlayOnce = (Button) view.findViewById(R.id.button_media_play_once);
+        mMediaPlayOnce.setOnClickListener(new OnClickListener() {
+            @Override
+            public void onClick(View v) {
+                mMusicPlayerShort.start(true, false);
+                // play only for 1 sec and stop
+                mHandler.postDelayed(new Runnable() {
+                    @Override
+                    public void run() {
+                        mMusicPlayerShort.stop();
+                    }
+                }, 1000);
+            }
+        });
+        mMediaStop = (Button) view.findViewById(R.id.button_media_play_stop);
+        mMediaStop.setOnClickListener(new OnClickListener() {
+            @Override
+            public void onClick(View v) {
+                mMusicPlayer.stop();
+            }
+        });
+        mNavPlayOnce = (Button) view.findViewById(R.id.button_nav_play_once);
+        mNavPlayOnce.setOnClickListener(new OnClickListener() {
+            @Override
+            public void onClick(View v) {
+                if (mAppContextManager == null) {
+                    return;
+                }
+                if (DBG) {
+                    Log.i(TAG, "Nav start");
+                }
+                if (!mNavGuidancePlayer.isPlaying()) {
+                    mAppContextManager.setActiveContexts(
+                            CarAppContextManager.APP_CONTEXT_NAVIGATION);
+                    mNavGuidancePlayer.start(true, false, new PlayStateListener() {
+                        @Override
+                        public void onCompletion() {
+                            mAppContextManager.resetActiveContexts(
+                                    CarAppContextManager.APP_CONTEXT_NAVIGATION);
+                        }
+                    });
+                }
+            }
+        });
+        mVrPlayOnce = (Button) view.findViewById(R.id.button_vr_play_once);
+        mVrPlayOnce.setOnClickListener(new OnClickListener() {
+            @Override
+            public void onClick(View v) {
+                if (mAppContextManager == null) {
+                    return;
+                }
+                if (DBG) {
+                    Log.i(TAG, "VR start");
+                }
+                mAppContextManager.setActiveContexts(
+                        CarAppContextManager.APP_CONTEXT_VOICE_COMMAND);
+                if (!mVrPlayer.isPlaying()) {
+                    mVrPlayer.start(true, false, new PlayStateListener() {
+                        @Override
+                        public void onCompletion() {
+                            mAppContextManager.resetActiveContexts(
+                                    CarAppContextManager.APP_CONTEXT_VOICE_COMMAND);
+                        }
+                    });
+                }
+            }
+        });
+        mSystemPlayOnce = (Button) view.findViewById(R.id.button_system_play_once);
+        mSystemPlayOnce.setOnClickListener(new OnClickListener() {
+            @Override
+            public void onClick(View v) {
+                if (DBG) {
+                    Log.i(TAG, "System start");
+                }
+                if (!mSystemPlayer.isPlaying()) {
+                    // system sound played without focus
+                    mSystemPlayer.start(false, false);
+                }
+            }
+        });
+        mNavFocusStart = (Button) view.findViewById(R.id.button_nav_start);
+        mNavFocusStart.setOnClickListener(new OnClickListener() {
+            @Override
+            public void onClick(View v) {
+                if (mAppContextManager == null) {
+                    return;
+                }
+                if (DBG) {
+                    Log.i(TAG, "Nav focus request");
+                }
+                mAppContextManager.setActiveContexts(
+                        CarAppContextManager.APP_CONTEXT_NAVIGATION);
+            }
+        });
+        mNavFocusEnd = (Button) view.findViewById(R.id.button_nav_end);
+        mNavFocusEnd.setOnClickListener(new OnClickListener() {
+            @Override
+            public void onClick(View v) {
+                if (mAppContextManager == null) {
+                    return;
+                }
+                if (DBG) {
+                    Log.i(TAG, "Nav focus release");
+                }
+                mAppContextManager.resetActiveContexts(
+                        CarAppContextManager.APP_CONTEXT_NAVIGATION);
+            }
+        });
+        mVrFocusStart = (Button) view.findViewById(R.id.button_vr_start);
+        mVrFocusStart.setOnClickListener(new OnClickListener() {
+            @Override
+            public void onClick(View v) {
+                if (mAppContextManager == null) {
+                    return;
+                }
+                if (DBG) {
+                    Log.i(TAG, "VR request");
+                }
+                mAppContextManager.setActiveContexts(
+                        CarAppContextManager.APP_CONTEXT_VOICE_COMMAND);
+            }
+        });
+        mVrFocusEnd = (Button) view.findViewById(R.id.button_vr_end);
+        mVrFocusEnd.setOnClickListener(new OnClickListener() {
+            @Override
+            public void onClick(View v) {
+                if (mAppContextManager == null) {
+                    return;
+                }
+                if (DBG) {
+                    Log.i(TAG, "VR request");
+                }
+                mAppContextManager.resetActiveContexts(
+                        CarAppContextManager.APP_CONTEXT_VOICE_COMMAND);
+            }
+        });
+        mSpeakerPhoneOn = (Button) view.findViewById(R.id.button_speaker_phone_on);
+        mSpeakerPhoneOn.setOnClickListener(new OnClickListener() {
+            @Override
+            public void onClick(View v) {
+                mAudioManager.setSpeakerphoneOn(true);
+            }
+        });
+        mSpeakerPhoneOff = (Button) view.findViewById(R.id.button_speaker_phone_off);
+        mSpeakerPhoneOff.setOnClickListener(new OnClickListener() {
+            @Override
+            public void onClick(View v) {
+                mAudioManager.setSpeakerphoneOn(false);
+            }
+        });
+        mMicrophoneOn = (Button) view.findViewById(R.id.button_microphone_on);
+        mMicrophoneOn.setOnClickListener(new OnClickListener() {
+            @Override
+            public void onClick(View v) {
+                mAudioManager.setMicrophoneMute(false); // Turn the microphone on.
+            }
+        });
+        mMicrophoneOff = (Button) view.findViewById(R.id.button_microphone_off);
+        mMicrophoneOff.setOnClickListener(new OnClickListener() {
+            @Override
+            public void onClick(View v) {
+                mAudioManager.setMicrophoneMute(true); // Mute the microphone.
+            }
+        });
+
+
+        mRejectFocus = (ToggleButton) view.findViewById(R.id.button_reject_audio_focus);
+        mRejectFocus.setOnCheckedChangeListener(new OnCheckedChangeListener() {
+            @Override
+            public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
+                if (mCarEmulator == null) {
+                    return;
+                }
+                if (!mEnableMocking.isChecked()) {
+                    return;
+                }
+                if (isChecked) {
+                    mCarEmulator.setAudioFocusControl(true);
+                } else {
+                    mCarEmulator.setAudioFocusControl(false);
+                }
+            }
+        });
+        mRejectFocus.setActivated(false);
+        mEnableMocking = (ToggleButton) view.findViewById(R.id.button_mock_audio);
+        mEnableMocking.setOnCheckedChangeListener(new OnCheckedChangeListener() {
+            @Override
+            public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
+                if (mCarEmulator == null) {
+                    mCarEmulator = new CarEmulator(mCar);
+                }
+                if (isChecked) {
+                    mRejectFocus.setActivated(true);
+                    mCarEmulator.start();
+                } else {
+                    mRejectFocus.setActivated(false);
+                    mCarEmulator.stop();
+                    mCarEmulator = null;
+                }
+            }
+        });
+        return view;
+    }
+
+    @Override
+    public void onDestroyView() {
+        Log.i(TAG, "onDestroyView");
+        super.onDestroyView();
+        if (mCarEmulator != null) {
+            mCarEmulator.setAudioFocusControl(false);
+            mCarEmulator.stop();
+        }
+        for (AudioPlayer p : mAllPlayers) {
+            p.stop();
+        }
+        if (mAudioFocusHandler != null) {
+            mAudioFocusHandler.release();
+            mAudioFocusHandler = null;
+        }
+        if (mAppContextManager != null) {
+            mAppContextManager.resetActiveContexts(CarAppContextManager.APP_CONTEXT_NAVIGATION |
+                    CarAppContextManager.APP_CONTEXT_VOICE_COMMAND);
+        }
+    }
+
+    private class FocusHandler {
+        private static final String AUDIO_FOCUS_STATE_GAIN = "gain";
+        private static final String AUDIO_FOCUS_STATE_RELEASED_UNKNOWN = "released / unknown";
+
+        private final RadioGroup mRequestSelection;
+        private final TextView mText;
+        private final AudioFocusListener mFocusListener;
+
+        public FocusHandler(RadioGroup radioGroup, Button requestButton, TextView text) {
+            mText = text;
+            mRequestSelection = radioGroup;
+            mRequestSelection.check(R.id.focus_gain);
+            setFocusText(AUDIO_FOCUS_STATE_RELEASED_UNKNOWN);
+            mFocusListener = new AudioFocusListener();
+            requestButton.setOnClickListener(new OnClickListener() {
+                @Override
+                public void onClick(View v) {
+                    int selectedButtonId = mRequestSelection.getCheckedRadioButtonId();
+                    int focusRequest = AudioManager.AUDIOFOCUS_GAIN;
+                    if (selectedButtonId == R.id.focus_gain_transient_duck) {
+                        focusRequest = AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK;
+                    } else if (selectedButtonId == R.id.focus_release) {
+                        mAudioManager.abandonAudioFocus(mFocusListener);
+                        setFocusText(AUDIO_FOCUS_STATE_RELEASED_UNKNOWN);
+                        return;
+                    }
+                    int ret = mAudioManager.requestAudioFocus(mFocusListener,
+                            AudioManager.STREAM_MUSIC, focusRequest);
+                    Log.i(TAG, "requestAudioFocus returned " + ret);
+                    if (ret == AudioManager.AUDIOFOCUS_REQUEST_GRANTED) {
+                        setFocusText(AUDIO_FOCUS_STATE_GAIN);
+                    }
+                }
+            });
+        }
+
+        public void release() {
+            abandonAudioFocus();
+        }
+
+        private void abandonAudioFocus() {
+            if (DBG) {
+                Log.i(TAG, "abandonAudioFocus");
+            }
+            mAudioManager.abandonAudioFocus(mFocusListener);
+            setFocusText(AUDIO_FOCUS_STATE_RELEASED_UNKNOWN);
+        }
+
+        private void setFocusText(String msg) {
+            mText.setText("focus state:" + msg);
+        }
+
+        private class AudioFocusListener implements AudioManager.OnAudioFocusChangeListener {
+            @Override
+            public void onAudioFocusChange(int focusChange) {
+                Log.i(TAG, "onAudioFocusChange " + focusChange);
+                if (focusChange == AudioManager.AUDIOFOCUS_GAIN) {
+                    setFocusText(AUDIO_FOCUS_STATE_GAIN);
+                } else if (focusChange == AudioManager.AUDIOFOCUS_LOSS) {
+                    setFocusText("loss");
+                } else if (focusChange == AudioManager.AUDIOFOCUS_LOSS_TRANSIENT) {
+                    setFocusText("loss,transient");
+                } else if (focusChange == AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK) {
+                    setFocusText("loss,transient,duck");
+                }
+            }
+        }
+    }
+}
diff --git a/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/camera/CameraTestFragment.java b/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/camera/CameraTestFragment.java
new file mode 100644
index 0000000..8d68cf4
--- /dev/null
+++ b/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/camera/CameraTestFragment.java
@@ -0,0 +1,170 @@
+/*
+ * Copyright (C) 2016 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.google.android.car.kitchensink.camera;
+
+import android.os.Bundle;
+import android.car.CarNotConnectedException;
+import android.car.hardware.camera.CarCamera;
+import android.car.hardware.camera.CarCameraState;
+import android.car.hardware.camera.CarCameraManager;
+import android.graphics.Rect;
+import android.support.v4.app.Fragment;
+import android.util.Log;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.Button;
+import android.widget.RadioButton;
+import android.widget.RadioGroup;
+import android.widget.TextView;
+import android.widget.ToggleButton;
+
+import com.google.android.car.kitchensink.R;
+
+import java.lang.Override;
+
+public class CameraTestFragment extends Fragment {
+    private final boolean DBG = true;
+    private final String TAG = "CameraTestFragment";
+    private TextView mTvCap;
+    private TextView mTvRvcCrop;
+    private TextView mTvRvcPos;
+    private TextView mTvCameraState;
+    private CarCameraManager mCarCameraManager;
+    private CarCamera mRvcCamera;
+
+    @Override
+    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstance) {
+        View v = inflater.inflate(R.layout.camera_test, container, false);
+
+        int[] cameraList = mCarCameraManager.getCameraList();
+        for (int camera : cameraList) {
+            if (camera == CarCameraManager.CAR_CAMERA_TYPE_RVC) {
+                mRvcCamera = mCarCameraManager.openCamera(1);
+                break;
+            }
+        }
+
+        mTvCap = (TextView)v.findViewById(R.id.tvCap);
+        mTvRvcCrop = (TextView)v.findViewById(R.id.tvRvcCrop);
+        mTvRvcPos = (TextView)v.findViewById(R.id.tvRvcPos);
+        mTvCameraState = (TextView)v.findViewById(R.id.tvCameraState);
+
+        Button btn = (Button) v.findViewById(R.id.btnGetCap);
+        btn.setOnClickListener(new View.OnClickListener() {
+            public void onClick(View v) {
+                int cap = mRvcCamera.getCapabilities();
+                mTvCap.setText(String.valueOf(cap));
+            }
+        });
+
+        btn = (Button) v.findViewById(R.id.btnGetRvcCrop);
+        btn.setOnClickListener(new View.OnClickListener() {
+            public void onClick(View v) {
+                Rect rect = mRvcCamera.getCameraCrop();
+                if(rect != null) {
+                    mTvRvcCrop.setText(rect.toString());
+                } else {
+                    mTvRvcCrop.setText("null");
+                }
+            }
+        });
+
+        btn = (Button) v.findViewById(R.id.btnGetRvcPos);
+        btn.setOnClickListener(new View.OnClickListener() {
+            public void onClick(View v) {
+                Rect rect = mRvcCamera.getCameraPosition();
+                if(rect != null) {
+                    mTvRvcPos.setText(String.valueOf(rect));
+                } else {
+                    mTvRvcPos.setText("null");
+                }
+            }
+        });
+
+        btn = (Button) v.findViewById(R.id.btnGetCameraState);
+        btn.setOnClickListener(new View.OnClickListener() {
+            public void onClick(View v) {
+                CarCameraState state = mRvcCamera.getCameraState();
+                if(state != null) {
+                    mTvCameraState.setText(state.toString());
+                } else {
+                    mTvCameraState.setText("null");
+                }
+            }
+        });
+
+        btn = (Button) v.findViewById(R.id.btnSetRvcCrop);
+        btn.setOnClickListener(new View.OnClickListener() {
+            public void onClick(View v) {
+                Rect rect = new Rect(160, 240, 560, 480);
+                mRvcCamera.setCameraCrop(rect);
+            }
+        });
+
+        btn = (Button) v.findViewById(R.id.btnSetRvcCrop2);
+        btn.setOnClickListener(new View.OnClickListener() {
+            public void onClick(View v) {
+                Rect rect = new Rect(0, 0, 720, 480);
+                mRvcCamera.setCameraCrop(rect);
+            }
+        });
+
+        btn = (Button) v.findViewById(R.id.btnSetRvcPos);
+        btn.setOnClickListener(new View.OnClickListener() {
+            public void onClick(View v) {
+                Rect rect = new Rect(300, 0, 800, 480);
+                mRvcCamera.setCameraPosition(rect);
+            }
+        });
+
+        btn = (Button) v.findViewById(R.id.btnSetRvcPos2);
+        btn.setOnClickListener(new View.OnClickListener() {
+            public void onClick(View v) {
+                Rect rect = new Rect(500, 0, 800, 480);
+                mRvcCamera.setCameraPosition(rect);
+            }
+        });
+
+        btn = (Button) v.findViewById(R.id.btnSetRvcPos3);
+        btn.setOnClickListener(new View.OnClickListener() {
+            public void onClick(View v) {
+                Rect rect = new Rect(300, 0, 500, 300);
+                mRvcCamera.setCameraPosition(rect);
+            }
+        });
+
+        final ToggleButton toggleBtn = (ToggleButton) v.findViewById(R.id.btnRvcState);
+        toggleBtn.setOnClickListener(new View.OnClickListener() {
+            public void onClick(View v) {
+                CarCameraState state = new CarCameraState(false, toggleBtn.isChecked());
+                mRvcCamera.setCameraState(state);
+            }
+        });
+
+        if(DBG) {
+            Log.d(TAG, "Starting CameraTestFragment");
+        }
+        return v;
+    }
+
+    public void setCameraManager(CarCameraManager cameraManager) {
+        Log.d(TAG, "setCameraManager()");
+        mCarCameraManager = cameraManager;
+    }
+}
+
diff --git a/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/cluster/InstrumentClusterFragment.java b/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/cluster/InstrumentClusterFragment.java
new file mode 100644
index 0000000..f3bc327
--- /dev/null
+++ b/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/cluster/InstrumentClusterFragment.java
@@ -0,0 +1,115 @@
+/*
+ * Copyright (C) 2016 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.google.android.car.kitchensink.cluster;
+
+import com.google.android.car.kitchensink.R;
+
+import android.app.AlertDialog;
+import android.os.Bundle;
+import android.support.annotation.Nullable;
+import android.support.car.CarAppContextManager;
+import android.support.car.CarAppContextManager.AppContextChangeListener;
+import android.support.car.CarNotConnectedException;
+import android.support.car.navigation.CarNavigationManager;
+import android.support.v4.app.Fragment;
+import android.util.Log;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.View.OnClickListener;
+import android.view.ViewGroup;
+
+/**
+ * Contains functions to test instrument cluster API.
+ */
+public class InstrumentClusterFragment extends Fragment {
+    private static final String TAG = InstrumentClusterFragment.class.getSimpleName();
+
+    private CarNavigationManager mCarNavigationManager;
+    private CarAppContextManager mCarAppContextManager;
+
+    public void setCarNavigationManager(CarNavigationManager carNavigationManager) {
+        mCarNavigationManager = carNavigationManager;
+    }
+
+    public void setCarAppContextManager(CarAppContextManager carAppContextManager) {
+        mCarAppContextManager = carAppContextManager;
+    }
+
+    @Nullable
+    @Override
+    public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container,
+            @Nullable Bundle savedInstanceState) {
+        View view = inflater.inflate(R.layout.instrument_cluster, container);
+
+        view.findViewById(R.id.cluster_start_button).setOnClickListener(new OnClickListener() {
+            @Override
+            public void onClick(View v) {
+                initCluster();
+            }
+        });
+        view.findViewById(R.id.cluster_turn_left_button).setOnClickListener(new OnClickListener() {
+            @Override
+            public void onClick(View v) {
+                turnLeft();
+            }
+        });
+
+        return super.onCreateView(inflater, container, savedInstanceState);
+    }
+
+    private void turnLeft() {
+        try {
+            mCarNavigationManager.sendNavigationTurnEvent(CarNavigationManager.TURN_TURN,
+                    "Huff Ave", 90, -1, null, CarNavigationManager.TURN_SIDE_LEFT);
+            mCarNavigationManager.sendNavigationTurnDistanceEvent(500, 10);
+        } catch (CarNotConnectedException e) {
+            e.printStackTrace();
+        }
+    }
+
+    private void initCluster() {
+        mCarAppContextManager.registerContextListener(new AppContextChangeListener() {
+            @Override
+            public void onAppContextChange(int activeContexts) {
+                Log.d(TAG, "onAppContextChange, activeContexts: " + activeContexts);
+            }
+
+            @Override
+            public void onAppContextOwnershipLoss(int context) {
+                Log.w(TAG, "onAppContextOwnershipLoss, context: " + context);
+
+                new AlertDialog.Builder(getContext())
+                        .setTitle(getContext().getApplicationInfo().name)
+                        .setMessage(R.string.cluster_nav_app_context_loss)
+                        .show();
+            }
+        }, CarAppContextManager.APP_CONTEXT_NAVIGATION);
+
+        mCarAppContextManager.setActiveContexts(CarAppContextManager.APP_CONTEXT_NAVIGATION);
+        boolean ownsContext =
+                mCarAppContextManager.isOwningContext(CarAppContextManager.APP_CONTEXT_NAVIGATION);
+        Log.d(TAG, "Owns APP_CONTEXT_NAVIGATION: " + ownsContext);
+        if (!ownsContext) {
+            throw new RuntimeException("Context was not acquired.");
+        }
+
+        try {
+            mCarNavigationManager.sendNavigationStatus(CarNavigationManager.STATUS_ACTIVE);
+        } catch (CarNotConnectedException e) {
+            e.printStackTrace();
+        }
+    }
+}
diff --git a/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/hvac/HvacTestFragment.java b/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/hvac/HvacTestFragment.java
new file mode 100644
index 0000000..c22cd6b
--- /dev/null
+++ b/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/hvac/HvacTestFragment.java
@@ -0,0 +1,387 @@
+/*
+ * Copyright (C) 2015 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.google.android.car.kitchensink.hvac;
+
+import android.car.CarNotConnectedException;
+import android.car.hardware.hvac.CarHvacEvent;
+import android.car.hardware.hvac.CarHvacManager.CarHvacBaseProperty;
+import android.car.hardware.hvac.CarHvacManager.CarHvacBooleanValue;
+import android.car.hardware.hvac.CarHvacManager.CarHvacFloatProperty;
+import android.car.hardware.hvac.CarHvacManager.CarHvacFloatValue;
+import android.car.hardware.hvac.CarHvacManager.CarHvacIntProperty;
+import android.car.hardware.hvac.CarHvacManager.CarHvacIntValue;
+import android.car.hardware.hvac.CarHvacManager;
+import android.os.Bundle;
+import android.support.v4.app.Fragment;
+import android.util.Log;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.Button;
+import android.widget.RadioButton;
+import android.widget.RadioGroup;
+import android.widget.TextView;
+import android.widget.ToggleButton;
+
+import com.android.car.vehiclenetwork.VehicleNetworkConsts.VehicleHvacFanDirection;
+import com.android.car.vehiclenetwork.VehicleNetworkConsts.VehicleWindow;
+import com.android.car.vehiclenetwork.VehicleNetworkConsts.VehicleZone;
+
+import com.google.android.car.kitchensink.R;
+
+import java.lang.Override;
+import java.util.List;
+
+public class HvacTestFragment extends Fragment {
+    private final boolean DBG = true;
+    private final String TAG = "HvacTestFragment";
+    private RadioButton mRbFanPositionFace;
+    private RadioButton mRbFanPositionFloor;
+    private RadioButton mRbFanPositionFaceAndFloor;
+    private ToggleButton mTbAc;
+    private ToggleButton mTbDefrostFront;
+    private ToggleButton mTbDefrostRear;
+    private TextView mTvFanSpeed;
+    private TextView mTvDTemp;
+    private TextView mTvPTemp;
+    private int mCurFanSpeed = 1;
+    private float mCurDTemp = 23;
+    private float mCurPTemp = 23;
+    private CarHvacManager mCarHvacManager;
+
+    private final CarHvacManager.CarHvacEventListener mHvacListener =
+            new CarHvacManager.CarHvacEventListener () {
+                @Override
+                public void onChangeEvent(final CarHvacManager.CarHvacBaseProperty value) {
+                    int zone = value.getZone();
+                    switch(value.getPropertyId()) {
+                        case CarHvacManager.HVAC_ZONED_AC_ON:
+                            mTbAc.setChecked(((CarHvacBooleanValue)value).getValue());
+                            break;
+                        case CarHvacManager.HVAC_ZONED_FAN_POSITION:
+                            switch(((CarHvacIntValue)value).getValue()) {
+                                case VehicleHvacFanDirection.VEHICLE_HVAC_FAN_DIRECTION_FACE:
+                                    mRbFanPositionFace.setChecked(true);
+                                    break;
+                                case VehicleHvacFanDirection.VEHICLE_HVAC_FAN_DIRECTION_FLOOR:
+                                    mRbFanPositionFloor.setChecked(true);
+                                    break;
+                                case VehicleHvacFanDirection.
+                                        VEHICLE_HVAC_FAN_DIRECTION_FACE_AND_FLOOR:
+                                    mRbFanPositionFaceAndFloor.setChecked(true);
+                                    break;
+                                default:
+                                    Log.e(TAG, "Unknown fan position: " +
+                                            ((CarHvacIntValue)value).getValue());
+                                    break;
+                            }
+                            break;
+                        case CarHvacManager.HVAC_ZONED_FAN_SPEED_SETPOINT:
+                            mCurFanSpeed = ((CarHvacIntValue)value).getValue();
+                            mTvFanSpeed.setText(String.valueOf(mCurFanSpeed));
+                            break;
+                        case CarHvacManager.HVAC_ZONED_TEMP_SETPOINT:
+                            switch(zone) {
+                                case VehicleZone.VEHICLE_ZONE_ROW_1_LEFT:
+                                    mCurDTemp = ((CarHvacFloatValue)value).getValue();
+                                    mTvDTemp.setText(String.valueOf(mCurDTemp));
+                                    break;
+                                case VehicleZone.VEHICLE_ZONE_ROW_1_RIGHT:
+                                    mCurPTemp = ((CarHvacFloatValue)value).getValue();
+                                    mTvPTemp.setText(String.valueOf(mCurPTemp));
+                                    break;
+                                default:
+                                    Log.w(TAG, "Unknown zone = " + zone);
+                                    break;
+                            }
+                            break;
+                        case CarHvacManager.HVAC_WINDOW_DEFROSTER_ON:
+                            if((zone & VehicleWindow.VEHICLE_WINDOW_FRONT_WINDSHIELD) ==
+                                    VehicleWindow.VEHICLE_WINDOW_FRONT_WINDSHIELD) {
+                                mTbDefrostFront.setChecked(((CarHvacBooleanValue)value).getValue());
+                            }
+                            if((zone & VehicleWindow.VEHICLE_WINDOW_REAR_WINDSHIELD) ==
+                                    VehicleWindow.VEHICLE_WINDOW_REAR_WINDSHIELD) {
+                                mTbDefrostRear.setChecked(((CarHvacBooleanValue)value).getValue());
+                            }
+                            break;
+                        default:
+                            Log.d(TAG, "onChangeEvent(): unknown property id = " + value
+                                    .getPropertyId());
+                    }
+                }
+
+                @Override
+                public void onErrorEvent(final int propertyId, final int zone) {
+                    Log.d(TAG, "Error:  propertyId=" + propertyId + "  zone=" + zone);
+                }
+            };
+
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        try {
+            mCarHvacManager.registerListener(mHvacListener);
+        } catch (CarNotConnectedException e) {
+            Log.e(TAG, "Car is not connected!");
+        }
+    }
+
+    @Override
+    public void onDestroy() {
+        super.onDestroy();
+        mCarHvacManager.unregisterListener();
+    }
+
+    @Override
+    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstance) {
+        View v = inflater.inflate(R.layout.hvac_test, container, false);
+
+        List<CarHvacBaseProperty> props = mCarHvacManager.getPropertyList();
+
+        for(CarHvacBaseProperty prop : props) {
+            int propId = prop.getPropertyId();
+            int type = prop.getType();
+
+            if(DBG) {
+                switch(type) {
+                    case CarHvacManager.PROPERTY_TYPE_BOOLEAN:
+                        Log.d(TAG, "propertyId = " + propId + " type = " + type +
+                                " zone = " + prop.getZone());
+                        break;
+                    case CarHvacManager.PROPERTY_TYPE_FLOAT:
+                        CarHvacFloatProperty f = (CarHvacFloatProperty)prop;
+                        Log.d(TAG, "propertyId = " + propId + " type = " + type +
+                                " zone = " + f.getZone() + " min = " + f.getMinValue() +
+                                " max = " + f.getMaxValue());
+                        break;
+                    case CarHvacManager.PROPERTY_TYPE_INT:
+                        CarHvacIntProperty i = (CarHvacIntProperty)prop;
+                        Log.d(TAG, "propertyId = " + propId + " type = " + type +
+                                " zone = " + i.getZone() + " min = " + i.getMinValue() +
+                                " max = " + i.getMaxValue());
+                        break;
+                }
+            }
+
+            switch(propId) {
+                case CarHvacManager.HVAC_ZONED_AC_ON:
+                    configureAcOn(v);
+                    break;
+                case CarHvacManager.HVAC_ZONED_FAN_POSITION:
+                    configureFanPosition(v);
+                    break;
+                case CarHvacManager.HVAC_ZONED_FAN_SPEED_SETPOINT:
+                    configureFanSpeed(v);
+                    break;
+                case CarHvacManager.HVAC_ZONED_TEMP_SETPOINT:
+                    configureTempSetpoint(v);
+                    break;
+                case CarHvacManager.HVAC_WINDOW_DEFROSTER_ON:
+                    configureDefrosterOn(v, prop.getZone());
+                    break;
+                default:
+                    Log.w(TAG, "propertyId " + propId + " is not handled");
+                    break;
+            }
+        }
+
+        mTvFanSpeed = (TextView) v.findViewById(R.id.tvFanSpeed);
+        mTvFanSpeed.setText(String.valueOf(mCurFanSpeed));
+        mTvDTemp = (TextView) v.findViewById(R.id.tvDTemp);
+        mTvDTemp.setText(String.valueOf(mCurDTemp));
+        mTvPTemp = (TextView) v.findViewById(R.id.tvPTemp);
+        mTvPTemp.setText(String.valueOf(mCurPTemp));
+
+        if(DBG) {
+            Log.d(TAG, "Starting HvacTestFragment");
+        }
+
+        return v;
+    }
+
+    public void setHvacManager(CarHvacManager hvacManager) {
+        Log.d(TAG, "setHvacManager()");
+        mCarHvacManager = hvacManager;
+    }
+
+    private void configureAcOn(View v) {
+        mTbAc = (ToggleButton)v.findViewById(R.id.tbAc);
+        mTbAc.setEnabled(true);
+        mTbAc.setOnClickListener(new View.OnClickListener() {
+            public void onClick(View v) {
+                // TODO handle zone properly
+                mCarHvacManager.setBooleanProperty(CarHvacManager.HVAC_ZONED_AC_ON, 0x8,
+                        mTbAc.isChecked());
+            }
+        });
+    }
+
+    private void configureFanPosition(View v) {
+        RadioGroup rg = (RadioGroup)v.findViewById(R.id.rgFanPosition);
+        rg.setOnCheckedChangeListener(new RadioGroup.OnCheckedChangeListener() {
+            @Override
+            public void onCheckedChanged(RadioGroup group, int checkedId) {
+                switch(checkedId) {
+                    case R.id.rbPositionFace:
+                        mCarHvacManager.setIntProperty(CarHvacManager.HVAC_ZONED_FAN_POSITION,
+                                VehicleZone.VEHICLE_ZONE_ROW_1_ALL,
+                                VehicleHvacFanDirection.VEHICLE_HVAC_FAN_DIRECTION_FACE);
+                        break;
+                    case R.id.rbPositionFloor:
+                        mCarHvacManager.setIntProperty(CarHvacManager.HVAC_ZONED_FAN_POSITION,
+                                VehicleZone.VEHICLE_ZONE_ROW_1_ALL,
+                                VehicleHvacFanDirection.VEHICLE_HVAC_FAN_DIRECTION_FLOOR);
+                        break;
+                    case R.id.rbPositionFaceAndFloor:
+                        mCarHvacManager.setIntProperty(CarHvacManager.HVAC_ZONED_FAN_POSITION,
+                                VehicleZone.VEHICLE_ZONE_ROW_1_ALL, VehicleHvacFanDirection.
+                                        VEHICLE_HVAC_FAN_DIRECTION_FACE_AND_FLOOR);
+                        break;
+                }
+            }
+        });
+
+        mRbFanPositionFace = (RadioButton)v.findViewById(R.id.rbPositionFace);
+        mRbFanPositionFace.setClickable(true);
+        mRbFanPositionFloor = (RadioButton)v.findViewById(R.id.rbPositionFloor);
+        mRbFanPositionFaceAndFloor = (RadioButton)v.findViewById(R.id.rbPositionFaceAndFloor);
+        mRbFanPositionFaceAndFloor.setClickable(true);
+        mRbFanPositionFloor.setClickable(true);
+    }
+
+    private void configureFanSpeed(View v) {
+        mCurFanSpeed = mCarHvacManager.getIntProperty(
+                CarHvacManager.HVAC_ZONED_FAN_SPEED_SETPOINT,
+                VehicleZone.VEHICLE_ZONE_ALL);
+
+        Button btnFanSpeedUp = (Button) v.findViewById(R.id.btnFanSpeedUp);
+        btnFanSpeedUp.setEnabled(true);
+        btnFanSpeedUp.setOnClickListener(new View.OnClickListener() {
+            public void onClick(View v) {
+                if(mCurFanSpeed < 7) {
+                    mCurFanSpeed++;
+                    mTvFanSpeed.setText(String.valueOf(mCurFanSpeed));
+                    mCarHvacManager.setIntProperty(CarHvacManager.HVAC_ZONED_FAN_SPEED_SETPOINT,
+                            VehicleZone.VEHICLE_ZONE_ALL, mCurFanSpeed);
+                }
+            }
+        });
+
+        Button btnFanSpeedDn = (Button) v.findViewById(R.id.btnFanSpeedDn);
+        btnFanSpeedDn.setEnabled(true);
+        btnFanSpeedDn.setOnClickListener(new View.OnClickListener() {
+            public void onClick(View v) {
+                if (mCurFanSpeed > 1) {
+                    mCurFanSpeed--;
+                    mTvFanSpeed.setText(String.valueOf(mCurFanSpeed));
+                    mCarHvacManager.setIntProperty(CarHvacManager.HVAC_ZONED_FAN_SPEED_SETPOINT,
+                            VehicleZone.VEHICLE_ZONE_ALL, mCurFanSpeed);
+                }
+            }
+        });
+    }
+
+    private void configureTempSetpoint(View v) {
+        mCurDTemp = mCarHvacManager.getFloatProperty(
+                CarHvacManager.HVAC_ZONED_TEMP_SETPOINT,
+                VehicleZone.VEHICLE_ZONE_ROW_1_LEFT);
+        mCurPTemp = mCarHvacManager.getFloatProperty(
+                CarHvacManager.HVAC_ZONED_TEMP_SETPOINT,
+                VehicleZone.VEHICLE_ZONE_ROW_1_RIGHT);
+
+        Button btnDTempUp = (Button) v.findViewById(R.id.btnDTempUp);
+        btnDTempUp.setEnabled(true);
+        btnDTempUp.setOnClickListener(new View.OnClickListener() {
+            public void onClick(View v) {
+                if(mCurDTemp < 29.5) {
+                    mCurDTemp += 0.5;
+                    mTvDTemp.setText(String.valueOf(mCurDTemp));
+                    mCarHvacManager.setFloatProperty(CarHvacManager.HVAC_ZONED_TEMP_SETPOINT,
+                            VehicleZone.VEHICLE_ZONE_ROW_1_LEFT, mCurDTemp);
+                }
+            }
+        });
+
+        Button btnDTempDn = (Button) v.findViewById(R.id.btnDTempDn);
+        btnDTempDn.setEnabled(true);
+        btnDTempDn.setOnClickListener(new View.OnClickListener() {
+            public void onClick(View v) {
+                if(mCurDTemp > 15.5) {
+                    mCurDTemp -= 0.5;
+                    mTvDTemp.setText(String.valueOf(mCurDTemp));
+                    mCarHvacManager.setFloatProperty(CarHvacManager.HVAC_ZONED_TEMP_SETPOINT,
+                            VehicleZone.VEHICLE_ZONE_ROW_1_LEFT, mCurDTemp);
+                }
+            }
+        });
+
+        Button btnPTempUp = (Button) v.findViewById(R.id.btnPTempUp);
+        btnPTempUp.setEnabled(true);
+        btnPTempUp.setOnClickListener(new View.OnClickListener() {
+            public void onClick(View v) {
+                if (mCurPTemp < 29.5) {
+                    mCurPTemp += 0.5;
+                    mTvPTemp.setText(String.valueOf(mCurPTemp));
+                    mCarHvacManager.setFloatProperty(CarHvacManager.HVAC_ZONED_TEMP_SETPOINT,
+                            VehicleZone.VEHICLE_ZONE_ROW_1_RIGHT, mCurPTemp);
+                }
+            }
+        });
+
+        Button btnPTempDn = (Button) v.findViewById(R.id.btnPTempDn);
+        btnPTempDn.setEnabled(true);
+        btnPTempDn.setOnClickListener(new View.OnClickListener() {
+            public void onClick(View v) {
+                if (mCurPTemp > 15.5) {
+                    mCurPTemp -= 0.5;
+                    mTvPTemp.setText(String.valueOf(mCurPTemp));
+                    mCarHvacManager.setFloatProperty(CarHvacManager.HVAC_ZONED_TEMP_SETPOINT,
+                            VehicleZone.VEHICLE_ZONE_ROW_1_RIGHT, mCurPTemp);
+                }
+            }
+        });
+    }
+
+    private void configureDefrosterOn(View v, int zone) {
+        if((zone & VehicleWindow.VEHICLE_WINDOW_FRONT_WINDSHIELD) ==
+                VehicleWindow.VEHICLE_WINDOW_FRONT_WINDSHIELD) {
+            mTbDefrostFront = (ToggleButton)v.findViewById(R.id.tbDefrostFront);
+            mTbDefrostFront.setEnabled(true);
+            mTbDefrostFront.setOnClickListener(new View.OnClickListener() {
+                public void onClick(View v) {
+                    mCarHvacManager.setBooleanProperty(CarHvacManager.HVAC_WINDOW_DEFROSTER_ON,
+                            VehicleWindow.VEHICLE_WINDOW_FRONT_WINDSHIELD,
+                            mTbDefrostFront.isChecked());
+                }
+            });
+        }
+        if((zone & VehicleWindow.VEHICLE_WINDOW_REAR_WINDSHIELD) ==
+                VehicleWindow.VEHICLE_WINDOW_REAR_WINDSHIELD) {
+            mTbDefrostRear = (ToggleButton)v.findViewById(R.id.tbDefrostRear);
+            mTbDefrostRear.setEnabled(true);
+            mTbDefrostRear.setOnClickListener(new View.OnClickListener() {
+                public void onClick(View v) {
+                    mCarHvacManager.setBooleanProperty(CarHvacManager.HVAC_WINDOW_DEFROSTER_ON,
+                            VehicleWindow.VEHICLE_WINDOW_REAR_WINDSHIELD,
+                            mTbDefrostRear.isChecked());
+                }
+            });
+        }
+    }
+}
+
diff --git a/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/job/DishService.java b/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/job/DishService.java
new file mode 100644
index 0000000..c2758a8
--- /dev/null
+++ b/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/job/DishService.java
@@ -0,0 +1,130 @@
+/*
+ * Copyright (C) 2015 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.google.android.car.kitchensink.job;
+
+import android.app.job.JobParameters;
+import android.app.job.JobService;
+import android.os.AsyncTask;
+import android.os.Handler;
+import android.os.Message;
+import android.util.Log;
+import android.util.SparseArray;
+
+import java.lang.ref.WeakReference;
+
+public class DishService extends JobService {
+    private static final String TAG = "JobScheduler_DishService";
+    private static final int DELAY_MS = 1000; // wash a plate every second!
+
+    private static final int MSG_FINISHED = 0;
+    private static final int MSG_RUN_JOB = 1;
+    private static final int MSG_CANCEL_JOB = 2;
+
+    public static final String EXTRA_DISH_COUNT = "dish_count";
+
+    private final Handler mHandler = new Handler() {
+        private SparseArray<JobParameters> mTaskMap = new SparseArray<JobParameters>();
+        @Override
+        public void handleMessage(Message msg) {
+            JobParameters job = (JobParameters) msg.obj;
+            switch (msg.what) {
+                case MSG_FINISHED:
+                    Log.d(TAG, "Job done! " + job.getJobId());
+                    mTaskMap.remove(job.getJobId());
+                    jobFinished(job, false);
+                    break;
+                case MSG_RUN_JOB:
+                    DishWasherTask task = new DishWasherTask(this, job, msg.arg1);
+                    task.execute();
+                    mTaskMap.put(job.getJobId(), job);
+                    break;
+                case MSG_CANCEL_JOB:
+                    JobParameters job1 = mTaskMap.get(job.getJobId());
+                    if (job1 != null) {
+                        removeMessages(MSG_RUN_JOB, job1);
+                        Log.d(TAG, "cancelled job " + job1);
+                        mTaskMap.remove(job.getJobId());
+                    }
+                    break;
+                default:
+                    Log.w(TAG, "Unknown message " + msg.what);
+            }
+        }
+    };
+
+    @Override
+    public boolean onStopJob(JobParameters jobParameters) {
+        Log.d(TAG, "onStopJob " + jobParameters);
+        Message msg = mHandler.obtainMessage(MSG_CANCEL_JOB, 0, 0, jobParameters);
+        mHandler.sendMessage(msg);
+        return false;
+    }
+
+    @Override
+    public boolean onStartJob(final JobParameters jobParameters) {
+        Log.d(TAG, "onStartJob " + jobParameters);
+        Message msg = mHandler.obtainMessage(MSG_RUN_JOB, 0, 0, jobParameters);
+        mHandler.sendMessage(msg);
+        return true;
+    }
+
+    private static final class DishWasherTask extends AsyncTask<Void, Void, Boolean> {
+        private final WeakReference<Handler> mHandler;
+        private final JobParameters mJobParameter;
+        private final int mMyDishNum;
+
+
+        public DishWasherTask(Handler handler, JobParameters jobParameters, int dishNum) {
+            mHandler = new WeakReference<Handler>(handler);
+            mJobParameter = jobParameters;
+            mMyDishNum = dishNum;
+        }
+
+        @Override
+        protected Boolean doInBackground(Void... infos) {
+            int dishTotal = mJobParameter.getExtras().getInt(EXTRA_DISH_COUNT);
+
+            Log.d(TAG, "jobId: " + mJobParameter.getJobId() + " totalDish: " + dishTotal
+                    + " washing: #" + mMyDishNum);
+            wash();
+            if (mMyDishNum >= dishTotal - 1) {
+                // all done!
+                return false;
+            }
+            return true;
+        }
+
+        @Override
+        protected void onPostExecute(Boolean result) {
+            if (mHandler.get() == null) {
+                return;
+            }
+            if (result) {
+                Message msg = mHandler.get().obtainMessage(MSG_RUN_JOB,
+                        mMyDishNum +1, 0, mJobParameter);
+                mHandler.get().sendMessageDelayed(msg, DELAY_MS);
+            } else {
+                Message msg = mHandler.get().obtainMessage(MSG_FINISHED, 0,
+                        0, mJobParameter);
+                mHandler.get().sendMessage(msg);
+            }
+        }
+
+        private void wash() {
+            // TODO: add heavy wash tasks here...
+        }
+    }
+}
diff --git a/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/job/JobSchedulerFragment.java b/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/job/JobSchedulerFragment.java
new file mode 100644
index 0000000..3cf96e4
--- /dev/null
+++ b/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/job/JobSchedulerFragment.java
@@ -0,0 +1,165 @@
+/*
+ * Copyright (C) 2015 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.google.android.car.kitchensink.job;
+
+import android.app.job.JobInfo;
+import android.app.job.JobScheduler;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.SharedPreferences;
+import android.os.Bundle;
+import android.os.PersistableBundle;
+import android.support.annotation.Nullable;
+import android.support.v4.app.Fragment;
+import android.util.Log;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.Button;
+import android.widget.CheckBox;
+import android.widget.EditText;
+import android.widget.RadioGroup;
+import android.widget.TextView;
+import android.widget.Toast;
+
+import com.google.android.car.kitchensink.R;
+
+import java.util.List;
+
+public class JobSchedulerFragment extends Fragment {
+    private static final String TAG = "JobSchedulerFragment";
+    private static final String PREFS_NEXT_JOB_ID = "next_job_id";
+
+    private Button mScheduleButton;
+    private Button mRefreshButton;
+    private Button mCancelButton;
+    private CheckBox mRequireCharging;
+    private CheckBox mRequireIdle;
+    private CheckBox mRequirePersisted;
+    private RadioGroup mNetworkGroup;
+    private EditText mDishNum;
+    private TextView mJobInfo;
+    private JobScheduler mJobScheduler;
+
+    @Nullable
+    @Override
+    public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container,
+            @Nullable Bundle savedInstanceState) {
+        View v = inflater.inflate(R.layout.job_scheduler, container, false);
+        mScheduleButton = (Button) v.findViewById(R.id.schedule_button);
+        mScheduleButton.setOnClickListener(new View.OnClickListener() {
+            @Override
+            public void onClick(View view) {
+                scheduleJob();
+            }
+        });
+        mRefreshButton = (Button) v.findViewById(R.id.refresh_button);
+        mRefreshButton.setOnClickListener(new View.OnClickListener() {
+            @Override
+            public void onClick(View view) {
+                refreshCurrentJobs();
+            }
+        });
+
+        mNetworkGroup = (RadioGroup) v.findViewById(R.id.network_group);
+        mDishNum = (EditText) v.findViewById(R.id.dish_num);
+        mRequireCharging = (CheckBox) v.findViewById(R.id.require_charging);
+        mRequireIdle = (CheckBox) v.findViewById(R.id.require_idle);
+        mRequirePersisted = (CheckBox) v.findViewById(R.id.require_persisted);
+        mJobScheduler = (JobScheduler) getContext()
+                .getSystemService(Context.JOB_SCHEDULER_SERVICE);
+
+        mJobInfo = (TextView) v.findViewById(R.id.current_jobs);
+
+        mCancelButton = (Button) v.findViewById(R.id.cancel_button);
+        mCancelButton.setOnClickListener(new View.OnClickListener() {
+            @Override
+            public void onClick(View view) {
+                mJobScheduler.cancelAll();
+                refreshCurrentJobs();
+            }
+        });
+        return v;
+    }
+
+    @Override
+    public void onResume() {
+        super.onResume();
+        refreshCurrentJobs();
+    }
+
+    private void refreshCurrentJobs() {
+        StringBuilder sb = new StringBuilder();
+        List<JobInfo> jobs = mJobScheduler.getAllPendingJobs();
+        for (JobInfo job : jobs) {
+            sb.append("JobId: ");
+            sb.append(job.getId());
+            sb.append("\nDishCount: ");
+            sb.append(job.getExtras().getInt(DishService.EXTRA_DISH_COUNT, 0));
+            sb.append("\n");
+        }
+        mJobInfo.setText(sb.toString());
+    }
+
+    private void scheduleJob() {
+        ComponentName jobComponentName = new ComponentName(getContext(), DishService.class);
+        SharedPreferences prefs = getContext()
+                .getSharedPreferences(PREFS_NEXT_JOB_ID, Context.MODE_PRIVATE);
+        int jobId = prefs.getInt(PREFS_NEXT_JOB_ID, 0);
+        PersistableBundle bundle = new PersistableBundle();
+        int count = 50;
+        try {
+            count = Integer.valueOf(mDishNum.getText().toString());
+        } catch (NumberFormatException e) {
+            Log.e(TAG, "NOT A NUMBER!!!");
+        }
+
+        int selected = mNetworkGroup.getCheckedRadioButtonId();
+        int networkType = JobInfo.NETWORK_TYPE_ANY;
+        switch (selected) {
+            case R.id.network_none:
+                networkType = JobInfo.NETWORK_TYPE_NONE;
+                break;
+            case R.id.network_unmetered:
+                networkType = JobInfo.NETWORK_TYPE_UNMETERED;
+                break;
+            case R.id.network_any:
+                networkType = JobInfo.NETWORK_TYPE_ANY;
+                break;
+        }
+        bundle.putInt(DishService.EXTRA_DISH_COUNT, count);
+        JobInfo jobInfo = new JobInfo.Builder(jobId, jobComponentName)
+                .setRequiresCharging(mRequireCharging.isChecked())
+                .setRequiresDeviceIdle(mRequireIdle.isChecked())
+                // TODO: figure out why we crash here even we hold
+                // the RECEIVE_BOOT_COMPLETE permission
+                //.setPersisted(mRequirePersisted.isChecked())
+                .setExtras(bundle)
+                .setRequiredNetworkType(networkType)
+                .build();
+
+
+        mJobScheduler.schedule(jobInfo);
+        Toast.makeText(getContext(), "Scheduled: " + jobInfo, Toast.LENGTH_LONG ).show();
+
+        Log.d(TAG, "Scheduled a job: " + jobInfo);
+        SharedPreferences.Editor editor = prefs.edit();
+        editor.putInt(PREFS_NEXT_JOB_ID, jobId + 1);
+        editor.commit();
+
+        refreshCurrentJobs();
+    }
+}
diff --git a/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/keyboard/KeyboardFragment.java b/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/keyboard/KeyboardFragment.java
new file mode 100644
index 0000000..4ee0b16
--- /dev/null
+++ b/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/keyboard/KeyboardFragment.java
@@ -0,0 +1,123 @@
+/*
+ * Copyright (C) 2015 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.google.android.car.kitchensink.keyboard;
+
+import android.os.Bundle;
+import android.os.Handler;
+import android.support.annotation.Nullable;
+import android.support.car.app.menu.CarDrawerActivity;
+import android.support.car.app.menu.SearchBoxEditListener;
+import android.support.v4.app.Fragment;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.Button;
+import android.widget.TextView;
+
+import com.google.android.car.kitchensink.R;
+
+public class KeyboardFragment extends Fragment {
+    public static final int CARD = 0xfffafafa;
+    public static final int TEXT_PRIMARY_DAY = 0xde000000;
+    public static final int TEXT_SECONDARY_DAY = 0x8a000000;
+
+    private Button mImeButton;
+    private Button mCloseImeButton;
+    private Button mShowHideInputButton;
+    private CarDrawerActivity mActivity;
+    private TextView mOnSearchText;
+    private TextView mOnEditText;
+
+    private final Handler mHandler = new Handler();
+
+    @Override
+    public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container,
+                             @Nullable Bundle savedInstanceState) {
+        View v = inflater.inflate(R.layout.keyboard_test, container, false);
+        mActivity = (CarDrawerActivity) getHost();
+        mImeButton = (Button) v.findViewById(R.id.ime_button);
+        mImeButton.setOnClickListener(new View.OnClickListener() {
+            @Override
+            public void onClick(View view) {
+                mActivity.startInput("Hint");
+            }
+        });
+
+        mCloseImeButton = (Button) v.findViewById(R.id.stop_ime_button);
+        mCloseImeButton.setOnClickListener(new View.OnClickListener() {
+            @Override
+            public void onClick(View view) {
+                mActivity.stopInput();
+                resetInput();
+            }
+        });
+
+        mShowHideInputButton = (Button) v.findViewById(R.id.ime_button2);
+        mShowHideInputButton.setOnClickListener(new View.OnClickListener() {
+            @Override
+            public void onClick(View view) {
+                if (mActivity.isShowingSearchBox()) {
+                    mActivity.hideSearchBox();
+                } else {
+                    resetInput();
+                }
+            }
+        });
+
+        mOnSearchText = (TextView) v.findViewById(R.id.search_text);
+        mOnEditText = (TextView) v.findViewById(R.id.edit_text);
+        resetInput();
+        mActivity.setSearchBoxEndView(View.inflate(getContext(), R.layout.keyboard_end_view, null));
+
+        return v;
+    }
+
+    private void resetInput() {
+        mActivity.showSearchBox(new View.OnClickListener() {
+            @Override
+            public void onClick(View view) {
+                mActivity.startInput("Hint");
+            }
+        });
+        mActivity.setSearchBoxEditListener(mEditListener);
+        mActivity.setSearchBoxColors(CARD, TEXT_SECONDARY_DAY,
+                TEXT_PRIMARY_DAY, TEXT_SECONDARY_DAY);
+    }
+
+
+    private final SearchBoxEditListener mEditListener = new SearchBoxEditListener() {
+        @Override
+        public void onSearch(final String text) {
+            mHandler.post(new Runnable() {
+                @Override
+                public void run() {
+                    mOnSearchText.setText("Search: " + text);
+                    resetInput();
+                }
+            });
+        }
+
+        @Override
+        public void onEdit(final String text) {
+            mHandler.post(new Runnable() {
+                @Override
+                public void run() {
+                    mOnEditText.setText("Edit: " + text);
+                }
+            });
+        }
+    };
+}