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);
+ }
+ });
+ }
+ };
+}