Move car hvac app.
Rakesh Iyer [Thu, 20 Oct 2016 05:42:36 +0000 (22:42 -0700)]
Original Sha1: 27748caacfca0bb295d32871843e4b206aedf1e8

Credits:
  victorchan@
  rni@

Bug: 32118797
Test: Manual.
Change-Id: I15d4fd5760d1746d4aa21baae58ec4f8b94a81b4

65 files changed:
Android.mk [new file with mode: 0644]
AndroidManifest.xml [new file with mode: 0644]
res/drawable-nodpi/ic_launcher_hvac.xml [new file with mode: 0644]
res/drawable/fan_speed_inset.xml [new file with mode: 0644]
res/drawable/hvac_off_text_bg.xml [new file with mode: 0644]
res/drawable/hvac_panel_button_bg.xml [new file with mode: 0644]
res/drawable/ic_ac_off.xml [new file with mode: 0644]
res/drawable/ic_ac_on.xml [new file with mode: 0644]
res/drawable/ic_auto_off.xml [new file with mode: 0644]
res/drawable/ic_auto_on.xml [new file with mode: 0644]
res/drawable/ic_decrease_icon.xml [new file with mode: 0644]
res/drawable/ic_down_arrow.xml [new file with mode: 0644]
res/drawable/ic_fan_direction_1_off.xml [new file with mode: 0644]
res/drawable/ic_fan_direction_1_on.xml [new file with mode: 0644]
res/drawable/ic_fan_direction_2_off.xml [new file with mode: 0644]
res/drawable/ic_fan_direction_2_on.xml [new file with mode: 0644]
res/drawable/ic_fan_direction_3_off.xml [new file with mode: 0644]
res/drawable/ic_fan_direction_3_on.xml [new file with mode: 0644]
res/drawable/ic_fan_direction_4_off.xml [new file with mode: 0644]
res/drawable/ic_fan_direction_4_on.xml [new file with mode: 0644]
res/drawable/ic_fan_speed_bg.xml [new file with mode: 0644]
res/drawable/ic_front_defroster_off.xml [new file with mode: 0644]
res/drawable/ic_front_defroster_on.xml [new file with mode: 0644]
res/drawable/ic_increase_icon.xml [new file with mode: 0644]
res/drawable/ic_rear_defroster_off.xml [new file with mode: 0644]
res/drawable/ic_rear_defroster_on.xml [new file with mode: 0644]
res/drawable/ic_recycle_air_off.xml [new file with mode: 0644]
res/drawable/ic_recycle_air_on.xml [new file with mode: 0644]
res/drawable/ic_seat_heat_level_1.xml [new file with mode: 0644]
res/drawable/ic_seat_heat_level_2.xml [new file with mode: 0644]
res/drawable/ic_seat_heat_level_3.xml [new file with mode: 0644]
res/drawable/ic_seat_heat_off.xml [new file with mode: 0644]
res/drawable/ripple_background.xml [new file with mode: 0644]
res/drawable/temperature_bar_background.xml [new file with mode: 0644]
res/layout/fan_direction.xml [new file with mode: 0644]
res/layout/fan_speed.xml [new file with mode: 0644]
res/layout/hvac_panel.xml [new file with mode: 0644]
res/layout/hvac_temperature_bar_overlay.xml [new file with mode: 0644]
res/values-w1360dp/dimens.xml [new file with mode: 0644]
res/values-w1360dp/styles.xml [new file with mode: 0644]
res/values/colors.xml [new file with mode: 0644]
res/values/config.xml [new file with mode: 0644]
res/values/dimens.xml [new file with mode: 0644]
res/values/strings.xml [new file with mode: 0644]
res/values/styles.xml [new file with mode: 0644]
res/values/tags.xml [new file with mode: 0644]
src/com/android/car/hvac/BootCompleteReceiver.java [new file with mode: 0644]
src/com/android/car/hvac/DataStore.java [new file with mode: 0644]
src/com/android/car/hvac/HvacController.java [new file with mode: 0644]
src/com/android/car/hvac/HvacPolicy.java [new file with mode: 0644]
src/com/android/car/hvac/HvacUiService.java [new file with mode: 0644]
src/com/android/car/hvac/LocalHvacPropertyService.java [new file with mode: 0644]
src/com/android/car/hvac/controllers/FanDirectionButtonsController.java [new file with mode: 0644]
src/com/android/car/hvac/controllers/FanSpeedBarController.java [new file with mode: 0644]
src/com/android/car/hvac/controllers/HvacPanelController.java [new file with mode: 0644]
src/com/android/car/hvac/controllers/SeatWarmerController.java [new file with mode: 0644]
src/com/android/car/hvac/controllers/TemperatureController.java [new file with mode: 0644]
src/com/android/car/hvac/ui/FanDirectionButtons.java [new file with mode: 0644]
src/com/android/car/hvac/ui/FanSpeedBar.java [new file with mode: 0644]
src/com/android/car/hvac/ui/FanSpeedBarSegment.java [new file with mode: 0644]
src/com/android/car/hvac/ui/HvacPanelRow.java [new file with mode: 0644]
src/com/android/car/hvac/ui/PressAndHoldTouchListener.java [new file with mode: 0644]
src/com/android/car/hvac/ui/SeatWarmerButton.java [new file with mode: 0644]
src/com/android/car/hvac/ui/TemperatureBarOverlay.java [new file with mode: 0644]
src/com/android/car/hvac/ui/ToggleButton.java [new file with mode: 0644]

diff --git a/Android.mk b/Android.mk
new file mode 100644 (file)
index 0000000..336a183
--- /dev/null
@@ -0,0 +1,45 @@
+#
+# 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.
+#
+
+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 := CarHvacApp
+
+LOCAL_CERTIFICATE := platform
+
+LOCAL_MODULE_TAGS := optional
+
+LOCAL_PRIVILEGED_MODULE := true
+
+LOCAL_STATIC_JAVA_LIBRARIES += android-support-v4 \
+                               jsr305
+
+LOCAL_PROGUARD_ENABLED := disabled
+
+LOCAL_DEX_PREOPT := false
+
+include packages/services/Car/car-support-lib/car-support.mk
+
+# This library needs to be added after the car.mk include.
+LOCAL_STATIC_JAVA_LIBRARIES += car-systemtest
+
+include $(BUILD_PACKAGE)
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
new file mode 100644 (file)
index 0000000..996b183
--- /dev/null
@@ -0,0 +1,43 @@
+<?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.
+  -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="com.android.car.hvac">
+    <uses-sdk
+        android:minSdkVersion="22"
+        android:targetSdkVersion="23"/>
+
+    <uses-permission android:name="android.car.permission.CAR_HVAC" />
+    <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
+    <!-- Required to use the TYPE_DISPLAY_OVERLAY layout param for the overlay hvac ui-->
+    <uses-permission android:name="android.permission.INTERNAL_SYSTEM_WINDOW" />
+
+    <application android:label="@string/hvac_label"
+        android:icon="@drawable/ic_launcher_hvac"
+        android:persistent="true">
+        <service android:name=".HvacController" android:exported="false" />
+
+        <service android:name=".HvacUiService"
+                 android:exported="false"/>
+
+        <receiver android:name=".BootCompleteReceiver">
+            <intent-filter>
+                <action android:name="android.intent.action.BOOT_COMPLETED" />
+            </intent-filter>
+        </receiver>
+    </application>
+</manifest>
diff --git a/res/drawable-nodpi/ic_launcher_hvac.xml b/res/drawable-nodpi/ic_launcher_hvac.xml
new file mode 100644 (file)
index 0000000..b544bd1
--- /dev/null
@@ -0,0 +1,27 @@
+<!--
+Copyright (C) 2014 The Android Open Source Project
+
+   Licensed under the Apache License, Version 2.0 (the "License");
+    you may not use this file except in compliance with the License.
+    You may obtain a copy of the License at
+
+         http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing, software
+    distributed under the License is distributed on an "AS IS" BASIS,
+    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+    See the License for the specific language governing permissions and
+    limitations under the License.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+        android:width="48.0dp"
+        android:height="48.0dp"
+        android:viewportWidth="48.0"
+        android:viewportHeight="48.0">
+    <path
+        android:fillColor="#FF000000"
+        android:pathData="M44.0,22.0l-8.34,0.0l6.48,-6.49 -2.83,-2.82L30.0,22.0l-4.0,0.0l0.0,-4.0l9.31,-9.31 -2.82,-2.83L26.0,12.34L26.0,4.0l-4.0,0.0l0.0,8.34l-6.49,-6.48 -2.82,2.83L22.0,18.0l0.0,4.0l-4.0,0.0l-9.31,-9.31 -2.83,2.82L12.34,22.0L4.0,22.0l0.0,4.0l8.34,0.0l-6.48,6.49 2.83,2.82L18.0,26.0l4.0,0.0l0.0,4.0l-9.31,9.31 2.82,2.83L22.0,35.66L22.0,44.0l4.0,0.0l0.0,-8.34l6.49,6.48 2.82,-2.83L26.0,30.0l0.0,-4.0l4.0,0.0l9.31,9.31 2.83,-2.82L35.66,26.0L44.0,26.0z"/>
+    <path
+        android:pathData="M0 0h48v48H0z"
+        android:fillColor="#00000000"/>
+</vector>
diff --git a/res/drawable/fan_speed_inset.xml b/res/drawable/fan_speed_inset.xml
new file mode 100644 (file)
index 0000000..4aa85a7
--- /dev/null
@@ -0,0 +1,22 @@
+<?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.
+*/
+-->
+<inset
+        xmlns:android="http://schemas.android.com/apk/res/android"
+        android:drawable="@drawable/ic_fan_speed_bg"
+        android:insetTop="@dimen/hvac_fan_speed_bar_vertical_inset"
+        android:insetBottom="@dimen/hvac_fan_speed_bar_vertical_inset"/>
\ No newline at end of file
diff --git a/res/drawable/hvac_off_text_bg.xml b/res/drawable/hvac_off_text_bg.xml
new file mode 100644 (file)
index 0000000..b687301
--- /dev/null
@@ -0,0 +1,23 @@
+<?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.
+*/
+-->
+<shape xmlns:android="http://schemas.android.com/apk/res/android">
+    <solid
+            android:color="@color/hvac_temperature_off_text_bg_color"/>
+    <corners
+            android:radius="@dimen/temperature_off_text_bg_height"/>
+</shape>
\ No newline at end of file
diff --git a/res/drawable/hvac_panel_button_bg.xml b/res/drawable/hvac_panel_button_bg.xml
new file mode 100644 (file)
index 0000000..6c7cc40
--- /dev/null
@@ -0,0 +1,23 @@
+<?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.
+*/
+-->
+<shape xmlns:android="http://schemas.android.com/apk/res/android">
+    <solid
+            android:color="@color/hvac_module_background_color"/>
+    <corners
+            android:radius="@dimen/hvac_panel_button_radius"/>
+</shape>
\ No newline at end of file
diff --git a/res/drawable/ic_ac_off.xml b/res/drawable/ic_ac_off.xml
new file mode 100644 (file)
index 0000000..acfb8d0
--- /dev/null
@@ -0,0 +1,51 @@
+<?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.
+*/
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+        android:width="108dp"
+        android:height="108dp"
+        android:viewportWidth="108"
+        android:viewportHeight="108">
+
+    <path
+            android:strokeWidth="1"
+            android:pathData="M 0 0 L 108 0 L 108 108 L 0 108 Z" />
+    <path
+            android:fillColor="#8FF5F5F5"
+            android:strokeWidth="1"
+            android:pathData="M59.4771,49.1323 C60.2261,48.1123 61.2991,47.6023 62.6951,47.6023
+C63.8971,47.6023 64.8091,47.8963 65.4311,48.4853 C66.0531,49.0733
+66.4401,50.0313 66.5931,51.3603 L69.7921,51.3603 C69.5891,49.3373
+68.8651,47.7713 67.6211,46.6633 C66.3771,45.5543 64.7351,45.0003 62.6951,45.0003
+C61.1801,45.0003 59.8451,45.3593 58.6901,46.0793 C57.5391,46.7963
+56.6551,47.8163 56.0381,49.1383 L58.5201,51.6203 C58.7021,50.6123
+59.0101,49.7683 59.4771,49.1323" />
+    <path
+            android:fillColor="#8FF5F5F5"
+            android:strokeWidth="1"
+            android:pathData="M69.792,57.7207 L66.593,57.7207 C66.514,58.3317 66.381,58.8537 66.203,59.3027
+L68.394,61.4947 C69.178,60.4917 69.649,59.2367 69.792,57.7207" />
+    <path
+            android:fillColor="#8FF5F5F5"
+            android:strokeWidth="1"
+            android:pathData="M42.7383,56.8447 L45.1023,50.0787 L46.0703,51.0477 L48.0953,56.8447
+L42.7383,56.8447 Z M73.9433,72.9937 L64.6893,63.7397 L62.3303,61.3807
+L58.5473,57.5977 L55.1033,54.1537 L47.2663,46.3167 L46.2033,45.2537
+L35.2763,34.3267 L32.3133,37.2897 L42.9663,47.9437 L37.0003,63.7387
+L40.3393,63.7387 L41.8373,59.4347 L48.9973,59.4347 L50.5083,63.7387
+L53.8593,63.7387 L50.8753,55.8517 L70.9803,75.9567 L73.9433,72.9937 Z" />
+</vector>
\ No newline at end of file
diff --git a/res/drawable/ic_ac_on.xml b/res/drawable/ic_ac_on.xml
new file mode 100644 (file)
index 0000000..37da338
--- /dev/null
@@ -0,0 +1,54 @@
+<?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.
+*/
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+        android:width="108dp"
+        android:height="108dp"
+        android:viewportWidth="108"
+        android:viewportHeight="108">
+
+    <path
+            android:strokeWidth="1"
+            android:pathData="M 0 0 L 108 0 L 108 108 L 0 108 Z" />
+    <path
+            android:fillColor="#F5F5F5"
+            android:strokeWidth="1"
+            android:pathData="M48.9970703,59.4345703 L41.8369141,59.4345703 L40.3388672,63.7382812
+L37,63.7382812 L43.9824219,45.2539062 L46.8642578,45.2539062
+L53.859375,63.7382812 L50.5078125,63.7382812 L48.9970703,59.4345703 Z
+M42.7382812,56.8447266 L48.0957031,56.8447266 L45.4169922,49.1767578
+L42.7382812,56.8447266 Z M69.7919922,57.7207031 C69.6057933,59.6927182
+68.877936,61.2309515 67.6083984,62.3354492 C66.3388608,63.4399469
+64.6504012,63.9921875 62.5429688,63.9921875 C61.0703051,63.9921875
+59.7732803,63.6430699 58.6518555,62.9448242 C57.5304306,62.2465785
+56.6650421,61.2542382 56.0556641,59.9677734 C55.446286,58.6813087
+55.1289064,57.1875085 55.1035156,55.4863281 L55.1035156,53.7597656
+C55.1035156,52.0162673 55.4124318,50.4801499 56.0302734,49.1513672
+C56.6481151,47.8225845 57.5346622,46.7985062 58.6899414,46.0791016
+C59.8452206,45.3596969 61.180331,45 62.6953125,45 C64.7350362,45
+66.3769469,45.5543564 67.6210938,46.6630859 C68.8652406,47.7718154
+69.5888662,49.337555 69.7919922,51.3603516 L66.5927734,51.3603516
+C66.4404289,50.0315689 66.0532258,49.0730824 65.4311523,48.4848633
+C64.8090789,47.8966442 63.8971414,47.6025391 62.6953125,47.6025391
+C61.2988211,47.6025391 60.226078,48.1124623 59.4770508,49.1323242
+C58.7280236,50.1521861 58.3450522,51.6481021 58.328125,53.6201172
+L58.328125,55.2578125 C58.328125,57.2552183 58.6857061,58.7786406
+59.4008789,59.828125 C60.1160518,60.8776094 61.1634046,61.4023438
+62.5429688,61.4023438 C63.8040428,61.4023438 64.75195,61.1188179
+65.3867188,60.5517578 C66.0214875,59.9846977 66.4235018,59.0410222
+66.5927734,57.7207031 L69.7919922,57.7207031 Z" />
+</vector>
\ No newline at end of file
diff --git a/res/drawable/ic_auto_off.xml b/res/drawable/ic_auto_off.xml
new file mode 100644 (file)
index 0000000..fa0e422
--- /dev/null
@@ -0,0 +1,82 @@
+<?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.
+*/
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+        android:width="108dp"
+        android:height="108dp"
+        android:viewportWidth="108"
+        android:viewportHeight="108">
+
+    <path
+            android:strokeWidth="1"
+            android:pathData="M 0 0 L 108 0 L 108 108 L 0 108 Z" />
+    <path
+            android:fillColor="#8FF5F5F5"
+            android:strokeWidth="1"
+            android:pathData="M82.6514,54.9785 C82.6514,57.0605 82.2684,58.6435 81.5024,59.7265
+C80.7364,60.8095 79.6424,61.3515 78.2204,61.3515 C76.8154,61.3515
+75.7174,60.7975 74.9264,59.6885 C74.1354,58.5795 73.7394,57.0095 73.7394,54.9785
+L73.7394,53.9125 C73.7564,51.9235 74.1564,50.3845 74.9394,49.2975
+C75.7214,48.2095 76.8074,47.6665 78.1954,47.6665 C79.6174,47.6665
+80.7154,48.2115 81.4894,49.3035 C82.2644,50.3955 82.6514,51.9655 82.6514,54.0135
+L82.6514,54.9785 Z M84.8984,49.2085 C84.2634,47.8505 83.3644,46.8095
+82.2004,46.0855 C81.0374,45.3615 79.7014,44.9995 78.1954,44.9995
+C76.6884,44.9995 75.3534,45.3665 74.1904,46.0985 C73.0264,46.8305
+72.1244,47.8865 71.4854,49.2655 C70.8464,50.6455 70.5274,52.2365 70.5274,54.0395
+L70.5274,55.0805 C70.5354,56.8485 70.8594,58.4105 71.4984,59.7645
+C72.1374,61.1185 73.0434,62.1615 74.2154,62.8945 C75.3874,63.6265
+76.7224,63.9925 78.2204,63.9925 C79.7354,63.9925 81.0754,63.6265 82.2384,62.8945
+C83.4024,62.1615 84.2974,61.1125 84.9234,59.7455 C85.5504,58.3785
+85.8634,56.7895 85.8634,54.9785 L85.8634,53.9505 C85.8544,52.1475
+85.5334,50.5665 84.8984,49.2085 L84.8984,49.2085 Z" />
+    <path
+            android:fillColor="#8FF5F5F5"
+            android:strokeWidth="1"
+            android:pathData="M25.7383,56.8447 L28.4173,49.1767 L31.0953,56.8447 L25.7383,56.8447 Z
+M26.9823,45.2537 L20.0003,63.7387 L23.3393,63.7387 L24.8373,59.4347
+L31.9973,59.4347 L33.5083,63.7387 L36.8593,63.7387 L29.8643,45.2537
+L26.9823,45.2537 Z" />
+    <path
+            android:fillColor="#8FF5F5F5"
+            android:strokeWidth="1"
+            android:pathData="M 49.0596 48.4847 L 34.9016 34.3267 Z" />
+    <path
+            android:fillColor="#8FF5F5F5"
+            android:strokeWidth="1"
+            android:pathData="M 52.2588 51.684 L 52.2588 51.684 L 60.0918 59.517 L 60.0918 59.517 Z" />
+    <path
+            android:fillColor="#8FF5F5F5"
+            android:strokeWidth="1"
+            android:pathData="M 49.0596 48.4847 L 49.0596 48.4847 L 52.2586 51.6837 L 52.2586 51.6837 Z" />
+    <path
+            android:fillColor="#8FF5F5F5"
+            android:strokeWidth="1"
+            android:pathData="M49.0596,57.5176 C49.0596,60.1246 47.8236,61.4276 45.3526,61.4276
+C44.1336,61.4276 43.2116,61.1016 42.5846,60.4506 C41.9586,59.7986
+41.6456,58.8546 41.6456,57.6196 L41.6456,46.9976 L49.0596,54.4116
+L49.0596,57.5176 Z M63.2786,62.7036 L60.0916,59.5166 L60.0916,59.5166
+L52.2586,51.6836 L49.0596,48.4846 L34.9016,34.3266 L31.9386,37.2906
+L39.9026,45.2536 L38.4466,45.2536 L38.4466,57.5936 C38.4466,59.5996
+39.0686,61.1676 40.3126,62.2976 C41.5566,63.4276 43.2366,63.9926 45.3526,63.9926
+C47.4426,63.9926 49.1166,63.4186 50.3736,62.2716 C51.6296,61.1256
+52.2576,59.5726 52.2586,57.6106 L70.6056,75.9566 L73.5686,72.9936
+L63.2786,62.7036 Z" />
+    <path
+            android:fillColor="#8FF5F5F5"
+            android:strokeWidth="1"
+            android:pathData="M 63.2783 47.8437 L 69.0423 47.8437 L 69.0423 45.2537 L 54.3793 45.2537 L 54.3793 47.8437 L 60.0913 47.8437 L 60.0913 53.5667 L 63.2783 56.7537 Z" />
+</vector>
\ No newline at end of file
diff --git a/res/drawable/ic_auto_on.xml b/res/drawable/ic_auto_on.xml
new file mode 100644 (file)
index 0000000..8fabd74
--- /dev/null
@@ -0,0 +1,69 @@
+<?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.
+*/
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+        android:width="108dp"
+        android:height="108dp"
+        android:viewportWidth="108"
+        android:viewportHeight="108">
+
+    <path
+            android:strokeWidth="1"
+            android:pathData="M 0 0 L 108 0 L 108 108 L 0 108 Z" />
+    <path
+            android:fillColor="#F5F5F5"
+            android:strokeWidth="1"
+            android:pathData="M31.9970703,59.4345703 L24.8369141,59.4345703 L23.3388672,63.7382812
+L20,63.7382812 L26.9824219,45.2539062 L29.8642578,45.2539062
+L36.859375,63.7382812 L33.5078125,63.7382812 L31.9970703,59.4345703 Z
+M25.7382812,56.8447266 L31.0957031,56.8447266 L28.4169922,49.1767578
+L25.7382812,56.8447266 Z M52.2587891,45.2539062 L52.2587891,57.6064453
+C52.2587891,59.5699968 51.6303774,61.125157 50.3735352,62.2719727
+C49.1166929,63.4187883 47.4430443,63.9921875 45.3525391,63.9921875
+C43.2366431,63.9921875 41.5566468,63.4272517 40.3125,62.2973633
+C39.0683532,61.1674748 38.4462891,59.5996194 38.4462891,57.59375
+L38.4462891,45.2539062 L41.6455078,45.2539062 L41.6455078,57.6191406
+C41.6455078,58.8548239 41.9586557,59.7984993 42.5849609,60.4501953
+C43.2112662,61.1018913 44.133783,61.4277344 45.3525391,61.4277344
+C47.8239056,61.4277344 49.0595703,60.124362 49.0595703,57.5175781
+L49.0595703,45.2539062 L52.2587891,45.2539062 Z M69.0419922,47.84375
+L63.2783203,47.84375 L63.2783203,63.7382812 L60.0917969,63.7382812
+L60.0917969,47.84375 L54.3789062,47.84375 L54.3789062,45.2539062
+L69.0419922,45.2539062 L69.0419922,47.84375 Z M85.8632812,54.9785156
+C85.8632812,56.7897226 85.5501333,58.3787367 84.9238281,59.7456055
+C84.2975229,61.1124743 83.4025123,62.161943 82.2387695,62.894043
+C81.0750267,63.626143 79.7356847,63.9921875 78.2207031,63.9921875
+C76.7226488,63.9921875 75.3875384,63.626143 74.215332,62.894043
+C73.0431256,62.161943 72.1375357,61.1188219 71.4985352,59.7646484
+C70.8595346,58.410475 70.5358073,56.8489672 70.5273438,55.0800781
+L70.5273438,54.0390625 C70.5273438,52.2363191 70.8468393,50.6451892
+71.4858398,49.265625 C72.1248404,47.8860608 73.0261986,46.8302445
+74.1899414,46.0981445 C75.3536842,45.3660445 76.6887946,45 78.1953125,45
+C79.7018304,45 81.0369408,45.3618128 82.2006836,46.0854492
+C83.3644264,46.8090856 84.2636687,47.8500909 84.8984375,49.2084961
+C85.5332063,50.5669013 85.8548177,52.1474519 85.8632812,53.9501953
+L85.8632812,54.9785156 Z M82.6513672,54.0136719 C82.6513672,51.9654846
+82.264164,50.3955133 81.4897461,49.3037109 C80.7153282,48.2119086
+79.6171946,47.6660156 78.1953125,47.6660156 C76.8072847,47.6660156
+75.7218464,48.2097927 74.9389648,49.2973633 C74.1560833,50.3849338
+73.756185,51.9231671 73.7392578,53.9121094 L73.7392578,54.9785156
+C73.7392578,57.0097758 74.1349244,58.5797471 74.9262695,59.6884766
+C75.7176146,60.7972061 76.8157482,61.3515625 78.2207031,61.3515625
+C79.6425852,61.3515625 80.7364871,60.8099012 81.5024414,59.7265625
+C82.2683958,58.6432238 82.6513672,57.0605573 82.6513672,54.9785156
+L82.6513672,54.0136719 Z" />
+</vector>
\ No newline at end of file
diff --git a/res/drawable/ic_decrease_icon.xml b/res/drawable/ic_decrease_icon.xml
new file mode 100644 (file)
index 0000000..e43e764
--- /dev/null
@@ -0,0 +1,28 @@
+<?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.
+*/
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+        android:width="48dp"
+        android:height="48dp"
+        android:viewportWidth="48"
+        android:viewportHeight="48">
+    <path
+            android:fillColor="#000000"
+            android:pathData="M38 26H10v-4h28v4z" />
+    <path
+            android:pathData="M0 0h48v48H0z" />
+</vector>
\ No newline at end of file
diff --git a/res/drawable/ic_down_arrow.xml b/res/drawable/ic_down_arrow.xml
new file mode 100644 (file)
index 0000000..1aa4baf
--- /dev/null
@@ -0,0 +1,29 @@
+<?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.
+*/
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+        android:width="48dp"
+        android:height="48dp"
+        android:viewportWidth="48"
+        android:viewportHeight="48">
+
+    <path
+            android:fillColor="#96FFFFFF"
+            android:pathData="M14 20l10 10 10-10z"/>
+    <path
+            android:pathData="M0 0h48v48H0z"/>
+</vector>
\ No newline at end of file
diff --git a/res/drawable/ic_fan_direction_1_off.xml b/res/drawable/ic_fan_direction_1_off.xml
new file mode 100644 (file)
index 0000000..d41e16b
--- /dev/null
@@ -0,0 +1,38 @@
+<?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.
+*/
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+        android:viewportWidth="108"
+        android:viewportHeight="108"
+        android:width="108dp"
+        android:height="108dp">
+    <group
+            android:scaleX="-1"
+            android:scaleY="-1"
+            android:translateX="96.33334"
+            android:translateY="73">
+        <path
+                android:pathData="M57.5 38.25l0 -3.5 -12.845 0 0 -4.083333L38.833333 36.5 44.655 42.333333 44.655 38.25Z"
+                android:fillColor="#f5f5f5" />
+    </group>
+    <path
+            android:pathData="M59.012449 61.002836l2.402926 -9.566666 1.394434 -5.6c0.592448 -1.966757 1.907369 -2.950135 3.131464 -3.29 0.74654 -0.21 1.539739 -0.28 2.309608 -0.14 2.916172 0.513333 4.82918 3.243333 4.339264 6.09l-3.149466 13.813333c-0.653222 3.313333 -3.522735 5.693333 -6.882165 5.693333l-15.980621 0 -8.91182 7 -3.499406 -3.5 13.461048 -10.5 11.384734 0z"
+            android:fillColor="#f5f5f5" />
+    <path
+            android:pathData="M67.648549 39.158549c-2.1 -1.493334 -2.613334 -4.386667 -1.143334 -6.51 1.47 -2.1 4.386667 -2.613334 6.51 -1.143334 2.1 1.493334 2.613334 4.386667 1.143334 6.51 -1.493334 2.1 -4.386667 2.613334 -6.51 1.143334l0 0z"
+            android:fillColor="#f5f5f5" />
+</vector>
\ No newline at end of file
diff --git a/res/drawable/ic_fan_direction_1_on.xml b/res/drawable/ic_fan_direction_1_on.xml
new file mode 100644 (file)
index 0000000..1f9222d
--- /dev/null
@@ -0,0 +1,41 @@
+<?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.
+*/
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+        android:viewportWidth="108"
+        android:viewportHeight="108"
+        android:width="108dp"
+        android:height="108dp">
+    <group
+            android:scaleX="-1"
+            android:scaleY="-1"
+            android:translateX="96.33334"
+            android:translateY="73">
+        <path
+                android:pathData="M57.5 38.25l0 -3.5 -12.845 0 0 -4.083333L38.833333 36.5 44.655 42.333333 44.655 38.25Z"
+                android:fillColor="#f5f5f5" />
+    </group>
+    <path
+            android:pathData="M59.012449 61.002836l2.402926 -9.566666 1.394434 -5.6c0.592448 -1.966757 1.907369 -2.950135 3.131464 -3.29 0.74654 -0.21 1.539739 -0.28 2.309608 -0.14 2.916172 0.513333 4.82918 3.243333 4.339264 6.09l-3.149466 13.813333c-0.653222 3.313333 -3.522735 5.693333 -6.882165 5.693333l-15.980621 0 -8.91182 7 -3.499406 -3.5 13.461048 -10.5 11.384734 0z"
+            android:fillColor="#f5f5f5" />
+    <path
+            android:pathData="M67.648549 39.158549c-2.1 -1.493334 -2.613334 -4.386667 -1.143334 -6.51 1.47 -2.1 4.386667 -2.613334 6.51 -1.143334 2.1 1.493334 2.613334 4.386667 1.143334 6.51 -1.493334 2.1 -4.386667 2.613334 -6.51 1.143334l0 0z"
+            android:fillColor="#f5f5f5" />
+    <path
+            android:pathData="M0 54c0 29.823429 24.176571 54 54 54 29.823429 0 54 -24.176571 54 -54C108 24.176571 83.823429 0 54 0 24.176571 0 0 24.176571 0 54ZM54 3.8616071c27.57 0 50 22.4299999 50 49.9999999 0 27.57 -22.43 50.000003 -50 50.000003 -27.57 0 -50 -22.430003 -50 -50.000003 0 -27.57 22.43 -49.9999999 50 -49.9999999"
+            android:fillColor="#f5f5f5" />
+</vector>
\ No newline at end of file
diff --git a/res/drawable/ic_fan_direction_2_off.xml b/res/drawable/ic_fan_direction_2_off.xml
new file mode 100644 (file)
index 0000000..63bde96
--- /dev/null
@@ -0,0 +1,46 @@
+<?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.
+*/
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+        android:viewportWidth="108"
+        android:viewportHeight="108"
+        android:width="108dp"
+        android:height="108dp">
+    <group
+            android:scaleX="-1"
+            android:scaleY="-1"
+            android:translateX="96.33334"
+            android:translateY="73">
+        <path
+                android:pathData="M57.5 38.25l0 -3.5 -12.845 0 0 -4.083333L38.833333 36.5 44.655 42.333333 44.655 38.25Z"
+                android:fillColor="#f5f5f5" />
+    </group>
+    <group
+            android:rotation="-90"
+            android:translateX="-16.33333"
+            android:translateY="87">
+        <path
+                android:pathData="M44.666667 53.416667l0 -3.5 -12.845 0 0 -4.083334L26 51.666667 31.821667 57.5l0 -4.083333z"
+                android:fillColor="#f5f5f5" />
+    </group>
+    <path
+            android:pathData="M59.012449 61.002836l2.402926 -9.566666 1.394434 -5.6c0.592448 -1.966757 1.907369 -2.950135 3.131464 -3.29 0.74654 -0.21 1.539739 -0.28 2.309608 -0.14 2.916172 0.513333 4.82918 3.243333 4.339264 6.09l-3.149466 13.813333c-0.653222 3.313333 -3.522735 5.693333 -6.882165 5.693333l-15.980621 0 -8.91182 7 -3.499406 -3.5 13.461048 -10.5 11.384734 0z"
+            android:fillColor="#f5f5f5" />
+    <path
+            android:pathData="M67.648549 39.158549c-2.1 -1.493334 -2.613334 -4.386667 -1.143334 -6.51 1.47 -2.1 4.386667 -2.613334 6.51 -1.143334 2.1 1.493334 2.613334 4.386667 1.143334 6.51 -1.493334 2.1 -4.386667 2.613334 -6.51 1.143334l0 0z"
+            android:fillColor="#f5f5f5" />
+</vector>
\ No newline at end of file
diff --git a/res/drawable/ic_fan_direction_2_on.xml b/res/drawable/ic_fan_direction_2_on.xml
new file mode 100644 (file)
index 0000000..10ff718
--- /dev/null
@@ -0,0 +1,49 @@
+<?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.
+*/
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+        android:viewportWidth="108"
+        android:viewportHeight="108"
+        android:width="108dp"
+        android:height="108dp">
+    <group
+            android:scaleX="-1"
+            android:scaleY="-1"
+            android:translateX="96.33334"
+            android:translateY="73">
+        <path
+                android:pathData="M57.5 38.25l0 -3.5 -12.845 0 0 -4.083333L38.833333 36.5 44.655 42.333333 44.655 38.25Z"
+                android:fillColor="#f5f5f5" />
+    </group>
+    <group
+            android:rotation="-90"
+            android:translateX="-16.33333"
+            android:translateY="87">
+        <path
+                android:pathData="M44.666667 53.416667l0 -3.5 -12.845 0 0 -4.083334L26 51.666667 31.821667 57.5l0 -4.083333z"
+                android:fillColor="#f5f5f5" />
+    </group>
+    <path
+            android:pathData="M59.012449 61.002836l2.402926 -9.566666 1.394434 -5.6c0.592448 -1.966757 1.907369 -2.950135 3.131464 -3.29 0.74654 -0.21 1.539739 -0.28 2.309608 -0.14 2.916172 0.513333 4.82918 3.243333 4.339264 6.09l-3.149466 13.813333c-0.653222 3.313333 -3.522735 5.693333 -6.882165 5.693333l-15.980621 0 -8.91182 7 -3.499406 -3.5 13.461048 -10.5 11.384734 0z"
+            android:fillColor="#f5f5f5" />
+    <path
+            android:pathData="M67.648549 39.158549c-2.1 -1.493334 -2.613334 -4.386667 -1.143334 -6.51 1.47 -2.1 4.386667 -2.613334 6.51 -1.143334 2.1 1.493334 2.613334 4.386667 1.143334 6.51 -1.493334 2.1 -4.386667 2.613334 -6.51 1.143334l0 0z"
+            android:fillColor="#f5f5f5" />
+    <path
+            android:pathData="M0 54c0 29.823429 24.176571 54 54 54 29.823429 0 54 -24.176571 54 -54C108 24.176571 83.823429 0 54 0 24.176571 0 0 24.176571 0 54ZM54 3.8616071c27.57 0 50 22.4299999 50 49.9999999 0 27.57 -22.43 50.000003 -50 50.000003 -27.57 0 -50 -22.430003 -50 -50.000003 0 -27.57 22.43 -49.9999999 50 -49.9999999"
+            android:fillColor="#f5f5f5" />
+</vector>
\ No newline at end of file
diff --git a/res/drawable/ic_fan_direction_3_off.xml b/res/drawable/ic_fan_direction_3_off.xml
new file mode 100644 (file)
index 0000000..094708b
--- /dev/null
@@ -0,0 +1,37 @@
+<?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.
+*/
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+        android:viewportWidth="108"
+        android:viewportHeight="108"
+        android:width="108dp"
+        android:height="108dp">
+    <group
+            android:rotation="-90"
+            android:translateX="-16.33333"
+            android:translateY="87">
+        <path
+                android:pathData="M44.666667 53.416667l0 -3.5 -12.845 0 0 -4.083334L26 51.666667 31.821667 57.5l0 -4.083333z"
+                android:fillColor="#f5f5f5" />
+    </group>
+    <path
+            android:pathData="M59.012449 61.002836l2.402926 -9.566666 1.394434 -5.6c0.592448 -1.966757 1.907369 -2.950135 3.131464 -3.29 0.74654 -0.21 1.539739 -0.28 2.309608 -0.14 2.916172 0.513333 4.82918 3.243333 4.339264 6.09l-3.149466 13.813333c-0.653222 3.313333 -3.522735 5.693333 -6.882165 5.693333l-15.980621 0 -8.91182 7 -3.499406 -3.5 13.461048 -10.5 11.384734 0z"
+            android:fillColor="#f5f5f5" />
+    <path
+            android:pathData="M67.648549 39.158549c-2.1 -1.493334 -2.613334 -4.386667 -1.143334 -6.51 1.47 -2.1 4.386667 -2.613334 6.51 -1.143334 2.1 1.493334 2.613334 4.386667 1.143334 6.51 -1.493334 2.1 -4.386667 2.613334 -6.51 1.143334l0 0z"
+            android:fillColor="#f5f5f5" />
+</vector>
\ No newline at end of file
diff --git a/res/drawable/ic_fan_direction_3_on.xml b/res/drawable/ic_fan_direction_3_on.xml
new file mode 100644 (file)
index 0000000..20624ed
--- /dev/null
@@ -0,0 +1,40 @@
+<?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.
+*/
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+        android:viewportWidth="108"
+        android:viewportHeight="108"
+        android:width="108dp"
+        android:height="108dp">
+    <group
+            android:rotation="-90"
+            android:translateX="-16.33333"
+            android:translateY="87">
+        <path
+                android:pathData="M44.666667 53.416667l0 -3.5 -12.845 0 0 -4.083334L26 51.666667 31.821667 57.5l0 -4.083333z"
+                android:fillColor="#f5f5f5" />
+    </group>
+    <path
+            android:pathData="M59.012449 61.002836l2.402926 -9.566666 1.394434 -5.6c0.592448 -1.966757 1.907369 -2.950135 3.131464 -3.29 0.74654 -0.21 1.539739 -0.28 2.309608 -0.14 2.916172 0.513333 4.82918 3.243333 4.339264 6.09l-3.149466 13.813333c-0.653222 3.313333 -3.522735 5.693333 -6.882165 5.693333l-15.980621 0 -8.91182 7 -3.499406 -3.5 13.461048 -10.5 11.384734 0z"
+            android:fillColor="#f5f5f5" />
+    <path
+            android:pathData="M67.648549 39.158549c-2.1 -1.493334 -2.613334 -4.386667 -1.143334 -6.51 1.47 -2.1 4.386667 -2.613334 6.51 -1.143334 2.1 1.493334 2.613334 4.386667 1.143334 6.51 -1.493334 2.1 -4.386667 2.613334 -6.51 1.143334l0 0z"
+            android:fillColor="#f5f5f5" />
+    <path
+            android:pathData="M0 54c0 29.823429 24.176571 54 54 54 29.823429 0 54 -24.176571 54 -54C108 24.176571 83.823429 0 54 0 24.176571 0 0 24.176571 0 54ZM54 3.8616071c27.57 0 50 22.4299999 50 49.9999999 0 27.57 -22.43 50.000003 -50 50.000003 -27.57 0 -50 -22.430003 -50 -50.000003 0 -27.57 22.43 -49.9999999 50 -49.9999999"
+            android:fillColor="#f5f5f5" />
+</vector>
\ No newline at end of file
diff --git a/res/drawable/ic_fan_direction_4_off.xml b/res/drawable/ic_fan_direction_4_off.xml
new file mode 100644 (file)
index 0000000..92d3a2f
--- /dev/null
@@ -0,0 +1,49 @@
+<?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.
+*/
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+        android:viewportWidth="108"
+        android:viewportHeight="108"
+        android:width="108dp"
+        android:height="108dp">
+    <group
+            android:rotation="-90"
+            android:translateX="-16.33333"
+            android:translateY="87">
+        <path
+                android:pathData="M44.666667 53.416667l0 -3.5 -12.845 0 0 -4.083334L26 51.666667 31.821667 57.5l0 -4.083333z"
+                android:fillColor="#f5f5f5" />
+    </group>
+    <path
+            android:pathData="M59.012449 61.002836l2.402926 -9.566666 1.394434 -5.6c0.592448 -1.966757 1.907369 -2.950135 3.131464 -3.29 0.74654 -0.21 1.539739 -0.28 2.309608 -0.14 2.916172 0.513333 4.82918 3.243333 4.339264 6.09l-3.149466 13.813333c-0.653222 3.313333 -3.522735 5.693333 -6.882165 5.693333l-15.980621 0 -8.91182 7 -3.499406 -3.5 13.461048 -10.5 11.384734 0z"
+            android:fillColor="#f5f5f5" />
+    <path
+            android:pathData="M67.648549 39.158549c-2.1 -1.493334 -2.613334 -4.386667 -1.143334 -6.51 1.47 -2.1 4.386667 -2.613334 6.51 -1.143334 2.1 1.493334 2.613334 4.386667 1.143334 6.51 -1.493334 2.1 -4.386667 2.613334 -6.51 1.143334l0 0z"
+            android:fillColor="#f5f5f5" />
+    <path
+            android:pathData="M47.150143 44.565971c0 -0.403677 -0.09086 -0.562059 -0.252 -0.82853 -0.189 -0.331765 -0.447857 -0.778235 -0.447857 -1.578088 0 -0.799853 0.258857 -1.246765 0.447857 -1.571029 0.161143 -0.266471 0.252 -0.432353 0.252 -0.82853 0 -0.396617 -0.09086 -0.562059 -0.252 -0.82897 -0.189 -0.331324 -0.447857 -0.778236 -0.447857 -1.578089l1.399714 0c0 0.403677 0.09129 0.562059 0.252 0.828971 0.189 0.331323 0.448286 0.778235 0.448286 1.578088 0 0.799853 -0.259286 1.246324 -0.448286 1.570588 -0.160714 0.266912 -0.252 0.425294 -0.252 0.828971 0 0.403235 0.09129 0.562059 0.252 0.828529 0.189 0.331324 0.448286 0.778236 0.448286 1.578089 0 0.01235 -1.400143 0.01765 -1.400143 0"
+            android:fillColor="#f5f5f5" />
+    <path
+            android:pathData="M50.793 44.565971c0 -0.403677 -0.09086 -0.562059 -0.252 -0.82853 -0.189 -0.331765 -0.447857 -0.778235 -0.447857 -1.578088 0 -0.799853 0.258857 -1.246765 0.447857 -1.571029 0.161143 -0.266471 0.252 -0.432353 0.252 -0.82853 0 -0.396617 -0.09086 -0.562059 -0.252 -0.82897 -0.189 -0.331324 -0.447857 -0.778236 -0.447857 -1.578089l1.399714 0c0 0.403677 0.09129 0.562059 0.252 0.828971 0.189 0.331323 0.448286 0.778235 0.448286 1.578088 0 0.799853 -0.259286 1.246324 -0.448286 1.570588 -0.160714 0.266912 -0.252 0.425294 -0.252 0.828971 0 0.403235 0.09129 0.562059 0.252 0.828529 0.189 0.331324 0.448286 0.778236 0.448286 1.578089 0 0.01235 -1.400143 0.01765 -1.400143 0"
+            android:fillColor="#f5f5f5" />
+    <path
+            android:pathData="M54.342943 44.565971c0 -0.403677 -0.09086 -0.562059 -0.252 -0.82853 -0.189 -0.331765 -0.447857 -0.778235 -0.447857 -1.578088 0 -0.799853 0.258857 -1.246765 0.447857 -1.571029 0.161143 -0.266471 0.252 -0.432353 0.252 -0.82853 0 -0.396617 -0.09086 -0.562059 -0.252 -0.82897 -0.189 -0.331324 -0.447857 -0.778236 -0.447857 -1.578089l1.399714 0c0 0.403677 0.09129 0.562059 0.252 0.828971 0.189 0.331323 0.448286 0.778235 0.448286 1.578088 0 0.799853 -0.259286 1.246324 -0.448286 1.570588 -0.160714 0.266912 -0.252 0.425294 -0.252 0.828971 0 0.403235 0.09129 0.562059 0.252 0.828529 0.189 0.331324 0.448286 0.778236 0.448286 1.578089 0 0.01235 -1.400143 0.01765 -1.400143 0"
+            android:fillColor="#f5f5f5" />
+    <path
+            android:pathData="M58.865571 43.748823l-1.516285 -0.824117 3.63 -7.08C60.612857 35.418088 59.919857 34.705147 58.870714 33.998823 56.667 32.516471 54.018857 31.765147 51 31.765147c-3.018857 0 -5.667 0.751324 -7.870714 2.233676 -1.048715 0.705883 -1.741286 1.418383 -2.108572 1.846324L44.650286 42.924706 43.134 43.748823 39 35.685441 39.291 35.246029C39.433286 35.031618 42.862714 30 51 30c8.137286 0 11.566714 5.031618 11.709 5.246029L63 35.685441 58.865571 43.748823Z"
+            android:fillColor="#f5f5f5" />
+</vector>
\ No newline at end of file
diff --git a/res/drawable/ic_fan_direction_4_on.xml b/res/drawable/ic_fan_direction_4_on.xml
new file mode 100644 (file)
index 0000000..6139386
--- /dev/null
@@ -0,0 +1,52 @@
+<?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.
+*/
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+        android:viewportWidth="108"
+        android:viewportHeight="108"
+        android:width="108dp"
+        android:height="108dp">
+    <group
+            android:rotation="-90"
+            android:translateX="-16.33333"
+            android:translateY="87">
+        <path
+                android:pathData="M44.666667 53.416667l0 -3.5 -12.845 0 0 -4.083334L26 51.666667 31.821667 57.5l0 -4.083333z"
+                android:fillColor="#f5f5f5" />
+    </group>
+    <path
+            android:pathData="M59.012449 61.002836l2.402926 -9.566666 1.394434 -5.6c0.592448 -1.966757 1.907369 -2.950135 3.131464 -3.29 0.74654 -0.21 1.539739 -0.28 2.309608 -0.14 2.916172 0.513333 4.82918 3.243333 4.339264 6.09l-3.149466 13.813333c-0.653222 3.313333 -3.522735 5.693333 -6.882165 5.693333l-15.980621 0 -8.91182 7 -3.499406 -3.5 13.461048 -10.5 11.384734 0z"
+            android:fillColor="#f5f5f5" />
+    <path
+            android:pathData="M67.648549 39.158549c-2.1 -1.493334 -2.613334 -4.386667 -1.143334 -6.51 1.47 -2.1 4.386667 -2.613334 6.51 -1.143334 2.1 1.493334 2.613334 4.386667 1.143334 6.51 -1.493334 2.1 -4.386667 2.613334 -6.51 1.143334l0 0z"
+            android:fillColor="#f5f5f5" />
+    <path
+            android:pathData="M47.150143 44.565971c0 -0.403677 -0.09086 -0.562059 -0.252 -0.82853 -0.189 -0.331765 -0.447857 -0.778235 -0.447857 -1.578088 0 -0.799853 0.258857 -1.246765 0.447857 -1.571029 0.161143 -0.266471 0.252 -0.432353 0.252 -0.82853 0 -0.396617 -0.09086 -0.562059 -0.252 -0.82897 -0.189 -0.331324 -0.447857 -0.778236 -0.447857 -1.578089l1.399714 0c0 0.403677 0.09129 0.562059 0.252 0.828971 0.189 0.331323 0.448286 0.778235 0.448286 1.578088 0 0.799853 -0.259286 1.246324 -0.448286 1.570588 -0.160714 0.266912 -0.252 0.425294 -0.252 0.828971 0 0.403235 0.09129 0.562059 0.252 0.828529 0.189 0.331324 0.448286 0.778236 0.448286 1.578089 0 0.01235 -1.400143 0.01765 -1.400143 0"
+            android:fillColor="#f5f5f5" />
+    <path
+            android:pathData="M50.793 44.565971c0 -0.403677 -0.09086 -0.562059 -0.252 -0.82853 -0.189 -0.331765 -0.447857 -0.778235 -0.447857 -1.578088 0 -0.799853 0.258857 -1.246765 0.447857 -1.571029 0.161143 -0.266471 0.252 -0.432353 0.252 -0.82853 0 -0.396617 -0.09086 -0.562059 -0.252 -0.82897 -0.189 -0.331324 -0.447857 -0.778236 -0.447857 -1.578089l1.399714 0c0 0.403677 0.09129 0.562059 0.252 0.828971 0.189 0.331323 0.448286 0.778235 0.448286 1.578088 0 0.799853 -0.259286 1.246324 -0.448286 1.570588 -0.160714 0.266912 -0.252 0.425294 -0.252 0.828971 0 0.403235 0.09129 0.562059 0.252 0.828529 0.189 0.331324 0.448286 0.778236 0.448286 1.578089 0 0.01235 -1.400143 0.01765 -1.400143 0"
+            android:fillColor="#f5f5f5" />
+    <path
+            android:pathData="M54.342943 44.565971c0 -0.403677 -0.09086 -0.562059 -0.252 -0.82853 -0.189 -0.331765 -0.447857 -0.778235 -0.447857 -1.578088 0 -0.799853 0.258857 -1.246765 0.447857 -1.571029 0.161143 -0.266471 0.252 -0.432353 0.252 -0.82853 0 -0.396617 -0.09086 -0.562059 -0.252 -0.82897 -0.189 -0.331324 -0.447857 -0.778236 -0.447857 -1.578089l1.399714 0c0 0.403677 0.09129 0.562059 0.252 0.828971 0.189 0.331323 0.448286 0.778235 0.448286 1.578088 0 0.799853 -0.259286 1.246324 -0.448286 1.570588 -0.160714 0.266912 -0.252 0.425294 -0.252 0.828971 0 0.403235 0.09129 0.562059 0.252 0.828529 0.189 0.331324 0.448286 0.778236 0.448286 1.578089 0 0.01235 -1.400143 0.01765 -1.400143 0"
+            android:fillColor="#f5f5f5" />
+    <path
+            android:pathData="M58.865571 43.748823l-1.516285 -0.824117 3.63 -7.08C60.612857 35.418088 59.919857 34.705147 58.870714 33.998823 56.667 32.516471 54.018857 31.765147 51 31.765147c-3.018857 0 -5.667 0.751324 -7.870714 2.233676 -1.048715 0.705883 -1.741286 1.418383 -2.108572 1.846324L44.650286 42.924706 43.134 43.748823 39 35.685441 39.291 35.246029C39.433286 35.031618 42.862714 30 51 30c8.137286 0 11.566714 5.031618 11.709 5.246029L63 35.685441 58.865571 43.748823Z"
+            android:fillColor="#f5f5f5" />
+    <path
+            android:pathData="M0 54c0 29.823429 24.176571 54 54 54 29.823429 0 54 -24.176571 54 -54C108 24.176571 83.823429 0 54 0 24.176571 0 0 24.176571 0 54ZM54 3.8616071c27.57 0 50 22.4299999 50 49.9999999 0 27.57 -22.43 50.000003 -50 50.000003 -27.57 0 -50 -22.430003 -50 -50.000003 0 -27.57 22.43 -49.9999999 50 -49.9999999"
+            android:fillColor="#f5f5f5" />
+</vector>
\ No newline at end of file
diff --git a/res/drawable/ic_fan_speed_bg.xml b/res/drawable/ic_fan_speed_bg.xml
new file mode 100644 (file)
index 0000000..2609ed5
--- /dev/null
@@ -0,0 +1,22 @@
+<?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.
+*/
+-->
+<shape xmlns:android="http://schemas.android.com/apk/res/android">
+    <solid android:color="@color/hvac_fanspeed_bg_color"/>
+    <corners
+            android:radius="@dimen/hvac_fan_speed_bar_corner_radius"/>
+</shape>
\ No newline at end of file
diff --git a/res/drawable/ic_front_defroster_off.xml b/res/drawable/ic_front_defroster_off.xml
new file mode 100644 (file)
index 0000000..43a8cd9
--- /dev/null
@@ -0,0 +1,45 @@
+<?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.
+*/
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+        android:width="108dp"
+        android:height="108dp"
+        android:viewportWidth="108"
+        android:viewportHeight="108">
+
+    <path
+            android:strokeWidth="1"
+            android:pathData="M 0 0 L 108 0 L 108 108 L 0 108 Z" />
+    <path
+            android:strokeWidth="1"
+            android:pathData="M 26 26 L 82 26 L 82 82 L 26 82 Z" />
+    <path
+            android:fillColor="#8FF5F5F5"
+            android:strokeWidth="1"
+            android:pathData="M81.321,48.891 C80.989,48.405 72.987,37 54,37 C50.433,37 47.254,37.403
+44.44,38.06 L47.866,41.486 C49.832,41.176 51.871,41.001 54,41.001 C61.044,41.001
+67.223,42.704 72.365,46.064 C74.813,47.665 76.43,49.281 77.285,50.248
+L70.149,63.769 L73.11,66.73 L82,49.887 L81.321,48.891 Z" />
+    <path
+            android:fillColor="#8FF5F5F5"
+            android:strokeWidth="1"
+            android:pathData="M31.3689,36.8655 L35.8109,41.3075 C29.7069,44.5945 26.8729,48.6065
+26.6789,48.8905 L25.9999,49.8865 L35.6459,68.1635 L39.1839,66.2955
+L30.7149,50.2485 C31.5719,49.2785 33.1879,47.6635 35.6349,46.0635
+C36.6459,45.4035 37.7049,44.8215 38.7969,44.2935 L70.0359,75.5325
+L72.9989,72.5685 L34.3319,33.9025 L31.3689,36.8655 Z" />
+</vector>
\ No newline at end of file
diff --git a/res/drawable/ic_front_defroster_on.xml b/res/drawable/ic_front_defroster_on.xml
new file mode 100644 (file)
index 0000000..b5d9787
--- /dev/null
@@ -0,0 +1,75 @@
+<?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.
+*/
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+        android:width="108dp"
+        android:height="108dp"
+        android:viewportWidth="108"
+        android:viewportHeight="108">
+
+    <path
+            android:strokeWidth="1"
+            android:pathData="M 0 0 L 108 0 L 108 108 L 0 108 Z" />
+    <path
+            android:strokeWidth="1"
+            android:pathData="M 26 26 L 82 26 L 82 82 L 26 82 Z" />
+    <path
+            android:fillColor="#F5F5F5"
+            android:strokeWidth="1"
+            android:pathData="M45.017,70.0162 C45.017,69.1012 44.805,68.7422 44.429,68.1382 C43.988,67.3862
+43.384,66.3742 43.384,64.5612 C43.384,62.7482 43.988,61.7352 44.429,61.0002
+C44.805,60.3962 45.017,60.0202 45.017,59.1222 C45.017,58.2232 44.805,57.8482
+44.429,57.2432 C43.988,56.4922 43.384,55.4792 43.384,53.6662 L46.65,53.6662
+C46.65,54.5812 46.863,54.9402 47.238,55.5452 C47.679,56.2962 48.284,57.3092
+48.284,59.1222 C48.284,60.9352 47.679,61.9472 47.238,62.6822 C46.863,63.2872
+46.65,63.6462 46.65,64.5612 C46.65,65.4752 46.863,65.8352 47.238,66.4392
+C47.679,67.1902 48.284,68.2032 48.284,70.0162 C48.284,70.0442 45.017,70.0562
+45.017,70.0162" />
+    <path
+            android:fillColor="#F5F5F5"
+            android:strokeWidth="1"
+            android:pathData="M53.517,70.0162 C53.517,69.1012 53.305,68.7422 52.929,68.1382 C52.488,67.3862
+51.884,66.3742 51.884,64.5612 C51.884,62.7482 52.488,61.7352 52.929,61.0002
+C53.305,60.3962 53.517,60.0202 53.517,59.1222 C53.517,58.2232 53.305,57.8482
+52.929,57.2432 C52.488,56.4922 51.884,55.4792 51.884,53.6662 L55.15,53.6662
+C55.15,54.5812 55.363,54.9402 55.738,55.5452 C56.179,56.2962 56.784,57.3092
+56.784,59.1222 C56.784,60.9352 56.179,61.9472 55.738,62.6822 C55.363,63.2872
+55.15,63.6462 55.15,64.5612 C55.15,65.4752 55.363,65.8352 55.738,66.4392
+C56.179,67.1902 56.784,68.2032 56.784,70.0162 C56.784,70.0442 53.517,70.0562
+53.517,70.0162" />
+    <path
+            android:fillColor="#F5F5F5"
+            android:strokeWidth="1"
+            android:pathData="M61.8002,70.0162 C61.8002,69.1012 61.5882,68.7422 61.2122,68.1382
+C60.7712,67.3862 60.1672,66.3742 60.1672,64.5612 C60.1672,62.7482
+60.7712,61.7352 61.2122,61.0002 C61.5882,60.3962 61.8002,60.0202 61.8002,59.1222
+C61.8002,58.2232 61.5882,57.8482 61.2122,57.2432 C60.7712,56.4922
+60.1672,55.4792 60.1672,53.6662 L63.4332,53.6662 C63.4332,54.5812
+63.6462,54.9402 64.0212,55.5452 C64.4622,56.2962 65.0672,57.3092 65.0672,59.1222
+C65.0672,60.9352 64.4622,61.9472 64.0212,62.6822 C63.6462,63.2872
+63.4332,63.6462 63.4332,64.5612 C63.4332,65.4752 63.6462,65.8352 64.0212,66.4392
+C64.4622,67.1902 65.0672,68.2032 65.0672,70.0162 C65.0672,70.0442
+61.8002,70.0562 61.8002,70.0162" />
+    <path
+            android:fillColor="#F5F5F5"
+            android:strokeWidth="1"
+            android:pathData="M72.353,68.164 L68.815,66.296 L77.285,50.248 C76.43,49.281 74.813,47.665
+72.365,46.064 C67.223,42.704 61.044,41.001 54,41.001 C46.956,41.001
+40.777,42.704 35.635,46.064 C33.188,47.664 31.572,49.279 30.715,50.249
+L39.184,66.296 L35.646,68.164 L26,49.887 L26.679,48.891 C27.011,48.405 35.013,37
+54,37 C72.987,37 80.989,48.405 81.321,48.891 L82,49.887 L72.353,68.164 Z" />
+</vector>
\ No newline at end of file
diff --git a/res/drawable/ic_increase_icon.xml b/res/drawable/ic_increase_icon.xml
new file mode 100644 (file)
index 0000000..1b0a7c8
--- /dev/null
@@ -0,0 +1,29 @@
+<?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.
+*/
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+        android:width="48dp"
+        android:height="48dp"
+        android:viewportWidth="48"
+        android:viewportHeight="48">
+
+    <path
+            android:fillColor="#000000"
+            android:pathData="M38 26H26v12h-4V26H10v-4h12V10h4v12h12v4z" />
+    <path
+            android:pathData="M0 0h48v48H0z" />
+</vector>
\ No newline at end of file
diff --git a/res/drawable/ic_rear_defroster_off.xml b/res/drawable/ic_rear_defroster_off.xml
new file mode 100644 (file)
index 0000000..ed52890
--- /dev/null
@@ -0,0 +1,38 @@
+<?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.
+*/
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+        android:width="108dp"
+        android:height="108dp"
+        android:viewportWidth="108"
+        android:viewportHeight="108">
+
+    <path
+            android:strokeWidth="1"
+            android:pathData="M 0 0 L 108 0 L 108 108 L 0 108 Z" />
+    <path
+            android:strokeWidth="1"
+            android:pathData="M 26 26 L 82 26 L 82 82 L 26 82 Z" />
+    <path
+            android:fillColor="#8FF5F5F5"
+            android:strokeWidth="1"
+            android:pathData="M 45.38 39 L 49.38 43 L 73 43 L 73 64 L 70.38 64 L 74.38 68 L 77 68 L 77 39 Z" />
+    <path
+            android:fillColor="#8FF5F5F5"
+            android:strokeWidth="1"
+            android:pathData="M 68 67.5699 L 43.43 42.9999 L 39.43 38.9999 L 34.332 33.9019 L 31.369 36.8659 L 33.503 38.9999 L 31.003 38.9999 L 31.003 67.9999 L 40 67.9999 L 40 63.9999 L 35 63.9999 L 35 42.9999 L 37.503 42.9999 L 70.036 75.5319 L 72.999 72.5689 L 68.43 67.9999 Z" />
+</vector>
\ No newline at end of file
diff --git a/res/drawable/ic_rear_defroster_on.xml b/res/drawable/ic_rear_defroster_on.xml
new file mode 100644 (file)
index 0000000..a52d7f8
--- /dev/null
@@ -0,0 +1,71 @@
+<?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.
+*/
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+        android:width="108dp"
+        android:height="108dp"
+        android:viewportWidth="108"
+        android:viewportHeight="108">
+
+    <path
+            android:strokeWidth="1"
+            android:pathData="M 0 0 L 108 0 L 108 108 L 0 108 Z" />
+    <path
+            android:strokeWidth="1"
+            android:pathData="M 26 26 L 82 26 L 82 82 L 26 82 Z" />
+    <path
+            android:fillColor="#F5F5F5"
+            android:strokeWidth="1"
+            android:pathData="M 31.0031738 39 L 31.0031738 68 L 40 68 L 40 64 L 35 64 L 35 43 L 73 43 L 73 64 L 68 64 L 68 68 L 77 68 L 77 39 Z" />
+    <path
+            android:fillColor="#F5F5F5"
+            android:strokeWidth="1"
+            android:pathData="M45.017,70.0162 C45.017,69.1012 44.805,68.7422 44.429,68.1382 C43.988,67.3862
+43.384,66.3742 43.384,64.5612 C43.384,62.7482 43.988,61.7352 44.429,61.0002
+C44.805,60.3962 45.017,60.0202 45.017,59.1222 C45.017,58.2232 44.805,57.8482
+44.429,57.2432 C43.988,56.4922 43.384,55.4792 43.384,53.6662 L46.65,53.6662
+C46.65,54.5812 46.863,54.9402 47.238,55.5452 C47.679,56.2962 48.284,57.3092
+48.284,59.1222 C48.284,60.9352 47.679,61.9472 47.238,62.6822 C46.863,63.2872
+46.65,63.6462 46.65,64.5612 C46.65,65.4752 46.863,65.8352 47.238,66.4392
+C47.679,67.1902 48.284,68.2032 48.284,70.0162 C48.284,70.0442 45.017,70.0562
+45.017,70.0162" />
+    <path
+            android:fillColor="#F5F5F5"
+            android:strokeWidth="1"
+            android:pathData="M53.517,70.0162 C53.517,69.1012 53.305,68.7422 52.929,68.1382 C52.488,67.3862
+51.884,66.3742 51.884,64.5612 C51.884,62.7482 52.488,61.7352 52.929,61.0002
+C53.305,60.3962 53.517,60.0202 53.517,59.1222 C53.517,58.2232 53.305,57.8482
+52.929,57.2432 C52.488,56.4922 51.884,55.4792 51.884,53.6662 L55.15,53.6662
+C55.15,54.5812 55.363,54.9402 55.738,55.5452 C56.179,56.2962 56.784,57.3092
+56.784,59.1222 C56.784,60.9352 56.179,61.9472 55.738,62.6822 C55.363,63.2872
+55.15,63.6462 55.15,64.5612 C55.15,65.4752 55.363,65.8352 55.738,66.4392
+C56.179,67.1902 56.784,68.2032 56.784,70.0162 C56.784,70.0442 53.517,70.0562
+53.517,70.0162" />
+    <path
+            android:fillColor="#F5F5F5"
+            android:strokeWidth="1"
+            android:pathData="M61.8002,70.0162 C61.8002,69.1012 61.5882,68.7422 61.2122,68.1382
+C60.7712,67.3862 60.1672,66.3742 60.1672,64.5612 C60.1672,62.7482
+60.7712,61.7352 61.2122,61.0002 C61.5882,60.3962 61.8002,60.0202 61.8002,59.1222
+C61.8002,58.2232 61.5882,57.8482 61.2122,57.2432 C60.7712,56.4922
+60.1672,55.4792 60.1672,53.6662 L63.4332,53.6662 C63.4332,54.5812
+63.6462,54.9402 64.0212,55.5452 C64.4622,56.2962 65.0672,57.3092 65.0672,59.1222
+C65.0672,60.9352 64.4622,61.9472 64.0212,62.6822 C63.6462,63.2872
+63.4332,63.6462 63.4332,64.5612 C63.4332,65.4752 63.6462,65.8352 64.0212,66.4392
+C64.4622,67.1902 65.0672,68.2032 65.0672,70.0162 C65.0672,70.0442
+61.8002,70.0562 61.8002,70.0162" />
+</vector>
\ No newline at end of file
diff --git a/res/drawable/ic_recycle_air_off.xml b/res/drawable/ic_recycle_air_off.xml
new file mode 100644 (file)
index 0000000..4c62ea7
--- /dev/null
@@ -0,0 +1,51 @@
+<?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.
+*/
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+        android:width="108dp"
+        android:height="108dp"
+        android:viewportWidth="108"
+        android:viewportHeight="108">
+
+    <path
+            android:strokeWidth="1"
+            android:pathData="M 0 0 L 108 0 L 108 108 L 0 108 Z" />
+    <path
+            android:strokeWidth="1"
+            android:pathData="M 26 26 H 82 V 82 H 26 V 26 Z" />
+    <path
+            android:fillColor="#8FF5F5F5"
+            android:strokeWidth="1"
+            android:pathData="M49.3333,40.3333 L61.0003,40.3333 L61.0003,45.0003 L49.8883,45.0003
+L59.2223,54.3333 L61.0003,54.3333 L61.0003,49.6663 L68.0003,56.6663
+L64.7773,59.8893 L75.0003,70.1113 L75.0003,52.0003 L70.1463,38.0233
+C69.6803,36.6463 68.3733,35.6663 66.8333,35.6663 L41.1663,35.6663
+C40.9733,35.6663 40.7843,35.6823 40.6003,35.7123 L45.8893,41.0003
+C46.9563,40.5743 48.1163,40.3333 49.3333,40.3333" />
+    <path
+            android:fillColor="#8FF5F5F5"
+            android:strokeWidth="1"
+            android:pathData="M49.3333,59 C44.1883,59 40.0003,54.812 40.0003,49.667 C40.0003,48.843
+40.1183,48.048 40.3193,47.286 L49.3273,56.294 L52.0333,59 L49.3333,59 Z
+M71.9383,73 L71.9593,73 L68.0003,69.04 L67.2933,68.333 L61.8133,62.854
+L61.0003,62.04 L57.9593,59 L53.2933,54.333 L45.7133,46.754 L42.4003,43.441
+L37.6293,38.669 L31.3843,32.424 L29.5873,30.628 L29.3053,30.345 L29.2943,30.356
+L26.3413,33.309 L35.1643,42.131 L36.1013,43.069 L33.0003,52 L33.0003,70.667
+C33.0003,71.95 34.0503,73 35.3333,73 L37.6663,73 C38.9503,73 40.0003,71.95
+40.0003,70.667 L40.0003,68.333 L61.3663,68.333 L61.8803,68.848 L68.3673,75.334
+L68.3673,75.334 L73.3593,80.327 L76.3123,77.374 L71.9383,73 Z" />
+</vector>
\ No newline at end of file
diff --git a/res/drawable/ic_recycle_air_on.xml b/res/drawable/ic_recycle_air_on.xml
new file mode 100644 (file)
index 0000000..ae2204d
--- /dev/null
@@ -0,0 +1,45 @@
+<?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.
+*/
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+        android:width="108dp"
+        android:height="108dp"
+        android:viewportWidth="108"
+        android:viewportHeight="108">
+
+    <path
+            android:strokeWidth="1"
+            android:pathData="M 0 0 L 108 0 L 108 108 L 0 108 Z" />
+    <path
+            android:strokeWidth="1"
+            android:pathData="M 26 26 H 82 V 82 H 26 V 26 Z" />
+    <path
+            android:fillColor="#F5F5F5"
+            android:strokeWidth="1"
+            android:pathData="M61,63.6666667 L61,59 L49.3333333,59 C44.1883333,59 40,54.8116667 40,49.6666667
+C40,44.5216667 44.1883333,40.3333333 49.3333333,40.3333333 L61,40.3333333 L61,45
+L49.3333333,45 C46.755,45 44.6666667,47.0883333 44.6666667,49.6666667
+C44.6666667,52.245 46.755,54.3333333 49.3333333,54.3333333 L61,54.3333333
+L61,49.6666667 L68,56.6666667 L61,63.6666667 L61,63.6666667 Z
+M70.1466667,38.0233333 C69.68,36.6466667 68.3733333,35.6666667
+66.8333333,35.6666667 L41.1666667,35.6666667 C39.6266667,35.6666667
+38.3433333,36.6466667 37.8533333,38.0233333 L33,52 L33,70.6666667 C33,71.95
+34.05,73 35.3333333,73 L37.6666667,73 C38.95,73 40,71.95 40,70.6666667
+L40,68.3333333 L68,68.3333333 L68,70.6666667 C68,71.95 69.05,73 70.3333333,73
+L72.6666667,73 C73.95,73 75,71.95 75,70.6666667 L75,52 L70.1466667,38.0233333
+L70.1466667,38.0233333 Z" />
+</vector>
\ No newline at end of file
diff --git a/res/drawable/ic_seat_heat_level_1.xml b/res/drawable/ic_seat_heat_level_1.xml
new file mode 100644 (file)
index 0000000..e29618f
--- /dev/null
@@ -0,0 +1,160 @@
+<?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.
+*/
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+        android:width="108dp"
+        android:height="108dp"
+        android:viewportWidth="108"
+        android:viewportHeight="108">
+
+    <group>
+        <group>
+            <group>
+                <path
+                        android:strokeWidth="1"
+                        android:pathData="M 0 0 L 108 0 L 108 108 L 0 108 Z" />
+                <group
+                        android:translateX="26.000000"
+                        android:translateY="26.000000">
+                    <path
+                            android:strokeWidth="1"
+                            android:pathData="M 0 0 L 56 0 L 56 56 L 0 56 Z" />
+                    <path
+                            android:fillColor="#F5F5F5"
+                            android:strokeWidth="1"
+                            android:pathData="M30.8533333,41.3329281 C34.8,41.3329281 38.16,38.4529281 38.7466667,34.5595948
+L44,9.33292815 L49.3333333,9.33292815 L44.0266667,35.3595948
+C43.0133333,41.8662615 37.4133333,46.6662615 30.8266667,46.6662615
+L12,46.6662615 L12,41.3329281 L30.8533333,41.3329281 Z" />
+                    <path
+                            android:fillColor="#F5F5F5"
+                            android:strokeWidth="1"
+                            android:pathData="M14.017,37.0162 C14.017,36.1012 13.805,35.7422 13.429,35.1382 C12.988,34.3862
+12.384,33.3742 12.384,31.5612 C12.384,29.7482 12.988,28.7352 13.429,28.0002
+C13.805,27.3962 14.017,27.0202 14.017,26.1222 C14.017,25.2232 13.805,24.8482
+13.429,24.2432 C12.988,23.4922 12.384,22.4792 12.384,20.6662 L15.65,20.6662
+C15.65,21.5812 15.863,21.9402 16.238,22.5452 C16.679,23.2962 17.284,24.3092
+17.284,26.1222 C17.284,27.9352 16.679,28.9472 16.238,29.6822 C15.863,30.2872
+15.65,30.6462 15.65,31.5612 C15.65,32.4752 15.863,32.8352 16.238,33.4392
+C16.679,34.1902 17.284,35.2032 17.284,37.0162 C17.284,37.0442 14.017,37.0562
+14.017,37.0162" />
+                    <path
+                            android:fillColor="#F5F5F5"
+                            android:strokeWidth="1"
+                            android:pathData="M22.517,37.0162 C22.517,36.1012 22.305,35.7422 21.929,35.1382 C21.488,34.3862
+20.884,33.3742 20.884,31.5612 C20.884,29.7482 21.488,28.7352 21.929,28.0002
+C22.305,27.3962 22.517,27.0202 22.517,26.1222 C22.517,25.2232 22.305,24.8482
+21.929,24.2432 C21.488,23.4922 20.884,22.4792 20.884,20.6662 L24.15,20.6662
+C24.15,21.5812 24.363,21.9402 24.738,22.5452 C25.179,23.2962 25.784,24.3092
+25.784,26.1222 C25.784,27.9352 25.179,28.9472 24.738,29.6822 C24.363,30.2872
+24.15,30.6462 24.15,31.5612 C24.15,32.4752 24.363,32.8352 24.738,33.4392
+C25.179,34.1902 25.784,35.2032 25.784,37.0162 C25.784,37.0442 22.517,37.0562
+22.517,37.0162" />
+                    <path
+                            android:fillColor="#F5F5F5"
+                            android:strokeWidth="1"
+                            android:pathData="M30.8002,37.0162 C30.8002,36.1012 30.5882,35.7422 30.2122,35.1382
+C29.7712,34.3862 29.1672,33.3742 29.1672,31.5612 C29.1672,29.7482
+29.7712,28.7352 30.2122,28.0002 C30.5882,27.3962 30.8002,27.0202 30.8002,26.1222
+C30.8002,25.2232 30.5882,24.8482 30.2122,24.2432 C29.7712,23.4922
+29.1672,22.4792 29.1672,20.6662 L32.4332,20.6662 C32.4332,21.5812
+32.6462,21.9402 33.0212,22.5452 C33.4622,23.2962 34.0672,24.3092 34.0672,26.1222
+C34.0672,27.9352 33.4622,28.9472 33.0212,29.6822 C32.6462,30.2872
+32.4332,30.6462 32.4332,31.5612 C32.4332,32.4752 32.6462,32.8352 33.0212,33.4392
+C33.4622,34.1902 34.0672,35.2032 34.0672,37.0162 C34.0672,37.0442
+30.8002,37.0562 30.8002,37.0162" />
+                </group>
+            </group>
+            <path
+                    android:fillColor="#6BC2B8"
+                    android:strokeWidth="1"
+                    android:pathData="M43.1206656,107.936015 C39.6520522,107.220263 36.2396976,106.15025
+32.9797533,104.759027 C29.7801594,103.392591 26.6930832,101.699781
+23.8064931,99.731205 C20.9485439,97.7822527 18.260394,95.542042
+15.8167144,93.0746084 C13.3730348,90.6082077 11.1554135,87.8939276
+9.22420082,85.0071646 C7.27355328,82.0925152 5.59806262,78.9754308
+4.24478171,75.7437023 C2.86592859,72.4531023 1.80723944,69.0086105
+1.09735512,65.5042146 C0.369058819,61.9140936 -0.000203773585,58.2186238
+-0.000203773585,54.5200556 C-0.000203773585,50.8214874 0.369058819,47.1249848
+1.09735512,43.533831 C1.43695396,41.8575461 3.05516288,40.7741062
+4.71940177,41.1201047 C6.3795491,41.4630046 7.44949002,43.1010747
+7.10989118,44.7773597 C6.46444881,47.9595123 6.13712463,51.2377183
+6.13712463,54.5200556 C6.13712463,57.8013601 6.46444881,61.079566
+7.10989118,64.2627515 C7.73896735,67.3653762 8.67593282,70.4153265
+9.89726117,73.3299759 C11.096086,76.1929838 12.5813195,78.9537414
+14.3089774,81.5378848 C16.0223149,84.0972404 17.9893287,86.5047697
+20.1558056,88.6933388 C22.3233054,90.8808752 24.7086804,92.8680422
+27.2413512,94.5959687 C29.8006172,96.3414533 32.5358199,97.8400912
+35.3702427,99.0505693 C38.2578557,100.28377 41.2784442,101.230876
+44.3501771,101.866067 C46.0113473,102.208967 47.0812882,103.846004
+46.7416894,105.522289 C46.4450518,106.988909 45.1664417,108.000051
+43.73849,108.000051 C43.5349353,108.000051 43.3283119,107.979394
+43.1206656,107.936015 Z" />
+            <path
+                    android:fillColor="#222B31"
+                    android:strokeWidth="1"
+                    android:pathData="M96.2360712,28.8658223 C95.25103,28.8658223 94.2833779,28.3876215
+93.6901028,27.5014524 C91.9767653,24.9400312 90.0097515,22.5335347
+87.8442975,20.3470313 C85.6788435,18.1594949 83.2944914,16.1733607
+80.7587518,14.4433686 C78.198463,12.697884 75.4642832,11.199246
+72.6298604,9.98876796 C69.7432702,8.75556761 66.7216589,7.80846147
+63.6489031,7.17327033 C57.3438211,5.87087029 50.6561789,5.87087029
+44.350074,7.17327033 C41.2783411,7.80846147 38.2567298,8.75556761
+35.3711625,9.98876796 C32.5357168,11.199246 29.8005141,12.697884
+27.2412482,14.4433686 C24.7065315,16.1723279 22.3232023,18.1584621
+20.1557025,20.3459984 C17.9892256,22.5345676 16.0222118,24.9420969
+14.3098972,27.5014524 C13.3606571,28.9195295 11.4539937,29.2923816
+10.0495684,28.3339143 C8.64514308,27.3764799 8.2748576,25.4502498
+9.22409773,24.0321727 C11.1553104,21.1454097 13.3729317,18.4311295
+15.8166113,15.9647288 C18.259268,13.4983281 20.9474179,11.2591502
+23.80639,9.30813225 C26.6929801,7.33955614 29.7800563,5.64777877
+32.9806731,4.28031037 C36.2395945,2.88908685 39.6509262,1.81907381
+43.1205625,1.10228901 C50.234749,-0.367429671 57.7662739,-0.367429671
+64.8794375,1.10228901 C68.3490738,1.81907381 71.7604055,2.88908685
+75.0203498,4.28031037 C78.2199437,5.64777877 81.305997,7.33852331
+84.19361,9.30813225 C87.053605,11.2601831 89.7427777,13.4993609
+92.1844116,15.9647288 C94.6250225,18.4300967 96.8436667,21.1443769
+98.7759023,24.0321727 C99.7251424,25.4502498 99.3548569,27.3764799
+97.9504316,28.3339143 C97.4246671,28.6933395 96.8273005,28.8658223
+96.2360712,28.8658223" />
+            <path
+                    android:fillColor="#222B31"
+                    android:strokeWidth="1"
+                    android:pathData="M64.2613062,108 C62.8333545,108 61.5547444,106.988858 61.2581069,105.522238
+C60.918508,103.845953 61.9884489,102.208916 63.6496192,101.866016
+C66.721352,101.230825 69.7419405,100.283719 72.6295535,99.0505184
+C75.4639763,97.8410731 78.1981561,96.3414023 80.758445,94.5959177
+C83.2931616,92.8669584 85.6775137,90.8808242 87.8439906,88.6932879
+C90.0094447,86.5057516 91.9764584,84.0992551 93.6908188,81.5378339
+C95.4184768,78.9536904 96.9037102,76.1929329 98.1025351,73.329925
+C99.3228405,70.4163084 100.260829,67.3653253 100.890928,64.2627006
+C101.535347,61.0774494 101.862672,57.7992435 101.862672,54.5200046
+C101.862672,51.239733 101.535347,47.9625599 100.890928,44.7762759
+C100.551329,43.099991 101.62127,41.4629536 103.281417,41.1200537
+C104.940542,40.7771538 106.562842,41.858528 106.903464,43.5348129
+C107.630737,47.1269995 108,50.8235021 108,54.5200046 C108,58.2154744
+107.630737,61.911977 106.903464,65.5041636 C106.192557,69.0095924
+105.133868,72.4540842 103.755015,75.7446841 C102.401734,78.9753799
+100.726243,82.0924642 98.7755954,85.0071136 C96.8433598,87.8949094
+94.6257385,90.6102224 92.1830818,93.0745575 C89.7404251,95.5409582
+87.0522753,97.7801361 84.1933031,99.7311541 C81.306713,101.700763
+78.2196368,103.39254 75.0200429,104.758976 C71.7600987,106.151232
+68.3477441,107.220213 64.8791306,107.935964 C64.6714843,107.979343 64.464861,108
+64.2613062,108" />
+        </group>
+    </group>
+</vector>
\ No newline at end of file
diff --git a/res/drawable/ic_seat_heat_level_2.xml b/res/drawable/ic_seat_heat_level_2.xml
new file mode 100644 (file)
index 0000000..3e3a875
--- /dev/null
@@ -0,0 +1,155 @@
+<?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.
+*/
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+        android:width="108dp"
+        android:height="108dp"
+        android:viewportWidth="108"
+        android:viewportHeight="108">
+
+    <group>
+        <group>
+            <group>
+                <path
+                        android:strokeWidth="1"
+                        android:pathData="M 0 0 L 108 0 L 108 108 L 0 108 Z" />
+            </group>
+            <group
+                    android:translateX="26.000000"
+                    android:translateY="26.000000">
+                <path
+                        android:strokeWidth="1"
+                        android:pathData="M 0 0 L 56 0 L 56 56 L 0 56 Z" />
+                <path
+                        android:fillColor="#F5F5F5"
+                        android:strokeWidth="1"
+                        android:pathData="M30.8533333,41.3329281 C34.8,41.3329281 38.16,38.4529281 38.7466667,34.5595948
+L44,9.33292815 L49.3333333,9.33292815 L44.0266667,35.3595948
+C43.0133333,41.8662615 37.4133333,46.6662615 30.8266667,46.6662615
+L12,46.6662615 L12,41.3329281 L30.8533333,41.3329281 Z" />
+                <path
+                        android:fillColor="#F5F5F5"
+                        android:strokeWidth="1"
+                        android:pathData="M14.017,37.0162 C14.017,36.1012 13.805,35.7422 13.429,35.1382 C12.988,34.3862
+12.384,33.3742 12.384,31.5612 C12.384,29.7482 12.988,28.7352 13.429,28.0002
+C13.805,27.3962 14.017,27.0202 14.017,26.1222 C14.017,25.2232 13.805,24.8482
+13.429,24.2432 C12.988,23.4922 12.384,22.4792 12.384,20.6662 L15.65,20.6662
+C15.65,21.5812 15.863,21.9402 16.238,22.5452 C16.679,23.2962 17.284,24.3092
+17.284,26.1222 C17.284,27.9352 16.679,28.9472 16.238,29.6822 C15.863,30.2872
+15.65,30.6462 15.65,31.5612 C15.65,32.4752 15.863,32.8352 16.238,33.4392
+C16.679,34.1902 17.284,35.2032 17.284,37.0162 C17.284,37.0442 14.017,37.0562
+14.017,37.0162" />
+                <path
+                        android:fillColor="#F5F5F5"
+                        android:strokeWidth="1"
+                        android:pathData="M22.517,37.0162 C22.517,36.1012 22.305,35.7422 21.929,35.1382 C21.488,34.3862
+20.884,33.3742 20.884,31.5612 C20.884,29.7482 21.488,28.7352 21.929,28.0002
+C22.305,27.3962 22.517,27.0202 22.517,26.1222 C22.517,25.2232 22.305,24.8482
+21.929,24.2432 C21.488,23.4922 20.884,22.4792 20.884,20.6662 L24.15,20.6662
+C24.15,21.5812 24.363,21.9402 24.738,22.5452 C25.179,23.2962 25.784,24.3092
+25.784,26.1222 C25.784,27.9352 25.179,28.9472 24.738,29.6822 C24.363,30.2872
+24.15,30.6462 24.15,31.5612 C24.15,32.4752 24.363,32.8352 24.738,33.4392
+C25.179,34.1902 25.784,35.2032 25.784,37.0162 C25.784,37.0442 22.517,37.0562
+22.517,37.0162" />
+                <path
+                        android:fillColor="#F5F5F5"
+                        android:strokeWidth="1"
+                        android:pathData="M30.8002,37.0162 C30.8002,36.1012 30.5882,35.7422 30.2122,35.1382
+C29.7712,34.3862 29.1672,33.3742 29.1672,31.5612 C29.1672,29.7482
+29.7712,28.7352 30.2122,28.0002 C30.5882,27.3962 30.8002,27.0202 30.8002,26.1222
+C30.8002,25.2232 30.5882,24.8482 30.2122,24.2432 C29.7712,23.4922
+29.1672,22.4792 29.1672,20.6662 L32.4332,20.6662 C32.4332,21.5812
+32.6462,21.9402 33.0212,22.5452 C33.4622,23.2962 34.0672,24.3092 34.0672,26.1222
+C34.0672,27.9352 33.4622,28.9472 33.0212,29.6822 C32.6462,30.2872
+32.4332,30.6462 32.4332,31.5612 C32.4332,32.4752 32.6462,32.8352 33.0212,33.4392
+C33.4622,34.1902 34.0672,35.2032 34.0672,37.0162 C34.0672,37.0442
+30.8002,37.0562 30.8002,37.0162" />
+            </group>
+            <path
+                    android:fillColor="#80CBC4"
+                    android:strokeWidth="1"
+                    android:pathData="M93.6901028,27.5014524 C91.9767653,24.9400312 90.0097515,22.5335347
+87.8442975,20.3470313 C85.6788435,18.1594949 83.2944914,16.1733607
+80.7587518,14.4433686 C78.198463,12.697884 75.4642832,11.199246
+72.6298604,9.98876796 C69.7432702,8.75556761 66.7216589,7.80846147
+63.6489031,7.17327033 C57.3438211,5.87087029 50.6561789,5.87087029
+44.350074,7.17327033 C41.2783411,7.80846147 38.2567298,8.75556761
+35.3711625,9.98876796 C32.5357168,11.199246 29.8005141,12.697884
+27.2412482,14.4433686 C24.7065315,16.1723279 22.3232023,18.1584621
+20.1557025,20.3459984 C17.9892256,22.5345676 16.0222118,24.9420969
+14.3098972,27.5014524 C13.3606571,28.9195295 11.4539937,29.2923816
+10.0495684,28.3339143 C8.64514308,27.3764799 8.2748576,25.4502498
+9.22409773,24.0321727 C11.1553104,21.1454097 13.3729317,18.4311295
+15.8166113,15.9647288 C18.259268,13.4983281 20.9474179,11.2591502
+23.80639,9.30813225 C26.6929801,7.33955614 29.7800563,5.64777877
+32.9806731,4.28031037 C36.2395945,2.88908685 39.6509262,1.81907381
+43.1205625,1.10228901 C50.234749,-0.367429671 57.7662739,-0.367429671
+64.8794375,1.10228901 C68.3490738,1.81907381 71.7604055,2.88908685
+75.0203498,4.28031037 C78.2199437,5.64777877 81.305997,7.33852331
+84.19361,9.30813225 C87.053605,11.2601831 89.7427777,13.4993609
+92.1844116,15.9647288 C94.6250225,18.4300967 96.8436667,21.1443769
+98.7759023,24.0321727 C99.7251424,25.4502498 99.3548569,27.3764799
+97.9504316,28.3339143 C97.4246671,28.6933395 96.8273005,28.8658223
+96.2360712,28.8658223 C95.25103,28.8658223 94.2833779,28.3876215
+93.6901028,27.5014524 Z M43.1208694,107.935964 C39.6522559,107.220213
+36.2399013,106.150199 32.9799571,104.758976 C29.7803632,103.39254
+26.693287,101.69973 23.8066969,99.7311541 C20.9487476,97.7822017
+18.2605978,95.541991 15.8169182,93.0745575 C13.3732386,90.6081568
+11.1556173,87.8938766 9.22440459,85.0071136 C7.27375705,82.0924642
+5.59826639,78.9753799 4.24498548,75.7436513 C2.86613237,72.4530514
+1.80744322,69.0085596 1.0975589,65.5041636 C0.369262592,61.9140426 0,58.2185729
+0,54.5200046 C0,50.8214364 0.369262592,47.1249338 1.0975589,43.53378
+C1.43715774,41.8574951 3.05536666,40.7740553 4.71960554,41.1200537
+C6.37975288,41.4629536 7.4496938,43.1010238 7.11009496,44.7773087
+C6.46465259,47.9594614 6.13732841,51.2376674 6.13732841,54.5200046
+C6.13732841,57.8013091 6.46465259,61.0795151 7.11009496,64.2627006
+C7.73917112,67.3653253 8.67613659,70.4152756 9.89746494,73.329925
+C11.0962898,76.1929329 12.5815232,78.9536904 14.3091812,81.5378339
+C16.0225187,84.0971894 17.9895324,86.5047187 20.1560094,88.6932879
+C22.3235092,90.8808242 24.7088842,92.8679913 27.241555,94.5959177
+C29.800821,96.3414023 32.5360237,97.8400403 35.3704465,99.0505184
+C38.2580595,100.283719 41.278648,101.230825 44.3503808,101.866016
+C46.0115511,102.208916 47.081492,103.845953 46.7418931,105.522238
+C46.4452556,106.988858 45.1666455,108 43.7386938,108 C43.535139,108
+43.3285157,107.979343 43.1208694,107.935964 Z" />
+            <path
+                    android:fillColor="#222B31"
+                    android:strokeWidth="1"
+                    android:pathData="M64.2613062,108 C62.8333545,108 61.5547444,106.988858 61.2581069,105.522238
+C60.918508,103.845953 61.9884489,102.208916 63.6496192,101.866016
+C66.721352,101.230825 69.7419405,100.283719 72.6295535,99.0505184
+C75.4639763,97.8410731 78.1981561,96.3414023 80.758445,94.5959177
+C83.2931616,92.8669584 85.6775137,90.8808242 87.8439906,88.6932879
+C90.0094447,86.5057516 91.9764584,84.0992551 93.6908188,81.5378339
+C95.4184768,78.9536904 96.9037102,76.1929329 98.1025351,73.329925
+C99.3228405,70.4163084 100.260829,67.3653253 100.890928,64.2627006
+C101.535347,61.0774494 101.862672,57.7992435 101.862672,54.5200046
+C101.862672,51.239733 101.535347,47.9625599 100.890928,44.7762759
+C100.551329,43.099991 101.62127,41.4629536 103.281417,41.1200537
+C104.940542,40.7771538 106.562842,41.858528 106.903464,43.5348129
+C107.630737,47.1269995 108,50.8235021 108,54.5200046 C108,58.2154744
+107.630737,61.911977 106.903464,65.5041636 C106.192557,69.0095924
+105.133868,72.4540842 103.755015,75.7446841 C102.401734,78.9753799
+100.726243,82.0924642 98.7755954,85.0071136 C96.8433598,87.8949094
+94.6257385,90.6102224 92.1830818,93.0745575 C89.7404251,95.5409582
+87.0522753,97.7801361 84.1933031,99.7311541 C81.306713,101.700763
+78.2196368,103.39254 75.0200429,104.758976 C71.7600987,106.151232
+68.3477441,107.220213 64.8791306,107.935964 C64.6714843,107.979343 64.464861,108
+64.2613062,108" />
+        </group>
+    </group>
+</vector>
\ No newline at end of file
diff --git a/res/drawable/ic_seat_heat_level_3.xml b/res/drawable/ic_seat_heat_level_3.xml
new file mode 100644 (file)
index 0000000..1ddf181
--- /dev/null
@@ -0,0 +1,151 @@
+<?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.
+*/
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+        android:width="108dp"
+        android:height="108dp"
+        android:viewportWidth="108"
+        android:viewportHeight="108">
+
+    <group>
+        <group>
+            <group>
+                <path
+                        android:strokeWidth="1"
+                        android:pathData="M 0 0 L 108 0 L 108 108 L 0 108 Z" />
+                <group
+                        android:translateX="26.000000"
+                        android:translateY="26.000000">
+                    <path
+                            android:strokeWidth="1"
+                            android:pathData="M 0 0 L 56 0 L 56 56 L 0 56 Z" />
+                    <path
+                            android:fillColor="#F5F5F5"
+                            android:strokeWidth="1"
+                            android:pathData="M30.8533333,41.3329281 C34.8,41.3329281 38.16,38.4529281 38.7466667,34.5595948
+L44,9.33292815 L49.3333333,9.33292815 L44.0266667,35.3595948
+C43.0133333,41.8662615 37.4133333,46.6662615 30.8266667,46.6662615
+L12,46.6662615 L12,41.3329281 L30.8533333,41.3329281 Z" />
+                    <path
+                            android:fillColor="#F5F5F5"
+                            android:strokeWidth="1"
+                            android:pathData="M14.017,37.0162 C14.017,36.1012 13.805,35.7422 13.429,35.1382 C12.988,34.3862
+12.384,33.3742 12.384,31.5612 C12.384,29.7482 12.988,28.7352 13.429,28.0002
+C13.805,27.3962 14.017,27.0202 14.017,26.1222 C14.017,25.2232 13.805,24.8482
+13.429,24.2432 C12.988,23.4922 12.384,22.4792 12.384,20.6662 L15.65,20.6662
+C15.65,21.5812 15.863,21.9402 16.238,22.5452 C16.679,23.2962 17.284,24.3092
+17.284,26.1222 C17.284,27.9352 16.679,28.9472 16.238,29.6822 C15.863,30.2872
+15.65,30.6462 15.65,31.5612 C15.65,32.4752 15.863,32.8352 16.238,33.4392
+C16.679,34.1902 17.284,35.2032 17.284,37.0162 C17.284,37.0442 14.017,37.0562
+14.017,37.0162" />
+                    <path
+                            android:fillColor="#F5F5F5"
+                            android:strokeWidth="1"
+                            android:pathData="M22.517,37.0162 C22.517,36.1012 22.305,35.7422 21.929,35.1382 C21.488,34.3862
+20.884,33.3742 20.884,31.5612 C20.884,29.7482 21.488,28.7352 21.929,28.0002
+C22.305,27.3962 22.517,27.0202 22.517,26.1222 C22.517,25.2232 22.305,24.8482
+21.929,24.2432 C21.488,23.4922 20.884,22.4792 20.884,20.6662 L24.15,20.6662
+C24.15,21.5812 24.363,21.9402 24.738,22.5452 C25.179,23.2962 25.784,24.3092
+25.784,26.1222 C25.784,27.9352 25.179,28.9472 24.738,29.6822 C24.363,30.2872
+24.15,30.6462 24.15,31.5612 C24.15,32.4752 24.363,32.8352 24.738,33.4392
+C25.179,34.1902 25.784,35.2032 25.784,37.0162 C25.784,37.0442 22.517,37.0562
+22.517,37.0162" />
+                    <path
+                            android:fillColor="#F5F5F5"
+                            android:strokeWidth="1"
+                            android:pathData="M30.8002,37.0162 C30.8002,36.1012 30.5882,35.7422 30.2122,35.1382
+C29.7712,34.3862 29.1672,33.3742 29.1672,31.5612 C29.1672,29.7482
+29.7712,28.7352 30.2122,28.0002 C30.5882,27.3962 30.8002,27.0202 30.8002,26.1222
+C30.8002,25.2232 30.5882,24.8482 30.2122,24.2432 C29.7712,23.4922
+29.1672,22.4792 29.1672,20.6662 L32.4332,20.6662 C32.4332,21.5812
+32.6462,21.9402 33.0212,22.5452 C33.4622,23.2962 34.0672,24.3092 34.0672,26.1222
+C34.0672,27.9352 33.4622,28.9472 33.0212,29.6822 C32.6462,30.2872
+32.4332,30.6462 32.4332,31.5612 C32.4332,32.4752 32.6462,32.8352 33.0212,33.4392
+C33.4622,34.1902 34.0672,35.2032 34.0672,37.0162 C34.0672,37.0442
+30.8002,37.0562 30.8002,37.0162" />
+                </group>
+            </group>
+            <path
+                    android:fillColor="#80CBC4"
+                    android:strokeWidth="1"
+                    android:pathData="M96.2360712,28.8658223 C95.25103,28.8658223 94.2833779,28.3876215
+93.6901028,27.5014524 C91.9767653,24.9400312 90.0097515,22.5335347
+87.8442975,20.3470313 C85.6788435,18.1594949 83.2944914,16.1733607
+80.7587518,14.4433686 C78.198463,12.697884 75.4642832,11.199246
+72.6298604,9.98876796 C69.7432702,8.75556761 66.7216589,7.80846147
+63.6489031,7.17327033 C57.3438211,5.87087029 50.6561789,5.87087029
+44.350074,7.17327033 C41.2783411,7.80846147 38.2567298,8.75556761
+35.3711625,9.98876796 C32.5357168,11.199246 29.8005141,12.697884
+27.2412482,14.4433686 C24.7065315,16.1723279 22.3232023,18.1584621
+20.1557025,20.3459984 C17.9892256,22.5345676 16.0222118,24.9420969
+14.3098972,27.5014524 C13.3606571,28.9195295 11.4539937,29.2923816
+10.0495684,28.3339143 C8.64514308,27.3764799 8.2748576,25.4502498
+9.22409773,24.0321727 C11.1553104,21.1454097 13.3729317,18.4311295
+15.8166113,15.9647288 C18.259268,13.4983281 20.9474179,11.2591502
+23.80639,9.30813225 C26.6929801,7.33955614 29.7800563,5.64777877
+32.9806731,4.28031037 C36.2395945,2.88908685 39.6509262,1.81907381
+43.1205625,1.10228901 C50.234749,-0.367429671 57.7662739,-0.367429671
+64.8794375,1.10228901 C68.3490738,1.81907381 71.7604055,2.88908685
+75.0203498,4.28031037 C78.2199437,5.64777877 81.305997,7.33852331
+84.19361,9.30813225 C87.053605,11.2601831 89.7427777,13.4993609
+92.1844116,15.9647288 C94.6250225,18.4300967 96.8436667,21.1443769
+98.7759023,24.0321727 C99.7251424,25.4502498 99.3548569,27.3764799
+97.9504316,28.3339143 C97.4246671,28.6933395 96.8273005,28.8658223
+96.2360712,28.8658223 Z M43.7386938,108 C43.535139,108 43.3285157,107.979343
+43.1208694,107.935964 C39.6522559,107.220213 36.2399013,106.150199
+32.9799571,104.758976 C29.7803632,103.39254 26.693287,101.69973
+23.8066969,99.7311541 C20.9487476,97.7822017 18.2605978,95.541991
+15.8169182,93.0745575 C13.3732386,90.6081568 11.1556173,87.8938766
+9.22440459,85.0071136 C7.27375705,82.0924642 5.59826639,78.9753799
+4.24498548,75.7436513 C2.86613237,72.4530514 1.80744322,69.0085596
+1.0975589,65.5041636 C0.369262592,61.9140426 0,58.2185729 0,54.5200046
+C0,50.8214364 0.369262592,47.1249338 1.0975589,43.53378 C1.43715774,41.8574951
+3.05536666,40.7740553 4.71960554,41.1200537 C6.37975288,41.4629536
+7.4496938,43.1010238 7.11009496,44.7773087 C6.46465259,47.9594614
+6.13732841,51.2376674 6.13732841,54.5200046 C6.13732841,57.8013091
+6.46465259,61.0795151 7.11009496,64.2627006 C7.73917112,67.3653253
+8.67613659,70.4152756 9.89746494,73.329925 C11.0962898,76.1929329
+12.5815232,78.9536904 14.3091812,81.5378339 C16.0225187,84.0971894
+17.9895324,86.5047187 20.1560094,88.6932879 C22.3235092,90.8808242
+24.7088842,92.8679913 27.241555,94.5959177 C29.800821,96.3414023
+32.5360237,97.8400403 35.3704465,99.0505184 C38.2580595,100.283719
+41.278648,101.230825 44.3503808,101.866016 C46.0115511,102.208916
+47.081492,103.845953 46.7418931,105.522238 C46.4452556,106.988858 45.1666455,108
+43.7386938,108 Z M64.2613062,108 C62.8333545,108 61.5547444,106.988858
+61.2581069,105.522238 C60.918508,103.845953 61.9884489,102.208916
+63.6496192,101.866016 C66.721352,101.230825 69.7419405,100.283719
+72.6295535,99.0505184 C75.4639763,97.8410731 78.1981561,96.3414023
+80.758445,94.5959177 C83.2931616,92.8669584 85.6775137,90.8808242
+87.8439906,88.6932879 C90.0094447,86.5057516 91.9764584,84.0992551
+93.6908188,81.5378339 C95.4184768,78.9536904 96.9037102,76.1929329
+98.1025351,73.329925 C99.3228405,70.4163084 100.260829,67.3653253
+100.890928,64.2627006 C101.535347,61.0774494 101.862672,57.7992435
+101.862672,54.5200046 C101.862672,51.239733 101.535347,47.9625599
+100.890928,44.7762759 C100.551329,43.099991 101.62127,41.4629536
+103.281417,41.1200537 C104.940542,40.7771538 106.562842,41.858528
+106.903464,43.5348129 C107.630737,47.1269995 108,50.8235021 108,54.5200046
+C108,58.2154744 107.630737,61.911977 106.903464,65.5041636
+C106.192557,69.0095924 105.133868,72.4540842 103.755015,75.7446841
+C102.401734,78.9753799 100.726243,82.0924642 98.7755954,85.0071136
+C96.8433598,87.8949094 94.6257385,90.6102224 92.1830818,93.0745575
+C89.7404251,95.5409582 87.0522753,97.7801361 84.1933031,99.7311541
+C81.306713,101.700763 78.2196368,103.39254 75.0200429,104.758976
+C71.7600987,106.151232 68.3477441,107.220213 64.8791306,107.935964
+C64.6714843,107.979343 64.464861,108 64.2613062,108 Z" />
+        </group>
+    </group>
+</vector>
\ No newline at end of file
diff --git a/res/drawable/ic_seat_heat_off.xml b/res/drawable/ic_seat_heat_off.xml
new file mode 100644 (file)
index 0000000..f076606
--- /dev/null
@@ -0,0 +1,150 @@
+<?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.
+*/
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+        android:width="108dp"
+        android:height="108dp"
+        android:viewportWidth="108"
+        android:viewportHeight="108">
+
+    <group>
+        <group>
+            <group>
+                <path
+                        android:strokeWidth="1"
+                        android:pathData="M 0 0 L 108 0 L 108 108 L 0 108 Z" />
+                <path
+                        android:fillColor="#8FF5F5F5"
+                        android:strokeWidth="1"
+                        android:pathData="M70.0267,61.3596 L75.3337,35.3326 L69.9997,35.3326 L65.4707,57.0806
+L69.9797,61.5896 C69.9937,61.5126 70.0147,61.4376 70.0267,61.3596" />
+                <path
+                        android:fillColor="#8FF5F5F5"
+                        android:strokeWidth="1"
+                        android:pathData="M38.384,57.5612 C38.384,59.3742 38.988,60.3862 39.429,61.1382 C39.805,61.7422
+40.017,62.1012 40.017,63.0162 C40.017,63.0562 43.284,63.0442 43.284,63.0162
+C43.284,61.2032 42.679,60.1902 42.238,59.4392 C41.863,58.8352 41.65,58.4752
+41.65,57.5612 C41.65,56.6462 41.863,56.2872 42.238,55.6822 C42.679,54.9472
+43.284,53.9352 43.284,52.1222 C43.284,50.3092 42.679,49.2962 42.238,48.5452
+C41.863,47.9402 41.65,47.5812 41.65,46.6662 L38.384,46.6662 C38.384,48.4792
+38.988,49.4922 39.429,50.2432 C39.805,50.8482 40.017,51.2232 40.017,52.1222
+C40.017,53.0202 39.805,53.3962 39.429,54.0002 C38.988,54.7352 38.384,55.7482
+38.384,57.5612" />
+                <path
+                        android:fillColor="#8FF5F5F5"
+                        android:strokeWidth="1"
+                        android:pathData="M60.0478,51.6578 C59.9538,50.1358 59.4228,49.2288 59.0208,48.5448
+C58.6458,47.9398 58.4328,47.5808 58.4328,46.6658 L55.1668,46.6658
+C55.1668,46.7068 55.1718,46.7418 55.1718,46.7818 L60.0478,51.6578 Z" />
+                <path
+                        android:fillColor="#8FF5F5F5"
+                        android:strokeWidth="1"
+                        android:pathData="M64.4026,61.9626 L58.6996,56.2596 L56.2976,53.8576 L50.4226,47.9826
+L49.2616,46.8216 L49.1066,46.6666 L46.6586,44.2186 L36.4856,34.0456
+L33.5226,37.0086 L48.5086,51.9956 C48.5096,52.0406 48.5166,52.0736
+48.5166,52.1226 C48.5166,53.0206 48.3046,53.3966 47.9286,54.0006
+C47.4876,54.7356 46.8836,55.7486 46.8836,57.5616 C46.8836,59.3746
+47.4876,60.3866 47.9286,61.1386 C48.3046,61.7426 48.5166,62.1016 48.5166,63.0166
+C48.5166,63.0566 51.7836,63.0446 51.7836,63.0166 C51.7836,61.2036
+51.1786,60.1906 50.7376,59.4396 C50.3626,58.8356 50.1496,58.4756 50.1496,57.5616
+C50.1496,56.6466 50.3626,56.2876 50.7376,55.6826 C50.8996,55.4136
+51.0826,55.1056 51.2496,54.7366 L55.2646,58.7516 C55.4526,59.8506
+55.8786,60.5696 56.2126,61.1386 C56.5886,61.7426 56.8006,62.1016 56.8006,63.0166
+C56.8006,63.0456 58.6256,63.0466 59.5476,63.0336 L61.9816,65.4676
+C60.5856,66.6356 58.7916,67.3326 56.8536,67.3326 L37.9996,67.3326
+L37.9996,72.6666 L56.8266,72.6666 C60.2326,72.6666 63.3686,71.3756
+65.7486,69.2356 L72.1886,75.6756 L75.1526,72.7126 L68.4046,65.9646
+L64.4026,61.9626 Z" />
+            </group>
+            <path
+                    android:fillColor="#222B31"
+                    android:strokeWidth="1"
+                    android:pathData="M96.2360712,28.8658223 C95.25103,28.8658223 94.2833779,28.3876215
+93.6901028,27.5014524 C91.9767653,24.9400312 90.0097515,22.5335347
+87.8442975,20.3470313 C85.6788435,18.1594949 83.2944914,16.1733607
+80.7587518,14.4433686 C78.198463,12.697884 75.4642832,11.199246
+72.6298604,9.98876796 C69.7432702,8.75556761 66.7216589,7.80846147
+63.6489031,7.17327033 C57.3438211,5.87087029 50.6561789,5.87087029
+44.350074,7.17327033 C41.2783411,7.80846147 38.2567298,8.75556761
+35.3711625,9.98876796 C32.5357168,11.199246 29.8005141,12.697884
+27.2412482,14.4433686 C24.7065315,16.1723279 22.3232023,18.1584621
+20.1557025,20.3459984 C17.9892256,22.5345676 16.0222118,24.9420969
+14.3098972,27.5014524 C13.3606571,28.9195295 11.4539937,29.2923816
+10.0495684,28.3339143 C8.64514308,27.3764799 8.2748576,25.4502498
+9.22409773,24.0321727 C11.1553104,21.1454097 13.3729317,18.4311295
+15.8166113,15.9647288 C18.259268,13.4983281 20.9474179,11.2591502
+23.80639,9.30813225 C26.6929801,7.33955614 29.7800563,5.64777877
+32.9806731,4.28031037 C36.2395945,2.88908685 39.6509262,1.81907381
+43.1205625,1.10228901 C50.234749,-0.367429671 57.7662739,-0.367429671
+64.8794375,1.10228901 C68.3490738,1.81907381 71.7604055,2.88908685
+75.0203498,4.28031037 C78.2199437,5.64777877 81.305997,7.33852331
+84.19361,9.30813225 C87.053605,11.2601831 89.7427777,13.4993609
+92.1844116,15.9647288 C94.6250225,18.4300967 96.8436667,21.1443769
+98.7759023,24.0321727 C99.7251424,25.4502498 99.3548569,27.3764799
+97.9504316,28.3339143 C97.4246671,28.6933395 96.8273005,28.8658223
+96.2360712,28.8658223" />
+            <path
+                    android:fillColor="#222B31"
+                    android:strokeWidth="1"
+                    android:pathData="M43.7386938,108 C43.535139,108 43.3285157,107.979343 43.1208694,107.935964
+C39.6522559,107.220213 36.2399013,106.150199 32.9799571,104.758976
+C29.7803632,103.39254 26.693287,101.69973 23.8066969,99.7311541
+C20.9487476,97.7822017 18.2605978,95.541991 15.8169182,93.0745575
+C13.3732386,90.6081568 11.1556173,87.8938766 9.22440459,85.0071136
+C7.27375705,82.0924642 5.59826639,78.9753799 4.24498548,75.7436513
+C2.86613237,72.4530514 1.80744322,69.0085596 1.0975589,65.5041636
+C0.369262592,61.9140426 0,58.2185729 0,54.5200046 C0,50.8214364
+0.369262592,47.1249338 1.0975589,43.53378 C1.43715774,41.8574951
+3.05536666,40.7740553 4.71960554,41.1200537 C6.37975288,41.4629536
+7.4496938,43.1010238 7.11009496,44.7773087 C6.46465259,47.9594614
+6.13732841,51.2376674 6.13732841,54.5200046 C6.13732841,57.8013091
+6.46465259,61.0795151 7.11009496,64.2627006 C7.73917112,67.3653253
+8.67613659,70.4152756 9.89746494,73.329925 C11.0962898,76.1929329
+12.5815232,78.9536904 14.3091812,81.5378339 C16.0225187,84.0971894
+17.9895324,86.5047187 20.1560094,88.6932879 C22.3235092,90.8808242
+24.7088842,92.8679913 27.241555,94.5959177 C29.800821,96.3414023
+32.5360237,97.8400403 35.3704465,99.0505184 C38.2580595,100.283719
+41.278648,101.230825 44.3503808,101.866016 C46.0115511,102.208916
+47.081492,103.845953 46.7418931,105.522238 C46.4452556,106.988858 45.1666455,108
+43.7386938,108" />
+            <path
+                    android:fillColor="#222B31"
+                    android:strokeWidth="1"
+                    android:pathData="M64.2613062,108 C62.8333545,108 61.5547444,106.988858 61.2581069,105.522238
+C60.918508,103.845953 61.9884489,102.208916 63.6496192,101.866016
+C66.721352,101.230825 69.7419405,100.283719 72.6295535,99.0505184
+C75.4639763,97.8410731 78.1981561,96.3414023 80.758445,94.5959177
+C83.2931616,92.8669584 85.6775137,90.8808242 87.8439906,88.6932879
+C90.0094447,86.5057516 91.9764584,84.0992551 93.6908188,81.5378339
+C95.4184768,78.9536904 96.9037102,76.1929329 98.1025351,73.329925
+C99.3228405,70.4163084 100.260829,67.3653253 100.890928,64.2627006
+C101.535347,61.0774494 101.862672,57.7992435 101.862672,54.5200046
+C101.862672,51.239733 101.535347,47.9625599 100.890928,44.7762759
+C100.551329,43.099991 101.62127,41.4629536 103.281417,41.1200537
+C104.940542,40.7771538 106.562842,41.858528 106.903464,43.5348129
+C107.630737,47.1269995 108,50.8235021 108,54.5200046 C108,58.2154744
+107.630737,61.911977 106.903464,65.5041636 C106.192557,69.0095924
+105.133868,72.4540842 103.755015,75.7446841 C102.401734,78.9753799
+100.726243,82.0924642 98.7755954,85.0071136 C96.8433598,87.8949094
+94.6257385,90.6102224 92.1830818,93.0745575 C89.7404251,95.5409582
+87.0522753,97.7801361 84.1933031,99.7311541 C81.306713,101.700763
+78.2196368,103.39254 75.0200429,104.758976 C71.7600987,106.151232
+68.3477441,107.220213 64.8791306,107.935964 C64.6714843,107.979343 64.464861,108
+64.2613062,108" />
+        </group>
+    </group>
+</vector>
\ No newline at end of file
diff --git a/res/drawable/ripple_background.xml b/res/drawable/ripple_background.xml
new file mode 100644 (file)
index 0000000..3bda80d
--- /dev/null
@@ -0,0 +1,20 @@
+<?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.
+*/
+-->
+<ripple xmlns:android="http://schemas.android.com/apk/res/android"
+        android:color="@color/car_card_ripple_light_color_background">
+</ripple>
diff --git a/res/drawable/temperature_bar_background.xml b/res/drawable/temperature_bar_background.xml
new file mode 100644 (file)
index 0000000..d91dbb5
--- /dev/null
@@ -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.
+*/
+-->
+<shape xmlns:android="http://schemas.android.com/apk/res/android">
+    <corners
+            android:radius="@dimen/temperature_bar_bg_radius"/>
+</shape>
\ No newline at end of file
diff --git a/res/layout/fan_direction.xml b/res/layout/fan_direction.xml
new file mode 100644 (file)
index 0000000..a3e15fc
--- /dev/null
@@ -0,0 +1,43 @@
+<?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.
+*/
+-->
+<merge xmlns:android="http://schemas.android.com/apk/res/android"
+       android:layout_width="match_parent"
+       android:layout_height="match_parent">
+    <ImageView
+            android:id="@+id/direction_1"
+            android:src="@drawable/ic_fan_direction_1_off"
+            style="@style/HvacButton.FanDirection"/>
+    <View
+            style="@style/HvacSpacerView"/>
+    <ImageView
+            android:id="@+id/direction_2"
+            android:src="@drawable/ic_fan_direction_2_off"
+            style="@style/HvacButton.FanDirection"/>
+    <View
+            style="@style/HvacSpacerView"/>
+    <ImageView
+            android:id="@+id/direction_3"
+            android:src="@drawable/ic_fan_direction_3_off"
+            style="@style/HvacButton.FanDirection"/>
+    <View
+            style="@style/HvacSpacerView"/>
+    <ImageView
+            android:id="@+id/direction_4"
+            android:src="@drawable/ic_fan_direction_4_off"
+            style="@style/HvacButton.FanDirection"/>
+</merge>
\ No newline at end of file
diff --git a/res/layout/fan_speed.xml b/res/layout/fan_speed.xml
new file mode 100644 (file)
index 0000000..1252ffd
--- /dev/null
@@ -0,0 +1,56 @@
+<?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.
+*/
+-->
+<merge xmlns:android="http://schemas.android.com/apk/res/android"
+       android:layout_width="match_parent"
+       android:layout_height="match_parent">
+
+    <LinearLayout
+            android:layout_centerInParent="true"
+            android:id="@+id/segment_container"
+            android:orientation="horizontal"
+            android:background="@drawable/fan_speed_inset"
+            android:layout_width="@dimen/hvac_fan_speed_bar_width"
+            android:layout_height="@dimen/hvac_fan_speed_bar_height"
+            android:layout_marginStart="@dimen/hvac_fan_speed_bar_margin"
+            android:layout_marginEnd="@dimen/hvac_fan_speed_bar_margin">
+        <TextView
+                android:id="@+id/fan_off"
+                android:text="@string/fan_speed_off"
+                android:layout_marginStart="@dimen/hvac_fan_speed_bar_end_button_margin"
+                android:layout_marginEnd="@dimen/hvac_fan_speed_bar_segment_margin"
+                style="@style/HvacFanSpeedText"/>
+        <com.android.car.hvac.ui.FanSpeedBarSegment
+                android:id="@+id/fan_speed_1"
+                style="@style/HvacFanSpeedBar"/>
+        <com.android.car.hvac.ui.FanSpeedBarSegment
+                android:id="@+id/fan_speed_2"
+                style="@style/HvacFanSpeedBar"/>
+        <com.android.car.hvac.ui.FanSpeedBarSegment
+                android:id="@+id/fan_speed_3"
+                style="@style/HvacFanSpeedBar"/>
+        <com.android.car.hvac.ui.FanSpeedBarSegment
+                android:id="@+id/fan_speed_4"
+                style="@style/HvacFanSpeedBar"/>
+        <TextView
+                android:id="@+id/fan_max"
+                android:text="@string/fan_speed_max"
+                android:layout_marginEnd="@dimen/hvac_fan_speed_bar_end_button_margin"
+                android:layout_marginStart="@dimen/hvac_fan_speed_bar_segment_margin"
+                style="@style/HvacFanSpeedText"/>
+    </LinearLayout>
+</merge>
\ No newline at end of file
diff --git a/res/layout/hvac_panel.xml b/res/layout/hvac_panel.xml
new file mode 100644 (file)
index 0000000..b32c73d
--- /dev/null
@@ -0,0 +1,143 @@
+<?xml version="1.0" encoding="utf-8"?>\r
+<!--\r
+* Copyright (c) 2016, The Android Open Source Project\r
+*\r
+* Licensed under the Apache License, Version 2.0 (the "License");\r
+* you may not use this file except in compliance with the License.\r
+* You may obtain a copy of the License at\r
+*\r
+*     http://www.apache.org/licenses/LICENSE-2.0\r
+*\r
+* Unless required by applicable law or agreed to in writing, software\r
+* distributed under the License is distributed on an "AS IS" BASIS,\r
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r
+* See the License for the specific language governing permissions and\r
+* limitations under the License.\r
+*/\r
+-->\r
+\r
+<!-- The HVAC panel is added as an overlay in the WindowManager, so animating\r
+the container height would cause janking. The FrameLayout allows us to maintain constant height\r
+and animate only the children -->\r
+<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"\r
+             android:layout_width="@dimen/hvac_center_panel_width"\r
+             android:layout_height="@dimen/car_hvac_panel_full_expanded_height"\r
+             android:background="@color/hvac_scrim_color">\r
+    <!-- A dark background that animates in to cover the facet buttons-->\r
+    <FrameLayout\r
+            android:id="@+id/hvac_center_panel"\r
+            android:layout_width="match_parent"\r
+            android:layout_height="@dimen/car_hvac_panel_full_expanded_height"\r
+            android:layout_gravity="bottom"\r
+            android:background="@color/hvac_background_color">\r
+        <View\r
+                android:id="@+id/fan_control_bg"\r
+                android:background="@drawable/hvac_panel_button_bg"\r
+                android:layout_width="@dimen/hvac_panel_center_group_width"\r
+                android:layout_height="@dimen/hvac_panel_group_height"\r
+                android:layout_alignParentTop="true"\r
+                android:layout_marginTop="@dimen/hvac_panel_row_margin"\r
+                android:layout_gravity="center_horizontal"\r
+                android:alpha="0"\r
+        />\r
+    </FrameLayout>\r
+    <RelativeLayout\r
+            android:id="@+id/fan_control"\r
+            android:layout_width="@dimen/hvac_center_panel_width"\r
+            android:layout_height="@dimen/car_hvac_panel_full_expanded_height"\r
+            android:layout_gravity="center_horizontal">\r
+        <com.android.car.hvac.ui.HvacPanelRow\r
+                android:id="@+id/top_row"\r
+                android:layout_alignParentTop="true"\r
+                android:layout_centerHorizontal="true"\r
+                android:layout_width="match_parent"\r
+                android:layout_height="wrap_content"\r
+                android:layout_marginTop="@dimen/hvac_panel_row_margin"\r
+                android:alpha="0"\r
+                android:layout_marginBottom="@dimen/hvac_panel_row_animation_height_shift">\r
+            <com.android.car.hvac.ui.ToggleButton\r
+                    android:id="@+id/recycle_air_button"\r
+                    android:scaleType="center"\r
+                    android:layout_width="@dimen/hvac_panel_button_dimen"\r
+                    android:layout_height="@dimen/hvac_panel_group_height"\r
+                    android:background="@drawable/hvac_panel_button_bg"/>\r
+            <View style="@style/HvacSpacerView"/>\r
+            <RelativeLayout\r
+                    android:id="@+id/fan_group"\r
+                    android:layout_width="@dimen/hvac_panel_center_group_width"\r
+                    android:layout_height="@dimen/hvac_panel_group_height"\r
+                    android:layout_alignParentTop="true"\r
+                    android:layout_marginStart="@dimen/hvac_panel_center_group_margin"\r
+                    android:layout_marginEnd="@dimen/hvac_panel_center_group_margin"\r
+                    android:layout_centerHorizontal="true">\r
+\r
+                <com.android.car.hvac.ui.FanSpeedBar\r
+                        android:id="@+id/fan_speed_bar"\r
+                        android:layout_marginTop="@dimen/hvac_fan_speed_top_margin"\r
+                        android:layout_centerHorizontal="true"\r
+                        android:layout_alignParentTop="true"\r
+                        android:layout_width="match_parent"\r
+                        android:layout_height="wrap_content"/>\r
+\r
+                <com.android.car.hvac.ui.FanDirectionButtons\r
+                        android:id="@+id/fan_direction_buttons"\r
+                        android:layout_centerHorizontal="true"\r
+                        android:layout_alignParentBottom="true"\r
+                        android:layout_marginStart="@dimen/hvac_panel_fan_direction_margin"\r
+                        android:layout_marginEnd="@dimen/hvac_panel_fan_direction_margin"\r
+                        android:layout_marginBottom="@dimen/hvac_panel_fan_direction_margin"\r
+                        android:layout_width="wrap_content"\r
+                        android:layout_height="wrap_content"/>\r
+\r
+            </RelativeLayout>\r
+            <View style="@style/HvacSpacerView"/>\r
+            <com.android.car.hvac.ui.ToggleButton\r
+                    android:id="@+id/ac_button"\r
+                    android:scaleType="center"\r
+                    android:layout_width="@dimen/hvac_panel_button_dimen"\r
+                    android:layout_height="@dimen/hvac_panel_group_height"\r
+                    android:background="@drawable/hvac_panel_button_bg"/>\r
+        </com.android.car.hvac.ui.HvacPanelRow>\r
+\r
+        <com.android.car.hvac.ui.HvacPanelRow\r
+                android:id="@+id/bottom_row"\r
+                android:layout_centerHorizontal="true"\r
+                android:layout_below="@+id/top_row"\r
+                android:layout_width="match_parent"\r
+                android:layout_height="wrap_content"\r
+                android:alpha="0"\r
+                android:translationY="@dimen/hvac_panel_row_animation_height_shift"\r
+                android:layout_marginTop="@dimen/hvac_panel_margin"\r
+                android:layout_marginBottom="@dimen/hvac_panel_row_margin">\r
+            <com.android.car.hvac.ui.SeatWarmerButton\r
+                    android:id="@+id/left_seat_heater"\r
+                    android:src="@drawable/ic_seat_heat_off"\r
+                    android:layout_marginEnd="@dimen/hvac_panel_margin"\r
+                    style="@style/HvacButton"/>\r
+            <com.android.car.hvac.ui.ToggleButton\r
+                    android:id="@+id/front_defroster"\r
+                    android:background="@drawable/hvac_panel_button_bg"\r
+                    android:layout_marginEnd="@dimen/hvac_panel_margin"\r
+                    style="@style/HvacButton"/>\r
+            <ImageView\r
+                    android:id="@+id/auto_button"\r
+                    android:background="@drawable/hvac_panel_button_bg"\r
+                    android:src="@drawable/ic_auto_off"\r
+                    android:scaleType="center"\r
+                    android:layout_gravity="center"\r
+                    android:layout_width="0dp"\r
+                    android:layout_weight="1"\r
+                    style="@style/HvacButton"/>\r
+            <com.android.car.hvac.ui.ToggleButton\r
+                    android:id="@+id/rear_defroster"\r
+                    android:background="@drawable/hvac_panel_button_bg"\r
+                    android:layout_marginStart="@dimen/hvac_panel_margin"\r
+                    style="@style/HvacButton"/>\r
+            <com.android.car.hvac.ui.SeatWarmerButton\r
+                    android:id="@+id/right_seat_heater"\r
+                    android:src="@drawable/ic_seat_heat_off"\r
+                    android:layout_marginStart="@dimen/hvac_panel_margin"\r
+                    style="@style/HvacButton"/>\r
+        </com.android.car.hvac.ui.HvacPanelRow>\r
+    </RelativeLayout>\r
+</FrameLayout>
\ No newline at end of file
diff --git a/res/layout/hvac_temperature_bar_overlay.xml b/res/layout/hvac_temperature_bar_overlay.xml
new file mode 100644 (file)
index 0000000..c2df692
--- /dev/null
@@ -0,0 +1,77 @@
+<?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.
+*/
+-->
+
+<!-- The HVAC panel is added as an overlay in the WindowManager, so animating
+the container height would cause janking. The FrameLayout allows us to maintain constant height
+and animate only the children -->
+<com.android.car.hvac.ui.TemperatureBarOverlay
+        xmlns:android="http://schemas.android.com/apk/res/android"
+        android:layout_width="@dimen/temperature_bar_width_expanded"
+        android:layout_height="@dimen/car_hvac_panel_full_expanded_height">
+    <TextView
+            android:id="@+id/floating_temperature_text"
+            android:textColor="@color/hvac_temperature_floating_text_color"
+            android:textSize="@dimen/temperature_bar_floating_text_size"
+            android:layout_gravity="bottom"
+            android:layout_marginBottom="@dimen/temperature_bar_floating_text_bottom_margin"
+            android:layout_marginLeft="@dimen/hvac_floating_text_left_margin"
+            style="@style/HvacTemperature"/>
+    <TextView
+            android:id="@+id/temperature_off_text"
+            android:background="@drawable/hvac_off_text_bg"
+            android:textColor="@color/hvac_background_color"
+            android:textSize="@dimen/temperature_bar_off_text_size"
+            android:textStyle="bold"
+            android:visibility="gone"
+            android:includeFontPadding="false"
+            style="@style/HvacTemperatureOff"/>
+    <!-- The collapseable temperature bar -->
+    <FrameLayout
+            xmlns:android="http://schemas.android.com/apk/res/android"
+            android:id="@+id/temperature_bar"
+            android:layout_gravity="center_horizontal"
+            android:layout_width="@dimen/temperature_bar_width_collapsed"
+            android:layout_height="@dimen/temperature_bar_collapsed_height"
+            android:background="@drawable/temperature_bar_background">
+        <ImageView
+                android:id="@+id/increase_button"
+                android:layout_marginTop="@dimen/temperature_bar_icon_margin"
+                android:layout_gravity="top"
+                android:src="@drawable/ic_increase_icon"
+                style="@style/HvacTemperatureControlIcon"/>
+        <TextView
+                android:id="@+id/temperature_text"
+                android:layout_gravity="center_vertical"
+                style="@style/HvacTemperature"/>
+        <ImageView
+                android:id="@+id/decrease_button"
+                android:layout_marginBottom="@dimen/temperature_bar_icon_margin"
+                android:layout_gravity="bottom"
+                android:src="@drawable/ic_decrease_icon"
+                style="@style/HvacTemperatureControlIcon"/>
+    </FrameLayout>
+    <ImageView
+            android:id="@+id/close_button"
+            android:src="@drawable/ic_down_arrow"
+            android:visibility="gone"
+            android:layout_gravity="center_horizontal|bottom"
+            android:scaleType="center"
+            android:layout_marginBottom="@dimen/hvac_close_button_bottom_margin"
+            android:layout_height="@dimen/temperature_bar_close_icon_dimen"
+            android:layout_width="@dimen/temperature_bar_close_icon_dimen"/>
+</com.android.car.hvac.ui.TemperatureBarOverlay>
\ No newline at end of file
diff --git a/res/values-w1360dp/dimens.xml b/res/values-w1360dp/dimens.xml
new file mode 100644 (file)
index 0000000..2f10850
--- /dev/null
@@ -0,0 +1,47 @@
+<?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="car_hvac_panel_full_expanded_height">558dp</dimen>
+
+    <dimen name="hvac_center_panel_width">1032dp</dimen>
+
+    <dimen name="temperature_bar_text_size">40sp</dimen>
+    <dimen name="temperature_bar_floating_text_size">32sp</dimen>
+
+    <dimen name="temperature_bar_width_expanded">112dp</dimen>
+    <dimen name="temperature_bar_expanded_height">400dp</dimen>
+    <dimen name="temperature_bar_icon_margin">32dp</dimen>
+    <dimen name="temperature_bar_close_icon_dimen">72dp</dimen>
+
+    <dimen name="hvac_panel_margin">32dp</dimen>
+    <dimen name="hvac_panel_button_dimen">112dp</dimen>
+
+    <dimen name="hvac_panel_button_radius">56dp</dimen>
+    <dimen name="hvac_panel_group_height">256dp</dimen>
+    <dimen name="hvac_panel_center_group_width">744dp</dimen>
+    <dimen name="hvac_panel_center_group_margin">24dp</dimen>
+
+    <dimen name="hvac_fan_speed_bar_margin">16dp</dimen>
+    <dimen name="hvac_fan_speed_bar_text_size">26sp</dimen>
+
+    <dimen name="hvac_fan_speed_top_margin">20dp</dimen>
+
+    <dimen name="hvac_floating_text_left_margin">36dp</dimen>
+    <dimen name="hvac_text_left_margin">31dp</dimen>
+    <dimen name="hvac_close_button_bottom_margin">20dp</dimen>
+</resources>
diff --git a/res/values-w1360dp/styles.xml b/res/values-w1360dp/styles.xml
new file mode 100644 (file)
index 0000000..4e27269
--- /dev/null
@@ -0,0 +1,25 @@
+<?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>
+    <!-- Font for the temperature text inside the HVAC panel controls-->
+    <style name="HvacTemperatureFont">
+        <item name="android:textSize">@dimen/temperature_bar_text_size</item>
+        <item name="android:textColor">#000000</item>
+        <item name="android:fontFamily">sans-serif-medium</item>
+    </style>
+</resources>
\ No newline at end of file
diff --git a/res/values/colors.xml b/res/values/colors.xml
new file mode 100644 (file)
index 0000000..ae76c94
--- /dev/null
@@ -0,0 +1,38 @@
+<?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>
+    <color name="temperature_1">#FF5151</color>
+    <color name="temperature_2">#FFD63F</color>
+    <color name="temperature_3">#F4F4F4</color>
+    <color name="temperature_4">#7AA9F6</color>
+    <color name="temperature_5">#00AFFF</color>
+
+    <color name="hvac_module_background_color">#99222B31</color>
+
+    <color name="hvac_icon_color">#F5F5F5</color>
+    <color name="hvac_background_color">#FF172026</color>
+    <color name="hvac_temperature_floating_text_color">#9FA2A5</color>
+    <color name="hvac_temperature_off_text_bg_color">#9FA2A5</color>
+    <color name="hvac_fanspeed_bg_color">#141C21</color>
+    <color name="hvac_fanspeed_segment_color">#7FCAC3</color>
+
+    <color name="hvac_fanspeed_off_enabled_bg">#F5F6F5</color>
+    <color name="hvac_fanspeed_off_enabled_text_color">#13181C</color>
+    <color name="hvac_fanspeed_off_disabled_text_color">#89F5F5F5</color>
+    <color name="hvac_scrim_color">#3F000000</color>
+</resources>
diff --git a/res/values/config.xml b/res/values/config.xml
new file mode 100644 (file)
index 0000000..f4d844d
--- /dev/null
@@ -0,0 +1,22 @@
+<?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>
+    <bool name="config_hardwareUsesCelsius">true</bool>
+    <!-- This should really be configurable using settings but config will do for now. -->
+    <bool name="config_userUsesCelsius">false</bool>
+</resources>
diff --git a/res/values/dimens.xml b/res/values/dimens.xml
new file mode 100644 (file)
index 0000000..2c4104d
--- /dev/null
@@ -0,0 +1,76 @@
+<?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="car_hvac_panel_collapsed_height">100dp</dimen>
+    <dimen name="car_hvac_panel_full_expanded_height">480dp</dimen>
+
+    <dimen name="hvac_center_panel_width">760dp</dimen>
+
+    <dimen name="temperature_side_margin">32dp</dimen>
+
+    <dimen name="temperature_control_arrow_height">48dp</dimen>
+
+    <dimen name="temperature_bar_text_size">32sp</dimen>
+    <dimen name="temperature_bar_floating_text_size">32sp</dimen>
+    <dimen name="temperature_bar_off_text_size">22sp</dimen>
+
+    <dimen name="hvac_panel_row_animation_height_shift">0dp</dimen>
+
+    <dimen name="temperature_bar_width_collapsed">96dp</dimen>
+    <dimen name="temperature_bar_width_expanded">96dp</dimen>
+    <dimen name="temperature_bar_collapsed_height">96dp</dimen>
+    <dimen name="temperature_bar_expanded_height">356dp</dimen>
+    <dimen name="temperature_bar_icon_margin">20dp</dimen>
+    <dimen name="temperature_bar_close_icon_dimen">96dp</dimen>
+
+    <dimen name="temperature_bar_bg_radius">54dp</dimen>
+    <dimen name="temperature_bar_floating_text_bottom_margin">50dp</dimen>
+    <dimen name="temperature_bar_off_text_bottom_margin">56dp</dimen>
+
+    <dimen name="temperature_off_text_bg_width">72dp</dimen>
+    <dimen name="temperature_off_text_bg_height">24dp</dimen>
+
+    <dimen name="hvac_panel_row_margin">40dp</dimen>
+    <dimen name="hvac_panel_fan_direction_margin">32dp</dimen>
+
+    <dimen name="hvac_panel_margin">16dp</dimen>
+    <dimen name="hvac_panel_button_dimen">96dp</dimen>
+
+    <dimen name="hvac_panel_button_radius">54dp</dimen>
+    <dimen name="hvac_panel_group_height">244dp</dimen>
+    <dimen name="hvac_panel_center_group_width">536dp</dimen>
+    <dimen name="hvac_panel_center_group_margin">16dp</dimen>
+
+    <dimen name="hvac_fan_speed_bar_height">72dp</dimen>
+    <dimen name="hvac_fan_speed_bar_vertical_inset">8dp</dimen>
+    <dimen name="hvac_fan_speed_bar_width">680dp</dimen>
+    <dimen name="hvac_fan_speed_bar_corner_radius">28dp</dimen>
+    <dimen name="hvac_fan_speed_bar_end_button_margin">12dp</dimen>
+    <dimen name="hvac_fan_speed_bar_segment_margin">4dp</dimen>
+
+    <dimen name="hvac_fan_speed_bar_margin">16dp</dimen>
+    <dimen name="hvac_fan_speed_bar_text_size">18sp</dimen>
+
+    <dimen name="hvac_fan_speed_dot_size">16dp</dimen>
+    <dimen name="hvac_fan_speed_dot_expanded_size">150dp</dimen>
+    <dimen name="hvac_fan_speed_top_margin">15dp</dimen>
+
+    <dimen name="hvac_floating_text_left_margin">31dp</dimen>
+    <dimen name="hvac_text_left_margin">26dp</dimen>
+    <dimen name="hvac_close_button_bottom_margin">0dp</dimen>
+</resources>
diff --git a/res/values/strings.xml b/res/values/strings.xml
new file mode 100644 (file)
index 0000000..cde5f7f
--- /dev/null
@@ -0,0 +1,27 @@
+<?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 xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <!-- Application name -->
+    <string name="hvac_label">Climate Control</string>
+
+    <string name="hvac_temperature_template"><xliff:g id="temperature">%1$s</xliff:g>&#x00B0;</string>
+    <string name="fan_speed_max">MAX</string>
+    <string name="fan_speed_off">OFF</string>
+    <string name="hvac_invalid_temperature">--</string>
+    <string name="hvac_temperature_off">OFF</string>
+</resources>
diff --git a/res/values/styles.xml b/res/values/styles.xml
new file mode 100644 (file)
index 0000000..1500c4b
--- /dev/null
@@ -0,0 +1,96 @@
+<?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>
+    <!-- A radio button that will have a ripple effect when touched. -->
+    <style name="HvacButton.Ripple" parent="@android:style/Widget.Material.Button.Borderless">
+        <item name="android:background">@drawable/ripple_background</item>
+    </style>
+    <!-- A radio button that has an image with a ripple effect. -->
+    <style name="HvacButton.Ripple.Image">
+        <item name="android:scaleType">fitCenter</item>
+    </style>
+    <!-- Font for the temperature text inside the HVAC panel controls-->
+    <style name="HvacTemperatureFont">
+        <item name="android:textSize">@dimen/temperature_bar_text_size</item>
+        <item name="android:textColor">#000000</item>
+    </style>
+    <style name="HvacTemperature" parent="HvacTemperatureFont">
+        <item name="android:layout_marginLeft">@dimen/hvac_text_left_margin</item>
+        <item name="android:layout_width">wrap_content</item>
+        <item name="android:layout_height">wrap_content</item>
+    </style>
+    <style name="HvacTemperatureOff" parent="HvacTemperatureFont">
+        <item name="android:gravity">center</item>
+        <item name="android:textAllCaps">true</item>
+        <item name="android:text">@string/hvac_temperature_off</item>
+        <item name="android:layout_gravity">center_horizontal|bottom</item>
+        <item name="android:layout_width">@dimen/temperature_off_text_bg_width</item>
+        <item name="android:layout_height">@dimen/temperature_off_text_bg_height</item>
+        <item name="android:layout_marginBottom">@dimen/temperature_bar_off_text_bottom_margin</item>
+    </style>
+
+    <style name="HvacTemperatureControlIcon">
+        <item name="android:scaleType">center</item>
+        <item name="android:layout_width">match_parent</item>
+        <item name="android:layout_height">@dimen/temperature_control_arrow_height</item>
+        <item name="android:layout_centerHorizontal">true</item>
+        <item name="android:background">?android:attr/selectableItemBackgroundBorderless</item>
+    </style>
+
+    <style name="HvacFanSpeedBar">
+        <item name="android:layout_width">0dp</item>
+        <item name="android:layout_weight">1</item>
+        <item name="android:layout_height">match_parent</item>
+        <item name="android:layout_marginTop">12dp</item>
+        <item name="android:layout_marginBottom">12dp</item>
+        <item name="android:layout_marginEnd">@dimen/hvac_fan_speed_bar_segment_margin</item>
+        <item name="android:layout_marginStart">@dimen/hvac_fan_speed_bar_segment_margin</item>
+        <item name="android:layout_gravity">center</item>
+    </style>
+
+    <style name="HvacFanSpeedText">
+        <item name="android:gravity">center</item>
+        <item name="android:layout_width">0dp</item>
+        <item name="android:layout_weight">1</item>
+        <item name="android:layout_marginTop">12dp</item>
+        <item name="android:layout_marginBottom">12dp</item>
+        <item name="android:layout_height">match_parent</item>
+        <item name="android:textSize">@dimen/hvac_fan_speed_bar_text_size</item>
+        <item name="android:fontFamily">sans-serif-medium</item>
+        <item name="android:textColor">@color/hvac_icon_color</item>
+        <item name="android:layout_centerVertical">true</item>
+    </style>
+
+    <style name="HvacSpacerView" >
+        <item name="android:layout_width">0dp</item>
+        <item name="android:layout_height">1dp</item>
+        <item name="android:layout_weight">1</item>
+    </style>
+
+    <style name="HvacButton.FanDirection" parent="HvacButton">
+        <item name="android:alpha">0.5</item>
+        <item name="android:layout_gravity">center</item>
+        <item name="android:scaleType">fitCenter</item>
+    </style>
+
+    <style name="HvacButton">
+        <item name="android:layout_width">@dimen/hvac_panel_button_dimen</item>
+        <item name="android:layout_height">@dimen/hvac_panel_button_dimen</item>
+    </style>
+</resources>
+
diff --git a/res/values/tags.xml b/res/values/tags.xml
new file mode 100644 (file)
index 0000000..2ea3a9c
--- /dev/null
@@ -0,0 +1,19 @@
+<!--
+ * 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 xmlns:android="http://schemas.android.com/apk/res/android">
+    <item name="TAG_FAN_SPEED_LEVEL" type="id"/>
+</resources>
\ No newline at end of file
diff --git a/src/com/android/car/hvac/BootCompleteReceiver.java b/src/com/android/car/hvac/BootCompleteReceiver.java
new file mode 100644 (file)
index 0000000..a5a88f9
--- /dev/null
@@ -0,0 +1,28 @@
+/*
+ * 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.android.car.hvac;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+
+public class BootCompleteReceiver extends BroadcastReceiver {
+    @Override
+    public void onReceive(Context context, Intent intent) {
+        Intent hvacUiService = new Intent(context, HvacUiService.class);
+        context.startService(hvacUiService);
+    }
+}
diff --git a/src/com/android/car/hvac/DataStore.java b/src/com/android/car/hvac/DataStore.java
new file mode 100644 (file)
index 0000000..b6b071e
--- /dev/null
@@ -0,0 +1,282 @@
+/*
+ * 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.android.car.hvac;
+
+import android.os.SystemClock;
+import android.util.SparseArray;
+import android.util.SparseBooleanArray;
+import android.util.SparseIntArray;
+import android.util.SparseLongArray;
+
+import java.util.concurrent.TimeUnit;
+import javax.annotation.concurrent.GuardedBy;
+
+/**
+ * The hvac unit can be controller from two places, the ui and the hardware buttons. Each of these
+ * request updates to the current state from different threads. Moreover, there can be conditions
+ * where the hvac could send spurious updates so this class routes everything through and coalesces
+ * them, keeping the application's view of the world sane.
+ */
+public class DataStore {
+    private static final long COALESCE_TIME_MS = TimeUnit.SECONDS.toMillis(2);
+
+    @GuardedBy("mTemperature")
+    private SparseArray<Float> mTemperature = new SparseArray<Float>();
+    @GuardedBy("mFanSpeed")
+    private Integer mFanSpeed = 0;
+    @GuardedBy("mAirflow")
+    private SparseIntArray mAirflow = new SparseIntArray();
+    @GuardedBy("mDefrosterState")
+    private SparseBooleanArray mDefrosterState = new SparseBooleanArray();
+    @GuardedBy("mAcState")
+    private Boolean mAcState = false;
+    @GuardedBy("mSeatWarmerLevel")
+    private SparseIntArray mSeatWarmerLevel = new SparseIntArray();
+    @GuardedBy("mAirCirculationState")
+    private Boolean mAirCirculationState = false;
+    @GuardedBy("mAutoModeState")
+    private Boolean mAutoModeState = false;
+    @GuardedBy("mHvacPowerState")
+    private Boolean mHvacPowerState = false;
+
+    @GuardedBy("mTemperature")
+    private SparseLongArray mLastTemperatureSet = new SparseLongArray();
+    @GuardedBy("mFanSpeed")
+    private long mLastFanSpeedSet;
+    @GuardedBy("mAirflow")
+    private SparseLongArray mLastAirflowSet = new SparseLongArray();
+    @GuardedBy("mDefrosterState")
+    private SparseLongArray mLastDefrosterSet = new SparseLongArray();
+    @GuardedBy("mAcState")
+    private long mLastAcSet;
+    @GuardedBy("mSeatWarmerLevel")
+    private SparseLongArray mLastSeatWarmerLevel = new SparseLongArray();
+    @GuardedBy("mAirCirculationState")
+    private long mAirCirculationLastSet;
+    @GuardedBy("mAutoModeState")
+    private long mAutoModeLastSet;
+    @GuardedBy("mHvacPowerState")
+    private long mHvacPowerLastSet;
+
+
+    public float getTemperature(int zone) {
+        synchronized (mTemperature) {
+            return mTemperature.get(zone);
+        }
+    }
+
+    public void setTemperature(int zone, float temperature) {
+        synchronized (mTemperature) {
+            mTemperature.put(zone, temperature);
+            mLastTemperatureSet.put(zone, SystemClock.uptimeMillis());
+        }
+    }
+
+    public boolean shouldPropagateTempUpdate(int zone, float temperature) {
+        synchronized (mTemperature) {
+            if (SystemClock.uptimeMillis() - mLastTemperatureSet.get(zone) < COALESCE_TIME_MS) {
+                return false;
+            }
+            mTemperature.put(zone, temperature);
+        }
+        return true;
+    }
+
+    public boolean getDefrosterState(int zone) {
+        synchronized (mDefrosterState) {
+            return mDefrosterState.get(zone);
+        }
+    }
+
+    public void setDefrosterState(int zone, boolean state) {
+        synchronized (mDefrosterState) {
+            mDefrosterState.put(zone, state);
+            mLastDefrosterSet.put(zone, SystemClock.uptimeMillis());
+        }
+    }
+
+    public boolean shouldPropagateDefrosterUpdate(int zone, boolean defrosterState) {
+        synchronized (mDefrosterState) {
+            if (SystemClock.uptimeMillis() - mLastDefrosterSet.get(zone) < COALESCE_TIME_MS) {
+                return false;
+            }
+            mDefrosterState.put(zone, defrosterState);
+        }
+        return true;
+    }
+
+    public int getFanSpeed() {
+        synchronized (mFanSpeed) {
+            return mFanSpeed;
+        }
+    }
+
+    public void setFanSpeed(int speed) {
+        synchronized (mFanSpeed) {
+            mFanSpeed = speed;
+            mLastFanSpeedSet = SystemClock.uptimeMillis();
+        }
+    }
+
+    public boolean shouldPropagateFanSpeedUpdate(int zone, int speed) {
+        // TODO: We ignore fan speed zones for now because we dont have a multi zone car.
+        synchronized (mFanSpeed) {
+            if (SystemClock.uptimeMillis() - mLastFanSpeedSet < COALESCE_TIME_MS) {
+                return false;
+            }
+            mFanSpeed = speed;
+        }
+        return true;
+    }
+
+    public boolean getAcState() {
+        synchronized (mAcState) {
+            return mAcState;
+        }
+    }
+
+    public void setAcState(boolean acState) {
+        synchronized (mAcState) {
+            mAcState = acState;
+            mLastAcSet = SystemClock.uptimeMillis();
+        }
+    }
+
+    public boolean shouldPropagateAcUpdate(boolean acState) {
+        synchronized (mAcState) {
+            if (SystemClock.uptimeMillis() - mLastAcSet < COALESCE_TIME_MS) {
+                return false;
+            }
+            mAcState = acState;
+        }
+        return true;
+    }
+
+    public int getAirflow(int zone) {
+        synchronized (mAirflow) {
+            return mAirflow.get(zone);
+        }
+    }
+
+    public void setAirflow(int zone, int index) {
+        synchronized (mAirflow) {
+            mAirflow.put(zone, index);
+            mLastAirflowSet.put(zone, SystemClock.uptimeMillis());
+        }
+    }
+
+    public boolean shouldPropagateFanPositionUpdate(int zone, int index) {
+        synchronized (mAirflow) {
+            if (SystemClock.uptimeMillis() - mLastAirflowSet.get(zone) < COALESCE_TIME_MS) {
+                return false;
+            }
+            mAirflow.put(zone, index);
+        }
+        return true;
+    }
+
+    public float getSeatWarmerLevel(int zone) {
+        synchronized (mSeatWarmerLevel) {
+            return mSeatWarmerLevel.get(zone);
+        }
+    }
+
+    public void setSeatWarmerLevel(int zone, int level) {
+        synchronized (mSeatWarmerLevel) {
+            mSeatWarmerLevel.put(zone, level);
+            mLastSeatWarmerLevel.put(zone, SystemClock.uptimeMillis());
+        }
+    }
+
+    public boolean shouldPropagateSeatWarmerLevelUpdate(int zone, int level) {
+        synchronized (mSeatWarmerLevel) {
+            if (SystemClock.uptimeMillis() - mLastSeatWarmerLevel.get(zone) < COALESCE_TIME_MS) {
+                return false;
+            }
+            mSeatWarmerLevel.put(zone, level);
+        }
+        return true;
+    }
+
+    public boolean getAirCirculationState() {
+        synchronized (mAirCirculationState) {
+            return mAirCirculationState;
+        }
+    }
+
+    public void setAirCirculationState(boolean airCirculationState) {
+        synchronized (mAirCirculationState) {
+            mAirCirculationState = airCirculationState;
+            mAirCirculationLastSet = SystemClock.uptimeMillis();
+        }
+    }
+
+    public boolean shouldPropagateAirCirculationUpdate(boolean airCirculationState) {
+        synchronized (mAirCirculationState) {
+            if (SystemClock.uptimeMillis() - mAirCirculationLastSet < COALESCE_TIME_MS) {
+                return false;
+            }
+            mAcState = airCirculationState;
+        }
+        return true;
+    }
+
+    public boolean getAutoModeState() {
+        synchronized (mAutoModeState) {
+            return mAutoModeState;
+        }
+    }
+
+    public void setAutoModeState(boolean autoModeState) {
+        synchronized (mAutoModeState) {
+            mAutoModeState = autoModeState;
+            mAutoModeLastSet = SystemClock.uptimeMillis();
+        }
+    }
+
+    public boolean shouldPropagateAutoModeUpdate(boolean autoModeState) {
+        synchronized (mAutoModeState) {
+            if (SystemClock.uptimeMillis() - mAutoModeLastSet < COALESCE_TIME_MS) {
+                return false;
+            }
+            mAcState = autoModeState;
+        }
+        return true;
+    }
+
+    public boolean getHvacPowerState() {
+        synchronized (mHvacPowerState) {
+            return mHvacPowerState;
+        }
+    }
+
+    public void setHvacPowerState(boolean hvacPowerState) {
+        synchronized (mHvacPowerState) {
+            mHvacPowerState = hvacPowerState;
+            mHvacPowerLastSet = SystemClock.uptimeMillis();
+        }
+    }
+
+    public boolean shouldPropagateHvacPowerUpdate(boolean hvacPowerState) {
+        synchronized (mHvacPowerState) {
+            if (SystemClock.uptimeMillis() - mHvacPowerLastSet < COALESCE_TIME_MS) {
+                return false;
+            }
+            mHvacPowerState = hvacPowerState;
+        }
+        return true;
+    }
+}
diff --git a/src/com/android/car/hvac/HvacController.java b/src/com/android/car/hvac/HvacController.java
new file mode 100644 (file)
index 0000000..d6c2397
--- /dev/null
@@ -0,0 +1,722 @@
+/*
+ * 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.android.car.hvac;
+
+import android.app.Service;
+import android.car.hardware.CarPropertyConfig;
+import android.car.hardware.CarPropertyValue;
+import android.car.hardware.hvac.CarHvacManager;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.os.AsyncTask;
+import android.os.Binder;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.SystemProperties;
+import android.util.Log;
+
+import android.support.car.Car;
+import android.support.car.CarNotConnectedException;
+import android.support.car.ServiceConnectionCallback;
+
+import com.android.car.vehiclenetwork.VehicleNetworkConsts.VehicleHvacFanDirection;
+import com.android.car.vehiclenetwork.VehicleNetworkConsts.VehicleSeat;
+import com.android.car.vehiclenetwork.VehicleNetworkConsts.VehicleWindow;
+import com.android.car.vehiclenetwork.VehicleNetworkConsts.VehicleZone;
+
+import java.util.ArrayList;
+import java.util.List;
+import javax.annotation.concurrent.GuardedBy;
+
+public class HvacController extends Service {
+    private static final String DEMO_MODE_PROPERTY = "android.car.hvac.demo";
+    private static final String TAG = "HvacController";
+    private static final int DRIVER_ZONE_ID = VehicleSeat.VEHICLE_SEAT_ROW_1_LEFT;
+    private static final int PASSENGER_ZONE_ID = VehicleSeat.VEHICLE_SEAT_ROW_1_RIGHT;
+
+    public static final int[] AIRFLOW_STATES = new int[]{
+            VehicleHvacFanDirection.VEHICLE_HVAC_FAN_DIRECTION_FACE,
+            VehicleHvacFanDirection.VEHICLE_HVAC_FAN_DIRECTION_FLOOR,
+            VehicleHvacFanDirection.VEHICLE_HVAC_FAN_DIRECTION_FACE_AND_FLOOR
+    };
+
+    /**
+     * Callback for receiving updates from the hvac manager. A Callback can be
+     * registered using {@link #registerCallback}.
+     */
+    public static abstract class Callback {
+
+        public void onPassengerTemperatureChange(float temp) {
+        }
+
+        public void onDriverTemperatureChange(float temp) {
+        }
+
+        public void onFanSpeedChange(int position) {
+        }
+
+        public void onAcStateChange(boolean isOn) {
+        }
+
+        public void onFrontDefrosterChange(boolean isOn) {
+        }
+
+        public void onRearDefrosterChange(boolean isOn) {
+        }
+
+        public void onPassengerSeatWarmerChange(int level) {
+        }
+
+        public void onDriverSeatWarmerChange(int level) {
+        }
+
+        public void onFanDirectionChange(int direction) {
+        }
+
+        public void onAirCirculationChange(boolean isOn) {
+        }
+
+        public void onAutoModeChange(boolean isOn) {
+        }
+
+        public void onHvacPowerChange(boolean isOn){
+        }
+    }
+
+    public class LocalBinder extends Binder {
+        HvacController getService() {
+            return HvacController.this;
+        }
+    }
+
+    private final Binder mBinder = new LocalBinder();
+
+    private Car mCarApiClient;
+    private CarHvacManager mHvacManager;
+    private Object mHvacManagerReady = new Object();
+
+    private HvacPolicy mPolicy;
+    @GuardedBy("mCallbacks")
+    private List<Callback> mCallbacks = new ArrayList<>();
+    private DataStore mDataStore = new DataStore();
+
+    @Override
+    public void onCreate() {
+        super.onCreate();
+        if (getPackageManager().hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE)) {
+            if (SystemProperties.getBoolean(DEMO_MODE_PROPERTY, false)) {
+                IBinder binder = (new LocalHvacPropertyService()).getCarPropertyService();
+                initHvacManager(new CarHvacManager(binder, this, new Handler()));
+                return;
+            }
+
+            mCarApiClient = Car.createCar(this, mServiceConnectionCallback);
+            mCarApiClient.connect();
+        }
+    }
+
+    @Override
+    public void onDestroy() {
+        super.onDestroy();
+        if (mHvacManager != null) {
+            mHvacManager.unregisterCallback(mHardwareCallback);
+        }
+        if (mCarApiClient != null) {
+            mCarApiClient.disconnect();
+        }
+    }
+
+    @Override
+    public int onStartCommand(Intent intent, int flags, int startId) {
+        return START_STICKY;
+    }
+
+    @Override
+    public IBinder onBind(Intent intent) {
+        return mBinder;
+    }
+
+    public void registerCallback(Callback callback) {
+        synchronized (mCallbacks) {
+            mCallbacks.add(callback);
+        }
+    }
+
+    public void unregisterCallback(Callback callback) {
+        synchronized (mCallbacks) {
+            mCallbacks.remove(callback);
+        }
+    }
+
+    private void initHvacManager(CarHvacManager carHvacManager) {
+        mHvacManager = carHvacManager;
+        List<CarPropertyConfig> properties = null;
+        try {
+            properties = mHvacManager.getPropertyList();
+            mPolicy = new HvacPolicy(HvacController.this, properties);
+            mHvacManager.registerCallback(mHardwareCallback);
+        } catch (android.car.CarNotConnectedException e) {
+            Log.e(TAG, "Car not connected in HVAC");
+        }
+
+    }
+
+    private final ServiceConnectionCallback mServiceConnectionCallback =
+            new ServiceConnectionCallback() {
+                @Override
+                public void onServiceConnected() {
+                    synchronized (mHvacManagerReady) {
+                        try {
+                            initHvacManager((CarHvacManager) mCarApiClient.getCarManager(
+                                    android.car.Car.HVAC_SERVICE));
+                            mHvacManagerReady.notifyAll();
+                        } catch (CarNotConnectedException e) {
+                            Log.e(TAG, "Car not connected in onServiceConnected");
+                        }
+                    }
+                }
+
+                @Override
+                public void onServiceDisconnected() {
+                }
+
+                @Override
+                public void onServiceConnectionFailed() {
+                }
+            };
+
+    private final CarHvacManager.CarHvacEventCallback mHardwareCallback =
+            new CarHvacManager.CarHvacEventCallback() {
+                @Override
+                public void onChangeEvent(final CarPropertyValue val) {
+                    int areaId = val.getAreaId();
+                    switch (val.getPropertyId()) {
+                        case CarHvacManager.ID_ZONED_AC_ON:
+                            handleAcStateUpdate(getValue(val));
+                            break;
+                        case CarHvacManager.ID_ZONED_FAN_POSITION:
+                            handleFanPositionUpdate(areaId, getValue(val));
+                            break;
+                        case CarHvacManager.ID_ZONED_FAN_SPEED_SETPOINT:
+                            handleFanSpeedUpdate(areaId, getValue(val));
+                            break;
+                        case CarHvacManager.ID_ZONED_TEMP_SETPOINT:
+                            handleTempUpdate(areaId, getValue(val));
+                            break;
+                        case CarHvacManager.ID_WINDOW_DEFROSTER_ON:
+                            handleDefrosterUpdate(areaId, getValue(val));
+                            break;
+                        case CarHvacManager.ID_ZONED_AIR_RECIRCULATION_ON:
+                            handleAirCirculationUpdate(getValue(val));
+                            break;
+                        case CarHvacManager.ID_ZONED_SEAT_TEMP:
+                            handleSeatWarmerUpdate(areaId, getValue(val));
+                            break;
+                        case CarHvacManager.ID_ZONED_AUTOMATIC_MODE_ON:
+                            handleAutoModeUpdate(getValue(val));
+                            break;
+                        case CarHvacManager.ID_ZONED_HVAC_POWER_ON:
+                            handleHvacPowerOn(getValue(val));
+                            break;
+                        default:
+                            if (Log.isLoggable(TAG, Log.DEBUG)) {
+                                Log.d(TAG, "Unhandled HVAC event, id: " + val.getPropertyId());
+                            }
+                    }
+                }
+
+                @Override
+                public void onErrorEvent(final int propertyId, final int zone) {
+                }
+            };
+
+    @SuppressWarnings("unchecked")
+    public static <E> E getValue(CarPropertyValue propertyValue) {
+        return (E) propertyValue.getValue();
+    }
+
+    void handleHvacPowerOn(boolean isOn) {
+        if (mDataStore.shouldPropagateHvacPowerUpdate(isOn)) {
+            synchronized (mCallbacks) {
+                for (int i = 0; i < mCallbacks.size(); i++) {
+                    mCallbacks.get(i).onHvacPowerChange(isOn);
+                }
+            }
+        }
+    }
+
+    void handleSeatWarmerUpdate(int zone, int level) {
+        if (mDataStore.shouldPropagateSeatWarmerLevelUpdate(zone, level)) {
+            synchronized (mCallbacks) {
+                for (int i = 0; i < mCallbacks.size(); i++) {
+                    if (zone == VehicleZone.VEHICLE_ZONE_ROW_1_LEFT) {
+                        mCallbacks.get(i).onDriverSeatWarmerChange(level);
+                    } else {
+                        mCallbacks.get(i).onPassengerSeatWarmerChange(level);
+                    }
+                }
+            }
+        }
+    }
+
+    private void handleAirCirculationUpdate(boolean airCirculationState) {
+        if (mDataStore.shouldPropagateAirCirculationUpdate(airCirculationState)) {
+            synchronized (mCallbacks) {
+                for (int i = 0; i < mCallbacks.size(); i++) {
+                    mCallbacks.get(i).onAirCirculationChange(airCirculationState);
+                }
+            }
+        }
+    }
+
+    private void handleAutoModeUpdate(boolean autoModeState) {
+        if (mDataStore.shouldPropagateAutoModeUpdate(autoModeState)) {
+            synchronized (mCallbacks) {
+                for (int i = 0; i < mCallbacks.size(); i++) {
+                    mCallbacks.get(i).onAutoModeChange(autoModeState);
+                }
+            }
+        }
+    }
+
+    private void handleAcStateUpdate(boolean acState) {
+        if (mDataStore.shouldPropagateAcUpdate(acState)) {
+            synchronized (mCallbacks) {
+                for (int i = 0; i < mCallbacks.size(); i++) {
+                    mCallbacks.get(i).onAcStateChange(acState);
+                }
+            }
+        }
+    }
+
+    private void handleFanPositionUpdate(int zone, int position) {
+        int index = fanPositionToAirflowIndex(position);
+        if (mDataStore.shouldPropagateFanPositionUpdate(zone, index)) {
+            synchronized (mCallbacks) {
+                for (int i = 0; i < mCallbacks.size(); i++) {
+                    mCallbacks.get(i).onFanDirectionChange(position);
+                }
+            }
+        }
+    }
+
+    private void handleFanSpeedUpdate(int zone, int speed) {
+        if (mDataStore.shouldPropagateFanSpeedUpdate(zone, speed)) {
+            synchronized (mCallbacks) {
+                for (int i = 0; i < mCallbacks.size(); i++) {
+                    mCallbacks.get(i).onFanSpeedChange(speed);
+                }
+            }
+        }
+    }
+
+    private void handleTempUpdate(int zone, float temp) {
+        if (mDataStore.shouldPropagateTempUpdate(zone, temp)) {
+            int userTemperature =  mPolicy.hardwareToUserTemp(temp);
+            synchronized (mCallbacks) {
+                for (int i = 0; i < mCallbacks.size(); i++) {
+                    if (zone == VehicleZone.VEHICLE_ZONE_ROW_1_LEFT) {
+                        mCallbacks.get(i)
+                                .onDriverTemperatureChange(userTemperature);
+                    } else {
+                        mCallbacks.get(i)
+                                .onPassengerTemperatureChange(userTemperature);
+                    }
+                }
+            }
+        }
+    }
+
+    private void handleDefrosterUpdate(int zone, boolean defrosterState) {
+        if (mDataStore.shouldPropagateDefrosterUpdate(zone, defrosterState)) {
+            synchronized (mCallbacks) {
+                for (int i = 0; i < mCallbacks.size(); i++) {
+                    if (zone == VehicleWindow.VEHICLE_WINDOW_FRONT_WINDSHIELD) {
+                        mCallbacks.get(i).onFrontDefrosterChange(defrosterState);
+                    } else if (zone == VehicleWindow.VEHICLE_WINDOW_REAR_WINDSHIELD) {
+                        mCallbacks.get(i).onRearDefrosterChange(defrosterState);
+                    }
+                }
+            }
+
+        }
+    }
+
+    public void requestRefresh(final Runnable r, final Handler h) {
+        final AsyncTask<Void, Void, Void> task = new AsyncTask<Void, Void, Void>() {
+            @Override
+            protected Void doInBackground(Void... unused) {
+                synchronized (mHvacManagerReady) {
+                    while (mHvacManager == null) {
+                        try {
+                            mHvacManagerReady.wait();
+                        } catch (InterruptedException e) {
+                            // We got interrupted so we might be shutting down.
+                            return null;
+                        }
+                    }
+                }
+                fetchTemperature(DRIVER_ZONE_ID);
+                fetchTemperature(PASSENGER_ZONE_ID);
+                fetchFanSpeed();
+                fetchDefrosterState(VehicleWindow.VEHICLE_WINDOW_FRONT_WINDSHIELD);
+                fetchDefrosterState(VehicleWindow.VEHICLE_WINDOW_REAR_WINDSHIELD);
+                fetchAirflow(DRIVER_ZONE_ID);
+                fetchAirflow(PASSENGER_ZONE_ID);
+                fetchAcState();
+                fetchAirCirculation();
+                fetchHvacPowerState();
+                return null;
+            }
+
+            @Override
+            protected void onPostExecute(Void unused) {
+                h.post(r);
+            }
+        };
+        task.execute();
+    }
+
+    public HvacPolicy getPolicy() {
+        return mPolicy;
+    }
+
+    private void fetchTemperature(int zone) {
+        if (mHvacManager != null) {
+            try {
+                mDataStore.setTemperature(zone, mHvacManager.getFloatProperty(
+                        CarHvacManager.ID_ZONED_TEMP_SETPOINT, zone));
+            } catch (android.car.CarNotConnectedException e) {
+                Log.e(TAG, "Car not connected in fetchTemperature");
+            }
+        }
+    }
+
+    public int getDriverTemperature() {
+        return mPolicy.hardwareToUserTemp(mDataStore.getTemperature(DRIVER_ZONE_ID));
+    }
+
+    public int getPassengerTemperature() {
+        return mPolicy.hardwareToUserTemp(mDataStore.getTemperature(PASSENGER_ZONE_ID));
+    }
+
+    public void setDriverTemperature(int temperature) {
+        setTemperature(DRIVER_ZONE_ID, mPolicy.userToHardwareTemp(temperature));
+    }
+
+    public void setPassengerTemperature(int temperature) {
+        setTemperature(PASSENGER_ZONE_ID, mPolicy.userToHardwareTemp(temperature));
+    }
+
+    public void setTemperature(final int zone, final float temperature) {
+        mDataStore.setTemperature(zone, temperature);
+        final AsyncTask<Void, Void, Void> task = new AsyncTask<Void, Void, Void>() {
+            protected Void doInBackground(Void... unused) {
+                if (mHvacManager != null) {
+                    try {
+                        mHvacManager.setFloatProperty(
+                                CarHvacManager.ID_ZONED_TEMP_SETPOINT, zone, temperature);
+                    } catch (android.car.CarNotConnectedException e) {
+                        Log.e(TAG, "Car not connected in setTemperature");
+                    }
+                }
+                return null;
+            }
+        };
+        task.execute();
+    }
+
+    public void setDriverSeatWarmerLevel(int level) {
+        setSeatWarmerLevel(DRIVER_ZONE_ID, level);
+    }
+
+    public void setPassengerSeatWarmerLevel(int level) {
+        setSeatWarmerLevel(PASSENGER_ZONE_ID, level);
+    }
+
+    public void setSeatWarmerLevel(final int zone, final int level) {
+        mDataStore.setSeatWarmerLevel(zone, level);
+        final AsyncTask<Void, Void, Void> task = new AsyncTask<Void, Void, Void>() {
+            protected Void doInBackground(Void... unused) {
+                if (mHvacManager != null) {
+                    try {
+                        mHvacManager.setIntProperty(
+                                CarHvacManager.ID_ZONED_SEAT_TEMP, zone, level);
+                    } catch (android.car.CarNotConnectedException e) {
+                        Log.e(TAG, "Car not connected in setSeatWarmerLevel");
+                    }
+                }
+                return null;
+            }
+        };
+        task.execute();
+    }
+
+    private void fetchFanSpeed() {
+        if (mHvacManager != null) {
+            int zone = VehicleZone.VEHICLE_ZONE_ROW_1_ALL; // Car specific workaround.
+            try {
+                mDataStore.setFanSpeed(mHvacManager.getIntProperty(
+                        CarHvacManager.ID_ZONED_FAN_SPEED_SETPOINT, zone));
+            } catch (android.car.CarNotConnectedException e) {
+                Log.e(TAG, "Car not connected in fetchFanSpeed");
+            }
+        }
+    }
+
+    public int getFanSpeed() {
+        return mDataStore.getFanSpeed();
+    }
+
+    public void setFanSpeed(final int fanSpeed) {
+        mDataStore.setFanSpeed(fanSpeed);
+
+        final AsyncTask<Void, Void, Void> task = new AsyncTask<Void, Void, Void>() {
+            int newFanSpeed;
+
+            protected Void doInBackground(Void... unused) {
+                if (mHvacManager != null) {
+                    int zone = VehicleZone.VEHICLE_ZONE_ROW_1_ALL; // Car specific workaround.
+                    try {
+                        if (Log.isLoggable(TAG, Log.DEBUG)) {
+                            Log.d(TAG, "Setting fanspeed to: " + fanSpeed);
+                        }
+                        mHvacManager.setIntProperty(
+                                CarHvacManager.ID_ZONED_FAN_SPEED_SETPOINT, zone, fanSpeed);
+
+                        newFanSpeed = mHvacManager.getIntProperty(
+                                CarHvacManager.ID_ZONED_FAN_SPEED_SETPOINT, zone);
+                    } catch (android.car.CarNotConnectedException e) {
+                        Log.e(TAG, "Car not connected in setFanSpeed");
+                    }
+                }
+                return null;
+            }
+
+            @Override
+            protected void onPostExecute(final Void result) {
+                Log.e(TAG, "postExecute new fanSpeed: " + newFanSpeed);
+            }
+        };
+        task.execute();
+    }
+
+    private void fetchDefrosterState(int zone) {
+        if (mHvacManager != null) {
+            try {
+                mDataStore.setDefrosterState(zone, mHvacManager.getBooleanProperty(
+                        CarHvacManager.ID_WINDOW_DEFROSTER_ON, zone));
+            } catch (android.car.CarNotConnectedException e) {
+                Log.e(TAG, "Car not connected in fetchDefrosterState");
+            }
+        }
+    }
+
+    public boolean getFrontDefrosterState() {
+        return mDataStore.getDefrosterState(VehicleWindow.VEHICLE_WINDOW_FRONT_WINDSHIELD);
+    }
+
+    public boolean getRearDefrosterState() {
+        return mDataStore.getDefrosterState(VehicleWindow.VEHICLE_WINDOW_REAR_WINDSHIELD);
+    }
+
+    public void setFrontDefrosterState(boolean state) {
+        setDefrosterState(VehicleWindow.VEHICLE_WINDOW_FRONT_WINDSHIELD, state);
+    }
+
+    public void setRearDefrosterState(boolean state) {
+        setDefrosterState(VehicleWindow.VEHICLE_WINDOW_REAR_WINDSHIELD, state);
+    }
+
+    public void setDefrosterState(final int zone, final boolean state) {
+        mDataStore.setDefrosterState(zone, state);
+        final AsyncTask<Void, Void, Void> task = new AsyncTask<Void, Void, Void>() {
+            protected Void doInBackground(Void... unused) {
+                if (mHvacManager != null) {
+                    try {
+                        mHvacManager.setBooleanProperty(
+                                CarHvacManager.ID_WINDOW_DEFROSTER_ON, zone, state);
+                    } catch (android.car.CarNotConnectedException e) {
+                        Log.e(TAG, "Car not connected in setDeforsterState");
+                    }
+                }
+                return null;
+            }
+        };
+        task.execute();
+    }
+
+    private void fetchAcState() {
+        if (mHvacManager != null) {
+            try {
+                mDataStore.setAcState(mHvacManager.getBooleanProperty(CarHvacManager.ID_ZONED_AC_ON,
+                        VehicleZone.VEHICLE_ZONE_ROW_1_ALL));
+            } catch (android.car.CarNotConnectedException e) {
+                Log.e(TAG, "Car not connected in fetchAcState");
+            }
+        }
+    }
+
+    public boolean getAcState() {
+        return mDataStore.getAcState();
+    }
+
+    public void setAcState(final boolean state) {
+        mDataStore.setAcState(state);
+        final AsyncTask<Void, Void, Void> task = new AsyncTask<Void, Void, Void>() {
+            protected Void doInBackground(Void... unused) {
+                if (mHvacManager != null) {
+                    try {
+                        mHvacManager.setBooleanProperty(CarHvacManager.ID_ZONED_AC_ON,
+                                VehicleZone.VEHICLE_ZONE_ROW_1_ALL, state);
+                    } catch (android.car.CarNotConnectedException e) {
+                        Log.e(TAG, "Car not connected in setAcState");
+                    }
+                }
+                return null;
+            }
+        };
+        task.execute();
+    }
+
+    private int fanPositionToAirflowIndex(int fanPosition) {
+        for (int i = 0; i < AIRFLOW_STATES.length; i++) {
+            if (fanPosition == AIRFLOW_STATES[i]) {
+                return i;
+            }
+        }
+        Log.e(TAG, "Unknown fan position " + fanPosition + ". Returning default.");
+        return AIRFLOW_STATES[0];
+    }
+
+    private void fetchAirflow(int zone) {
+        if (mHvacManager != null) {
+            zone = VehicleZone.VEHICLE_ZONE_ROW_1_ALL; // Car specific workaround.
+            try {
+                int val = mHvacManager.getIntProperty(CarHvacManager.ID_ZONED_FAN_POSITION, zone);
+                mDataStore.setAirflow(zone, fanPositionToAirflowIndex(val));
+            } catch (android.car.CarNotConnectedException e) {
+                Log.e(TAG, "Car not connected in fetchAirFlow");
+            }
+        }
+    }
+
+    public int getAirflowIndex(int zone) {
+        return mDataStore.getAirflow(zone);
+    }
+
+    public void setAirflowIndex(final int zone, final int index) {
+        mDataStore.setAirflow(zone, index);
+        int override = VehicleZone.VEHICLE_ZONE_ROW_1_ALL; // Car specific workaround.
+        int val = AIRFLOW_STATES[index];
+        setFanDirection(override, val);
+    }
+
+    public void setFanDirection(final int direction) {
+        mDataStore.setAirflow(VehicleZone.VEHICLE_ZONE_ROW_1_ALL, direction);
+        setFanDirection(VehicleZone.VEHICLE_ZONE_ROW_1_ALL, direction);
+    }
+
+    private void setFanDirection(final int zone, final int direction) {
+        final AsyncTask<Void, Void, Void> task = new AsyncTask<Void, Void, Void>() {
+            protected Void doInBackground(Void... unused) {
+                if (mHvacManager != null) {
+                    try {
+                        mHvacManager.setIntProperty(
+                                CarHvacManager.ID_ZONED_FAN_POSITION, zone, direction);
+                    } catch (android.car.CarNotConnectedException e) {
+                        Log.e(TAG, "Car not connected in setAirflowIndex");
+                    }
+                }
+                return null;
+            }
+        };
+        task.execute();
+    }
+
+
+    private void fetchAirCirculation() {
+        if (mHvacManager != null) {
+            try {
+                mDataStore.setAirCirculationState(mHvacManager
+                        .getBooleanProperty(CarHvacManager.ID_ZONED_AIR_RECIRCULATION_ON,
+                                VehicleZone.VEHICLE_ZONE_ROW_1_ALL));
+            } catch (android.car.CarNotConnectedException e) {
+                Log.e(TAG, "Car not connected in fetchAirCirculationState");
+            }
+        }
+    }
+
+    public boolean getAirCirculationState() {
+        return mDataStore.getAirCirculationState();
+    }
+
+    public void setAirCirculation(final boolean state) {
+        mDataStore.setAirCirculationState(state);
+        final AsyncTask<Void, Void, Void> task = new AsyncTask<Void, Void, Void>() {
+            protected Void doInBackground(Void... unused) {
+                if (mHvacManager != null) {
+                    try {
+                        mHvacManager.setBooleanProperty(
+                                CarHvacManager.ID_ZONED_AIR_RECIRCULATION_ON,
+                                VehicleZone.VEHICLE_ZONE_ROW_1_ALL, state);
+                    } catch (android.car.CarNotConnectedException e) {
+                        Log.e(TAG, "Car not connected in setAcState");
+                    }
+                }
+                return null;
+            }
+        };
+        task.execute();
+    }
+
+    public boolean getAutoModeState() {
+        return mDataStore.getAutoModeState();
+    }
+
+    public void setAutoMode(final boolean state) {
+        mDataStore.setAutoModeState(state);
+        final AsyncTask<Void, Void, Void> task = new AsyncTask<Void, Void, Void>() {
+            protected Void doInBackground(Void... unused) {
+                if (mHvacManager != null) {
+                    try {
+                        mHvacManager.setBooleanProperty(CarHvacManager.ID_ZONED_AUTOMATIC_MODE_ON,
+                                VehicleZone.VEHICLE_ZONE_ROW_1_ALL, state);
+                    } catch (android.car.CarNotConnectedException e) {
+                        Log.e(TAG, "Car not connected in setAutoModeState");
+                    }
+                }
+                return null;
+            }
+        };
+        task.execute();
+    }
+
+    public boolean getHvacPowerState() {
+        return mDataStore.getHvacPowerState();
+    }
+
+    private void fetchHvacPowerState() {
+        if (mHvacManager != null) {
+            try {
+                mDataStore.setHvacPowerState(mHvacManager.getBooleanProperty(
+                        CarHvacManager.ID_ZONED_HVAC_POWER_ON, VehicleZone.VEHICLE_ZONE_ROW_1_ALL));
+            } catch (android.car.CarNotConnectedException e) {
+                Log.e(TAG, "Car not connected in fetchHvacPowerState");
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/src/com/android/car/hvac/HvacPolicy.java b/src/com/android/car/hvac/HvacPolicy.java
new file mode 100644 (file)
index 0000000..de46285
--- /dev/null
@@ -0,0 +1,102 @@
+/*
+ * 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.android.car.hvac;
+
+import android.car.hardware.CarPropertyConfig;
+import android.car.hardware.hvac.CarHvacManager;
+import android.content.Context;
+
+import java.util.List;
+
+public class HvacPolicy {
+    private float mMaxHardwareTemp;
+    private float mMinHardwareTemp;
+    private int mMaxHardwareFanSpeed;
+
+    private final boolean mHardwareUsesCelsius;
+    private final boolean mUserUsesCelsius;
+
+    public HvacPolicy(Context context, List<CarPropertyConfig> properties) {
+        //TODO handle max / min per each zone
+        for (CarPropertyConfig config : properties) {
+            switch (config.getPropertyId()) {
+                case CarHvacManager.ID_ZONED_FAN_SPEED_SETPOINT: {
+                    mMaxHardwareFanSpeed = (int) config.getMaxValue();
+                } break;
+
+                case CarHvacManager.ID_ZONED_TEMP_SETPOINT: {
+                    mMaxHardwareTemp = (float) config.getMaxValue();
+                    mMinHardwareTemp = (float) config.getMinValue();
+                } break;
+            }
+        }
+
+        mHardwareUsesCelsius = context.getResources().getBoolean(R.bool.config_hardwareUsesCelsius);
+        mUserUsesCelsius = context.getResources().getBoolean(R.bool.config_userUsesCelsius);
+    }
+
+    public float userToHardwareTemp(int temp) {
+        if (!mUserUsesCelsius && mHardwareUsesCelsius) {
+            return fahrenheitToCelsius(temp);
+        }
+
+        if (mUserUsesCelsius && !mHardwareUsesCelsius) {
+            return celsiusToFahrenheit(temp);
+        }
+
+        return temp;
+    }
+
+    public int hardwareToUserTemp(float temp) {
+        if (mHardwareUsesCelsius && !mUserUsesCelsius) {
+            return (int) celsiusToFahrenheit(temp);
+        }
+
+        if (!mHardwareUsesCelsius && mUserUsesCelsius) {
+            return (int) fahrenheitToCelsius(temp);
+        }
+
+        return (int) temp;
+    }
+
+    private float celsiusToFahrenheit(float c) {
+        return c * 9 / 5 + 32;
+    }
+
+    private float fahrenheitToCelsius(float f) {
+        return (f - 32) * 5 / 9;
+    }
+
+    public int userToHardwareFanSpeed(int speed) {
+        return getMaxHardwareFanSpeed() * speed / 100;
+    }
+
+    public int hardwareToUserFanSpeed(int speed) {
+        return speed * 100 / getMaxHardwareFanSpeed();
+    }
+
+    public int getMaxHardwareFanSpeed() {
+        return mMaxHardwareFanSpeed;
+    }
+
+    public float getMaxHardwareTemp() {
+        return mMaxHardwareTemp;
+    }
+
+    public float getMinHardwareTemp() {
+        return mMinHardwareTemp;
+    }
+}
diff --git a/src/com/android/car/hvac/HvacUiService.java b/src/com/android/car/hvac/HvacUiService.java
new file mode 100644 (file)
index 0000000..5af2ac6
--- /dev/null
@@ -0,0 +1,262 @@
+/*
+ * 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.android.car.hvac;
+
+import android.app.Service;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.ServiceConnection;
+import android.content.res.Resources;
+import android.graphics.PixelFormat;
+import android.os.Handler;
+import android.os.IBinder;
+import android.util.DisplayMetrics;
+import android.util.Log;
+import android.view.Gravity;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.WindowManager;
+import android.widget.LinearLayout;
+import com.android.car.hvac.controllers.HvacPanelController;
+import com.android.car.hvac.ui.TemperatureBarOverlay;
+
+/**
+ * Creates a sliding panel for HVAC controls and adds it to the window manager above SystemUI.
+ */
+public class HvacUiService extends Service {
+    private static final String TAG = "HvacUiService";
+
+    private WindowManager mWindowManager;
+
+    private View mPanel;
+    private View mContainer;
+
+    private int mPanelCollapsedHeight;
+    private int mPanelFullExpandedHeight;
+    private int mScreenBottom;
+    private int mScreenWidth;
+
+    private int mTemperatureSideMargin;
+    private int mTemperatureOverlayWidth;
+    private int mTemperatureOverlayHeight;
+    private int mTemperatureBarCollapsedHeight;
+
+    private HvacPanelController mHvacPanelController;
+    private HvacController mHvacController;
+
+    private ViewGroup mDriverTemperatureBarTouchOverlay;
+    private ViewGroup mPassengerTemperatureBarTouchOverlay;
+    private TemperatureBarOverlay mDriverTemperatureBar;
+    private TemperatureBarOverlay mPassengerTemperatureBar;
+
+    @Override
+    public IBinder onBind(Intent intent) {
+        throw new UnsupportedOperationException("Not yet implemented.");
+    }
+
+    @Override
+    public void onCreate() {
+        Resources res = getResources();
+        mPanelCollapsedHeight = res.getDimensionPixelSize(R.dimen.car_hvac_panel_collapsed_height);
+        mPanelFullExpandedHeight
+                = res.getDimensionPixelSize(R.dimen.car_hvac_panel_full_expanded_height);
+
+        mTemperatureSideMargin = res.getDimensionPixelSize(R.dimen.temperature_side_margin);
+        mTemperatureOverlayWidth = res.getDimensionPixelSize(R.dimen.temperature_bar_width_expanded);
+        mTemperatureOverlayHeight
+                = res.getDimensionPixelSize(R.dimen.car_hvac_panel_full_expanded_height);
+        mTemperatureBarCollapsedHeight
+                = res.getDimensionPixelSize(R.dimen.temperature_bar_collapsed_height);
+
+        LayoutInflater inflater = (LayoutInflater) getSystemService(LAYOUT_INFLATER_SERVICE);
+        mWindowManager = (WindowManager) getSystemService(WINDOW_SERVICE);
+
+        DisplayMetrics metrics = new DisplayMetrics();
+        mWindowManager.getDefaultDisplay().getRealMetrics(metrics);
+        mScreenBottom = metrics.heightPixels - getStatusBarHeight();
+        mScreenWidth = metrics.widthPixels;
+
+        WindowManager.LayoutParams params = new WindowManager.LayoutParams(
+                WindowManager.LayoutParams.WRAP_CONTENT,
+                WindowManager.LayoutParams.WRAP_CONTENT,
+                WindowManager.LayoutParams.TYPE_DISPLAY_OVERLAY,
+                WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
+                        | WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE
+                        | WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS,
+                PixelFormat.TRANSLUCENT);
+
+        params.packageName = this.getPackageName();
+        params.gravity = Gravity.TOP | Gravity.LEFT;
+
+        params.x = 0;
+        params.y = 0;
+
+        params.width = mScreenWidth;
+        params.height = mScreenBottom;
+        disableAnimations(params);
+
+        mContainer = inflater.inflate(R.layout.hvac_panel, null);
+        mContainer.setLayoutParams(params);
+
+        // The top padding should be calculated on the screen height and the height of the
+        // expanded hvac panel. The space defined by the padding is meant to be clickable for
+        // dismissing the hvac panel.
+        int topPadding = mScreenBottom - mPanelFullExpandedHeight;
+        mContainer.setPadding(0, topPadding, 0, 0);
+
+        mContainer.setFocusable(false);
+        mContainer.setClickable(false);
+        mContainer.setFocusableInTouchMode(false);
+
+        mPanel = mContainer.findViewById(R.id.hvac_center_panel);
+        mPanel.getLayoutParams().height = mPanelCollapsedHeight;
+
+        mWindowManager.addView(mContainer, params);
+
+        createTemperatureBars(inflater);
+        mHvacPanelController = new HvacPanelController(this /* context */, mContainer,
+                mWindowManager, mDriverTemperatureBar, mPassengerTemperatureBar,
+                mDriverTemperatureBarTouchOverlay, mPassengerTemperatureBarTouchOverlay);
+        Intent bindIntent = new Intent(this /* context */, HvacController.class);
+        if (!bindService(bindIntent, mServiceConnection, Context.BIND_AUTO_CREATE)) {
+            Log.e(TAG, "Failed to connect to HvacController.");
+        }
+    }
+
+    @Override
+    public void onDestroy() {
+        mWindowManager.removeView(mPanel);
+        if(mHvacController != null){
+            unbindService(mServiceConnection);
+        }
+    }
+
+    private ServiceConnection mServiceConnection = new ServiceConnection() {
+        @Override
+        public void onServiceConnected(ComponentName className, IBinder service) {
+            mHvacController = ((HvacController.LocalBinder) service).getService();
+            final Context context = HvacUiService.this;
+
+            final Runnable r = new Runnable() {
+                @Override
+                public void run() {
+                    // Once the hvac controller has refreshed its values from the vehicle,
+                    // bind all the values.
+                    mHvacPanelController.updateHvacController(mHvacController);
+                }
+            };
+
+            if (mHvacController != null) {
+                mHvacController.requestRefresh(r, new Handler(context.getMainLooper()));
+            }
+        }
+
+        @Override
+        public void onServiceDisconnected(ComponentName className) {
+            mHvacController = null;
+            mHvacPanelController.updateHvacController(null);
+            //TODO: b/29126575 reconnect to controller if it is restarted
+        }
+    };
+
+    private WindowManager.LayoutParams createClickableOverlayLayoutParam() {
+        return new WindowManager.LayoutParams(
+                WindowManager.LayoutParams.WRAP_CONTENT,
+                WindowManager.LayoutParams.WRAP_CONTENT,
+                WindowManager.LayoutParams.TYPE_DISPLAY_OVERLAY,
+                WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
+                        | WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS,
+                PixelFormat.TRANSLUCENT);
+    }
+
+    private TemperatureBarOverlay createTemperatureBarOverlay(LayoutInflater inflater,
+            int gravity) {
+        TemperatureBarOverlay button = (TemperatureBarOverlay) inflater
+                .inflate(R.layout.hvac_temperature_bar_overlay, null);
+
+        WindowManager.LayoutParams params = createClickableOverlayLayoutParam();
+        params.gravity = gravity;
+        params.x = mTemperatureSideMargin;
+        params.y = mScreenBottom - mTemperatureOverlayHeight;
+        params.width = mTemperatureOverlayWidth;
+        params.height = mTemperatureOverlayHeight;
+
+        disableAnimations(params);
+        button.setLayoutParams(params);
+        mWindowManager.addView(button, params);
+
+        return button;
+    }
+
+    /**
+     * Creates a touchable overlay in the dimensions of a collapsed {@link TemperatureBarOverlay}.
+     * @return a {@link ViewGroup} that was added to the {@link WindowManager}
+     */
+    private ViewGroup addTemperatureTouchOverlay(int gravity) {
+        WindowManager.LayoutParams params = createClickableOverlayLayoutParam();
+        params.gravity = gravity;
+        params.x = mTemperatureSideMargin;
+        params.y = mScreenBottom - mTemperatureBarCollapsedHeight;
+        params.width = mTemperatureOverlayWidth;
+        params.height = mTemperatureBarCollapsedHeight;
+
+        ViewGroup overlay = new LinearLayout(this /* context */);
+        overlay.setLayoutParams(params);
+        mWindowManager.addView(overlay, params);
+        return overlay;
+    }
+
+    private void createTemperatureBars(LayoutInflater inflater) {
+        mDriverTemperatureBar
+                = createTemperatureBarOverlay(inflater, Gravity.TOP | Gravity.LEFT);
+        mPassengerTemperatureBar
+                = createTemperatureBarOverlay(inflater, Gravity.TOP | Gravity.RIGHT);
+
+        // Create a transparent overlay that is the size of the collapsed temperature bar.
+        // It will receive touch events and trigger the expand/collapse of the panel. This is
+        // necessary since changing the height of the temperature bar overlay dynamically, causes
+        // a jank when WindowManager updates the view with a new height. This hack allows us
+        // to maintain the temperature bar overlay at constant (expanded) height and just
+        // update whether or not it is touchable/clickable.
+        mDriverTemperatureBarTouchOverlay
+                = addTemperatureTouchOverlay(Gravity.TOP | Gravity.LEFT);
+        mPassengerTemperatureBarTouchOverlay
+                = addTemperatureTouchOverlay(Gravity.TOP | Gravity.RIGHT);
+    }
+
+    /**
+     * Disables animations when window manager updates a child view.
+     */
+    private void disableAnimations(WindowManager.LayoutParams params) {
+        try {
+            int currentFlags = (Integer) params.getClass().getField("privateFlags").get(params);
+            params.getClass().getField("privateFlags").set(params, currentFlags | 0x00000040);
+        } catch (Exception e) {
+            Log.e(TAG, "Error disabling animation");
+        }
+    }
+
+    private int getStatusBarHeight() {
+        int result = 0;
+        int resourceId = getResources().getIdentifier("status_bar_height", "dimen", "android");
+        if (resourceId > 0) {
+            result = getResources().getDimensionPixelSize(resourceId);
+        }
+        return result;
+    }
+}
diff --git a/src/com/android/car/hvac/LocalHvacPropertyService.java b/src/com/android/car/hvac/LocalHvacPropertyService.java
new file mode 100644 (file)
index 0000000..1c115f7
--- /dev/null
@@ -0,0 +1,141 @@
+/*
+ * 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.android.car.hvac;
+
+import android.car.VehicleAreaType;
+import android.car.hardware.CarPropertyConfig;
+import android.car.hardware.CarPropertyValue;
+import android.car.hardware.hvac.CarHvacManager;
+import android.car.hardware.property.CarPropertyEvent;
+import android.car.hardware.property.ICarProperty;
+import android.car.hardware.property.ICarPropertyEventListener;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.util.Pair;
+import com.android.car.vehiclenetwork.VehicleNetworkConsts.VehicleWindow;
+import com.android.car.vehiclenetwork.VehicleNetworkConsts.VehicleZone;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * A local {@link ICarProperty} that is used to mock up data for HVAC.
+ */
+public class LocalHvacPropertyService {
+    private static final int DRIVER_ZONE_ID = VehicleZone.VEHICLE_ZONE_ROW_1_LEFT;
+    private static final int PASSENGER_ZONE_ID = VehicleZone.VEHICLE_ZONE_ROW_1_RIGHT;
+
+    private static final float MIN_TEMP = 16;
+    private static final float MAX_TEMP = 32;
+
+    private static final int MAX_FAN_SPEED = 7;
+    private static final int MIN_FAN_SPEED = 1;
+
+    private static final int DEFAULT_AREA_ID = 0;
+
+    private static final boolean DEFAULT_POWER_ON = true;
+    private static final boolean DEFAULT_DEFROSTER_ON = true;
+    private static final boolean DEFAULT_AIR_CIRCULATION_ON = true;
+    private static final boolean DEFAULT_AC_ON = true;
+    private static final boolean DEFAULT_AUTO_MODE = false;
+    private static final int DEFAULT_FAN_SPEED = 3;
+    private static final int DEFAULT_FAN_POSITION = 2;
+    private static final float DEFAULT_DRIVER_TEMP = 16;
+    private static final float DEFAULT_PASSENGER_TEMP = 25;
+
+    private final List<CarPropertyConfig> mPropertyList;
+    private final Map<Pair, Object> mProperties = new HashMap<>();
+    private final List<ICarPropertyEventListener> mListeners = new ArrayList<>();
+
+    public LocalHvacPropertyService() {
+        CarPropertyConfig fanSpeedConfig = CarPropertyConfig.newBuilder(Integer.class,
+                CarHvacManager.ID_ZONED_FAN_SPEED_SETPOINT,
+                VehicleAreaType.VEHICLE_AREA_TYPE_ZONE)
+                .addAreaConfig(DEFAULT_AREA_ID, MIN_FAN_SPEED, MAX_FAN_SPEED).build();
+
+        CarPropertyConfig temperatureConfig = CarPropertyConfig.newBuilder(Float.class,
+                CarHvacManager.ID_ZONED_TEMP_SETPOINT,
+                VehicleAreaType.VEHICLE_AREA_TYPE_ZONE)
+                .addAreaConfig(DEFAULT_AREA_ID, MIN_TEMP, MAX_TEMP).build();
+
+        mPropertyList = new ArrayList<>(2);
+        mPropertyList.addAll(Arrays.asList(fanSpeedConfig, temperatureConfig));
+        setupDefaultValues();
+    }
+
+    private final IBinder mCarPropertyService = new ICarProperty.Stub(){
+        @Override
+        public void registerListener(ICarPropertyEventListener listener) throws RemoteException {
+            mListeners.add(listener);
+        }
+
+        @Override
+        public void unregisterListener(ICarPropertyEventListener listener) throws RemoteException {
+            mListeners.remove(listener);
+        }
+
+        @Override
+        public List<CarPropertyConfig> getPropertyList() throws RemoteException {
+            return mPropertyList;
+        }
+
+        @Override
+        public CarPropertyValue getProperty(int prop, int zone) throws RemoteException {
+            return new CarPropertyValue(prop, zone, mProperties.get(new Pair(prop, zone)));
+        }
+
+        @Override
+        public void setProperty(CarPropertyValue prop) throws RemoteException {
+            mProperties.put(new Pair(prop.getPropertyId(), prop.getAreaId()), prop.getValue());
+            for (ICarPropertyEventListener listener : mListeners) {
+                listener.onEvent(
+                        new CarPropertyEvent(CarPropertyEvent.PROPERTY_EVENT_PROPERTY_CHANGE, prop));
+            }
+        }
+    };
+
+    public IBinder getCarPropertyService() {
+        return mCarPropertyService;
+    }
+
+    private void setupDefaultValues() {
+        mProperties.put(new Pair<Integer,Integer>(CarHvacManager.ID_ZONED_HVAC_POWER_ON,
+                VehicleZone.VEHICLE_ZONE_ROW_1_ALL), DEFAULT_POWER_ON);
+        mProperties.put(new Pair<Integer,Integer>(CarHvacManager.ID_WINDOW_DEFROSTER_ON,
+                VehicleWindow.VEHICLE_WINDOW_FRONT_WINDSHIELD), DEFAULT_DEFROSTER_ON);
+        mProperties.put(new Pair<Integer,Integer>(CarHvacManager.ID_WINDOW_DEFROSTER_ON,
+                VehicleWindow.VEHICLE_WINDOW_REAR_WINDSHIELD), DEFAULT_DEFROSTER_ON);
+        mProperties.put(new Pair<Integer,Integer>(CarHvacManager.ID_ZONED_AIR_RECIRCULATION_ON,
+                VehicleZone.VEHICLE_ZONE_ROW_1_ALL), DEFAULT_AIR_CIRCULATION_ON);
+        mProperties.put(new Pair<Integer, Integer>(CarHvacManager.ID_ZONED_AC_ON,
+                VehicleZone.VEHICLE_ZONE_ROW_1_ALL), DEFAULT_AC_ON);
+        mProperties.put(new Pair<Integer, Integer>(CarHvacManager.ID_ZONED_AUTOMATIC_MODE_ON,
+                VehicleZone.VEHICLE_ZONE_ROW_1_ALL), DEFAULT_AUTO_MODE);
+
+        mProperties.put(new Pair<Integer, Integer>(CarHvacManager.ID_ZONED_FAN_SPEED_SETPOINT,
+                VehicleZone.VEHICLE_ZONE_ROW_1_ALL), DEFAULT_FAN_SPEED);
+        mProperties.put(new Pair<Integer, Integer>(CarHvacManager.ID_ZONED_FAN_POSITION,
+                VehicleZone.VEHICLE_ZONE_ROW_1_ALL), DEFAULT_FAN_POSITION);
+
+        mProperties.put(new Pair<Integer, Integer>(CarHvacManager.ID_ZONED_TEMP_SETPOINT,
+                DRIVER_ZONE_ID), DEFAULT_DRIVER_TEMP);
+        mProperties.put(new Pair<Integer, Integer>(CarHvacManager.ID_ZONED_TEMP_SETPOINT,
+                PASSENGER_ZONE_ID), DEFAULT_PASSENGER_TEMP);
+    }
+}
diff --git a/src/com/android/car/hvac/controllers/FanDirectionButtonsController.java b/src/com/android/car/hvac/controllers/FanDirectionButtonsController.java
new file mode 100644 (file)
index 0000000..efa70ed
--- /dev/null
@@ -0,0 +1,63 @@
+/*
+ * 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.android.car.hvac.controllers;
+
+import android.util.SparseIntArray;
+import com.android.car.hvac.HvacController;
+import com.android.car.hvac.ui.FanDirectionButtons;
+
+import com.android.car.vehiclenetwork.VehicleNetworkConsts.VehicleHvacFanDirection;
+
+/**
+ * A controller to handle changes in the fan direction. Also maps fan directions specified
+ * in the {@link FanDirectionButtons} to the {@link VehicleHvacFanDirection} in the vehicle
+ * hardware.
+ */
+public class FanDirectionButtonsController {
+    private final static int FAN_DIRECTION_COUNT = 4;
+
+    private final FanDirectionButtons mFanDirectionButtons;
+    private final HvacController mHvacController;
+    private SparseIntArray mFanDirectionMap = new SparseIntArray(FAN_DIRECTION_COUNT);
+
+    public FanDirectionButtonsController(FanDirectionButtons speedBar, HvacController controller) {
+        mFanDirectionButtons = speedBar;
+        mHvacController = controller;
+        initialize();
+    }
+
+    private void initialize() {
+        // Note Car specific values are being used here, as not all cars have the floor
+        // and defroster fan direction.
+        mFanDirectionMap.put(FanDirectionButtons.FAN_DIRECTION_FACE,
+                VehicleHvacFanDirection.VEHICLE_HVAC_FAN_DIRECTION_FACE);
+        mFanDirectionMap.put(FanDirectionButtons.FAN_DIRECTION_FACE_FLOOR,
+                VehicleHvacFanDirection.VEHICLE_HVAC_FAN_DIRECTION_FACE_AND_FLOOR);
+        mFanDirectionMap.put(FanDirectionButtons.FAN_DIRECTION_FLOOR,
+                VehicleHvacFanDirection.VEHICLE_HVAC_FAN_DIRECTION_FLOOR);
+        mFanDirectionMap.put(FanDirectionButtons.FAN_DIRECTION_FLOOR_DEFROSTER,
+                VehicleHvacFanDirection.VEHICLE_HVAC_FAN_DIRECTION_DEFROST_AND_FLOOR);
+        mFanDirectionButtons.setFanDirectionClickListener(mListener);
+    }
+
+    private final FanDirectionButtons.FanDirectionClickListener mListener
+            = new FanDirectionButtons.FanDirectionClickListener() {
+        @Override
+        public void onFanDirectionClicked(@FanDirectionButtons.FanDirection int direction) {
+            mHvacController.setFanDirection(mFanDirectionMap.get(direction));
+        }
+    };
+}
diff --git a/src/com/android/car/hvac/controllers/FanSpeedBarController.java b/src/com/android/car/hvac/controllers/FanSpeedBarController.java
new file mode 100644 (file)
index 0000000..1712c17
--- /dev/null
@@ -0,0 +1,98 @@
+/*
+ * 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.android.car.hvac.controllers;
+
+import android.util.Log;
+import com.android.car.hvac.HvacController;
+import com.android.car.hvac.ui.FanSpeedBar;
+
+/**
+ * Controller for the fan speed bar to adjust fan speed.
+ */
+public class FanSpeedBarController {
+    private final static String TAG = "FanSpeedBarCtrl";
+
+    private final FanSpeedBar mFanSpeedBar;
+    private final HvacController mHvacController;
+    private int mCurrentFanSpeed;
+
+    // Note the following are car specific values.
+    private static final int MAX_FAN_SPEED = 6;
+    private static final int MIN_FAN_SPEED = 1;
+
+    public FanSpeedBarController(FanSpeedBar speedBar, HvacController controller) {
+        mFanSpeedBar = speedBar;
+        mHvacController = controller;
+        initialize();
+    }
+
+    private void initialize() {
+        mFanSpeedBar.setFanspeedButtonClickListener(mClickListener);
+        mHvacController.registerCallback(mCallback);
+        // During initialization, we do not need to animate the changes.
+        handleFanSpeedUpdate(mHvacController.getFanSpeed(), false /* animateUpdate */);
+    }
+
+    private void handleFanSpeedUpdate(int speed, boolean animateUpdate) {
+        if (Log.isLoggable(TAG, Log.DEBUG)) {
+            Log.d(TAG, "Fan speed bar being set to value: " + speed);
+        }
+
+        mCurrentFanSpeed = speed;
+        if (mCurrentFanSpeed == MIN_FAN_SPEED) {
+            mFanSpeedBar.setOff();
+        } else if (mCurrentFanSpeed >= MAX_FAN_SPEED) {
+            mFanSpeedBar.setMax();
+        } else if (mCurrentFanSpeed < MAX_FAN_SPEED && mCurrentFanSpeed > MIN_FAN_SPEED) {
+            // Note car specific values being used:
+            // The lowest fanspeed is represented by the off button, the first segment
+            // actually represents the second fan speed setting.
+            if (animateUpdate) {
+                mFanSpeedBar.animateToSpeedSegment(mCurrentFanSpeed - 1);
+            } else {
+                mFanSpeedBar.setSpeedSegment(mCurrentFanSpeed - 1);
+            }
+        }
+    }
+
+    private FanSpeedBar.FanSpeedButtonClickListener mClickListener
+            = new FanSpeedBar.FanSpeedButtonClickListener() {
+        @Override
+        public void onMaxButtonClicked() {
+            mHvacController.setFanSpeed(MAX_FAN_SPEED);
+        }
+
+        @Override
+        public void onOffButtonClicked() {
+            mHvacController.setFanSpeed(MIN_FAN_SPEED);
+        }
+
+        @Override
+        public void onFanSpeedSegmentClicked(int position) {
+            // Note car specific values being used:
+            // The lowest fanspeed is represented by the off button, the first segment
+            // actually represents the second fan speed setting.
+            mHvacController.setFanSpeed(position + 1);
+        }
+    };
+
+    private HvacController.Callback mCallback = new HvacController.Callback() {
+        @Override
+        public void onFanSpeedChange(int speed) {
+            handleFanSpeedUpdate(speed, true /* animateUpdate */);
+        }
+    };
+}
diff --git a/src/com/android/car/hvac/controllers/HvacPanelController.java b/src/com/android/car/hvac/controllers/HvacPanelController.java
new file mode 100644 (file)
index 0000000..771c2ed
--- /dev/null
@@ -0,0 +1,598 @@
+/*
+ * 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.android.car.hvac.controllers;
+
+import android.animation.Animator;
+import android.animation.AnimatorSet;
+import android.animation.ObjectAnimator;
+import android.animation.ValueAnimator;
+import android.content.Context;
+import android.content.res.Resources;
+import android.graphics.drawable.Drawable;
+import android.support.annotation.IntDef;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.WindowManager;
+import android.widget.ImageView;
+import com.android.car.hvac.HvacController;
+import com.android.car.hvac.R;
+import com.android.car.hvac.ui.FanDirectionButtons;
+import com.android.car.hvac.ui.FanSpeedBar;
+import com.android.car.hvac.ui.HvacPanelRow;
+import com.android.car.hvac.ui.SeatWarmerButton;
+import com.android.car.hvac.ui.TemperatureBarOverlay;
+import com.android.car.hvac.ui.ToggleButton;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * A state machine to control transition from various HVAC UI layouts.
+ */
+public class HvacPanelController {
+    private static final int PANEL_ANIMATION_TIME_MS = 200;
+    private static final int PANEL_COLLAPSE_ANIMATION_TIME_MS = 500;
+
+    private static final int PANEL_ANIMATION_DELAY_MS = 100;
+    private static final int PANEL_ANIMATION_LONG_DELAY_MS = 3 * PANEL_ANIMATION_DELAY_MS;
+
+    private static final float DISABLED_BUTTON_ALPHA = 0.20f;
+    private static final float ENABLED_BUTTON_ALPHA = 1.0f;
+
+    private static final int STATE_COLLAPSED = 0;
+    private static final int STATE_COLLAPSED_DIMMED = 1;
+    private static final int STATE_FULL_EXPANDED = 2;
+
+    @IntDef({STATE_COLLAPSED,
+            STATE_COLLAPSED_DIMMED,
+            STATE_FULL_EXPANDED})
+    private @interface HvacPanelState {}
+
+    private @HvacPanelState int mCurrentState;
+
+    private int mPanelCollapsedHeight;
+    private int mPanelFullExpandedHeight;
+
+    private View mPanel;
+    private View mContainer;
+
+    private TemperatureBarOverlay mDriverTemperature;
+    private TemperatureBarOverlay mPassengerTemperature;
+
+    private ViewGroup mDriverTemperatureTouchOverlay;
+    private ViewGroup mPassengerTemperatureTouchOverlay;
+
+    private SeatWarmerButton mDriverSeatWarmer;
+    private SeatWarmerButton mPassengerSeatWarmer;
+
+    private ToggleButton mAcButton;
+    private ToggleButton mRecycleAirButton;
+
+    private ToggleButton mFrontDefrosterButton;
+    private ToggleButton mRearDefrosterButton;
+
+    private Drawable mAutoOnDrawable;
+    private Drawable mAutoOffDrawable;
+
+    private ImageView mAutoButton;
+
+    private HvacPanelRow mPanelTopRow;
+    private HvacPanelRow mPanelBottomRow;
+
+    private FanSpeedBar mFanSpeedBar;
+    private FanDirectionButtons mFanDirectionButtons;
+
+    private float mTopPanelMaxAlpha = 1.0f;
+
+    private WindowManager mWindowManager;
+
+    private HvacPanelStateTransition mTransition;
+
+    private View mHvacFanControlBackground;
+
+    private HvacController mHvacController;
+    private FanSpeedBarController mFanSpeedBarController;
+    private FanDirectionButtonsController mFanDirectionButtonsController;
+    private TemperatureController mTemperatureController;
+    private SeatWarmerController mSeatWarmerController;
+
+    private boolean mInAnimation;
+    private boolean mHvacIsOn;
+
+    // TODO: read from shared pref
+    private boolean mAutoMode;
+
+    public HvacPanelController(Context context, View container,
+            WindowManager windowManager, TemperatureBarOverlay driverTemperature,
+            TemperatureBarOverlay passengerTemperature, ViewGroup driverTemperatureBarTouchOverlay,
+            ViewGroup passengerTemperatureBarTouchOverlay) {
+
+        mCurrentState = STATE_COLLAPSED;
+        mWindowManager = windowManager;
+
+        Resources res = context.getResources();
+        mPanelCollapsedHeight = res.getDimensionPixelSize(R.dimen.car_hvac_panel_collapsed_height);
+        mPanelFullExpandedHeight
+                = res.getDimensionPixelSize(R.dimen.car_hvac_panel_full_expanded_height);
+
+        mAutoOffDrawable = res.getDrawable(R.drawable.ic_auto_off);
+        mAutoOnDrawable = res.getDrawable(R.drawable.ic_auto_on);
+
+        mDriverTemperature = driverTemperature;
+        mDriverTemperature.setBarOnClickListener(mPanelClickListener);
+
+        mPassengerTemperature = passengerTemperature;
+        mPassengerTemperature.setBarOnClickListener(mPanelClickListener);
+
+        mDriverTemperature.setCloseButtonOnClickListener(mOpenCloseToggleListener);
+        mPassengerTemperature.setCloseButtonOnClickListener(mOpenCloseToggleListener);
+
+        // Initially the hvac panel is collapsed, disable touches on the overlay container.
+        disableTouchOnOverlay(mDriverTemperature);
+        disableTouchOnOverlay(mPassengerTemperature);
+
+        mDriverTemperatureTouchOverlay = driverTemperatureBarTouchOverlay;
+        mPassengerTemperatureTouchOverlay = passengerTemperatureBarTouchOverlay;
+
+        mDriverTemperatureTouchOverlay.setOnClickListener(mOpenCloseToggleListener);
+        mPassengerTemperatureTouchOverlay.setOnClickListener(mOpenCloseToggleListener);
+
+        mContainer = container;
+        mContainer.setVisibility(View.INVISIBLE);
+        mContainer.setOnClickListener(mOpenCloseToggleListener);
+        mPanel = mContainer.findViewById(R.id.hvac_center_panel);
+
+        mHvacFanControlBackground = mPanel.findViewById(R.id.fan_control_bg);
+        mPanel.setOnClickListener(mPanelClickListener);
+
+        // Set up top row buttons
+        mPanelTopRow = (HvacPanelRow) mContainer.findViewById(R.id.top_row);
+
+        mAcButton = (ToggleButton) mPanelTopRow.findViewById(R.id.ac_button);
+        mAcButton.setToggleIcons(res.getDrawable(R.drawable.ic_ac_on),
+                res.getDrawable(R.drawable.ic_ac_off));
+
+        mRecycleAirButton = (ToggleButton) mPanelTopRow.findViewById(R.id.recycle_air_button);
+
+        mRecycleAirButton.setToggleIcons(res.getDrawable(R.drawable.ic_recycle_air_on),
+                res.getDrawable(R.drawable.ic_recycle_air_off));
+
+        // Setup bottom row buttons
+        mPanelBottomRow = (HvacPanelRow) mContainer.findViewById(R.id.bottom_row);
+
+        mAutoButton = (ImageView) mContainer.findViewById(R.id.auto_button);
+        mAutoButton.setOnClickListener(mAutoButtonClickListener);
+
+        mFrontDefrosterButton = (ToggleButton) mPanelBottomRow.findViewById(R.id.front_defroster);
+        mRearDefrosterButton = (ToggleButton) mPanelBottomRow.findViewById(R.id.rear_defroster);
+
+        mFrontDefrosterButton.setToggleIcons(res.getDrawable(R.drawable.ic_front_defroster_on),
+                res.getDrawable(R.drawable.ic_front_defroster_off));
+
+        mRearDefrosterButton.setToggleIcons(res.getDrawable(R.drawable.ic_rear_defroster_on),
+                res.getDrawable(R.drawable.ic_rear_defroster_off));
+
+        mFanSpeedBar = (FanSpeedBar) mContainer.findViewById(R.id.fan_speed_bar);
+        mFanDirectionButtons = (FanDirectionButtons) mContainer.findViewById(R.id.fan_direction_buttons);
+
+        mDriverSeatWarmer = (SeatWarmerButton) mContainer.findViewById(R.id.left_seat_heater);
+        mPassengerSeatWarmer = (SeatWarmerButton) mContainer.findViewById(R.id.right_seat_heater);
+    }
+
+    public void updateHvacController(HvacController controller) {
+        //TODO: handle disconnected HvacController.
+        mHvacController = controller;
+
+        mFanSpeedBarController = new FanSpeedBarController(mFanSpeedBar, mHvacController);
+        mFanDirectionButtonsController
+                = new FanDirectionButtonsController(mFanDirectionButtons, mHvacController);
+        mTemperatureController = new TemperatureController(mPassengerTemperature,
+                mDriverTemperature, mHvacController);
+        mSeatWarmerController = new SeatWarmerController(mPassengerSeatWarmer,
+                mDriverSeatWarmer, mHvacController);
+
+        // Toggle buttons do not need additional logic to map between hardware
+        // and UI settings. Simply use a ToggleListener to handle clicks.
+        mAcButton.setIsOn(mHvacController.getAcState());
+        mAcButton.setToggleListener(new ToggleButton.ToggleListener() {
+            @Override
+            public void onToggled(boolean isOn) {
+                mHvacController.setAcState(isOn);
+            }
+        });
+
+        mFrontDefrosterButton.setIsOn(mHvacController.getFrontDefrosterState());
+        mFrontDefrosterButton.setToggleListener(new ToggleButton.ToggleListener() {
+            @Override
+            public void onToggled(boolean isOn) {
+                mHvacController.setFrontDefrosterState(isOn);
+            }
+        });
+
+        mRearDefrosterButton.setIsOn(mHvacController.getRearDefrosterState());
+        mRearDefrosterButton.setToggleListener(new ToggleButton.ToggleListener() {
+            @Override
+            public void onToggled(boolean isOn) {
+                mHvacController.setRearDefrosterState(isOn);
+            }
+        });
+
+        mRecycleAirButton.setIsOn(mHvacController.getAirCirculationState());
+        mRecycleAirButton.setToggleListener(new ToggleButton.ToggleListener() {
+            @Override
+            public void onToggled(boolean isOn) {
+                mHvacController.setAirCirculation(isOn);
+            }
+        });
+
+        setAutoMode(mHvacController.getAutoModeState());
+
+        mHvacController.registerCallback(mToggleButtonCallbacks);
+        mToggleButtonCallbacks.onHvacPowerChange(mHvacController.getHvacPowerState());
+    }
+
+    private HvacController.Callback mToggleButtonCallbacks
+            = new HvacController.Callback() {
+        @Override
+        public void onAirCirculationChange(boolean isOn) {
+            mRecycleAirButton.setIsOn(isOn);
+        }
+
+        @Override
+        public void onFrontDefrosterChange(boolean isOn) {
+            mFrontDefrosterButton.setIsOn(isOn);
+        }
+
+        @Override
+        public void onRearDefrosterChange(boolean isOn) {
+            mRearDefrosterButton.setIsOn(isOn);
+        }
+
+        @Override
+        public void onAcStateChange(boolean isOn) {
+            mAcButton.setIsOn(isOn);
+        }
+
+        @Override
+        public void onAutoModeChange(boolean isOn) {
+            mAutoMode = isOn;
+            setAutoMode(mAutoMode);
+        }
+
+        @Override
+        public void onHvacPowerChange(boolean isOn) {
+            // When the HVAC Power is turned off, collapse the panel and fade the temperature
+            // bars. Also disable expanding the panel until power is back on.
+            mHvacIsOn = isOn;
+            if (!mHvacIsOn && mCurrentState == STATE_FULL_EXPANDED) {
+                transitionState(STATE_FULL_EXPANDED, STATE_COLLAPSED);
+            }
+
+            mDriverTemperature.setIsOn(mHvacIsOn);
+            mPassengerTemperature.setIsOn(mHvacIsOn);
+        }
+    };
+
+    /**
+     * Take the listeners and animators from a {@link AnimatorSet} and merge them to the
+     * input {@link Animator} and {@link android.animation.Animator.AnimatorListener} lists.
+     */
+    private void combineAnimationSet(List<Animator> animatorList,
+            List<Animator.AnimatorListener> listenerList, AnimatorSet set) {
+
+        ArrayList<Animator> list = set.getChildAnimations();
+        if (list != null) {
+            int size = list.size();
+            for (int i = 0; i < size; i++) {
+                animatorList.add(list.get(i));
+            }
+        }
+
+        ArrayList<Animator.AnimatorListener> listeners = set.getListeners();
+        if (listeners != null) {
+            int size = listeners.size();
+            for (int i = 0; i < size; i++) {
+                listenerList.add(listeners.get(i));
+            }
+        }
+    }
+
+    /**
+     * Play necessary animations between {@link HvacPanelState} transitions
+     */
+    private void transitionState(@HvacPanelState int startState, @HvacPanelState int endState) {
+        if (startState == endState || mInAnimation) {
+            return;
+        }
+
+        List<Animator> animationList = new ArrayList<>();
+        List<Animator.AnimatorListener> listenerList = new ArrayList<>();
+        ValueAnimator heightAnimator = getPanelHeightAnimator(startState, endState);
+        mTransition = new HvacPanelStateTransition(startState, endState);
+        ValueAnimator fanBgAlphaAnimator;
+        switch (endState) {
+            case STATE_COLLAPSED:
+                // Transition to collapsed state:
+                // 1. Collapse the temperature bars.
+                // 2. Collapse the top and bottom panel, staggered with a different delay.
+                // 3. Decrease height of the hvac center panel, but maintain container height.
+                // 4. Fade the background of the fan controls seperately to create staggered effect.
+                animationList.add(heightAnimator);
+                heightAnimator.setDuration(PANEL_COLLAPSE_ANIMATION_TIME_MS);
+                fanBgAlphaAnimator
+                        = ObjectAnimator.ofFloat(mHvacFanControlBackground, View.ALPHA, 1, 0)
+                        .setDuration(PANEL_COLLAPSE_ANIMATION_TIME_MS);
+                fanBgAlphaAnimator.setStartDelay(PANEL_ANIMATION_DELAY_MS);
+                animationList.add(fanBgAlphaAnimator);
+
+                ValueAnimator panelAlphaAnimator
+                        = ObjectAnimator.ofFloat(mContainer, View.ALPHA, 1, 0);
+                panelAlphaAnimator.setDuration(200);
+                panelAlphaAnimator.setStartDelay(300);
+
+                animationList.add(panelAlphaAnimator);
+
+                combineAnimationSet(animationList, listenerList,
+                        mDriverTemperature.getCollapseAnimations());
+                combineAnimationSet(animationList, listenerList,
+                        mPassengerTemperature.getCollapseAnimations());
+
+                combineAnimationSet(animationList, listenerList,
+                        mPanelTopRow.getCollapseAnimation(PANEL_ANIMATION_DELAY_MS,
+                                mTopPanelMaxAlpha));
+                combineAnimationSet(animationList, listenerList,
+                        mPanelBottomRow.getCollapseAnimation(PANEL_ANIMATION_DELAY_MS,
+                                mTopPanelMaxAlpha));
+                break;
+            case STATE_COLLAPSED_DIMMED:
+                // Hide the temperature numbers, open arrows and auto state button.
+                // TODO: determine if this section is still needed.
+                break;
+            case STATE_FULL_EXPANDED:
+                // Transition to expaneded state:
+                // 1. Expand the temperature bars.
+                // 2. Expand the top and bottom panel, staggered with a different delay.
+                // 3. Increase height of the hvac center panel, but maintain container height.
+                // 4. Fade in fan control background in a staggered manner.
+                fanBgAlphaAnimator
+                        = ObjectAnimator.ofFloat(mHvacFanControlBackground, View.ALPHA, 0, 1)
+                        .setDuration(PANEL_ANIMATION_TIME_MS);
+                fanBgAlphaAnimator.setStartDelay(PANEL_ANIMATION_DELAY_MS);
+                animationList.add(fanBgAlphaAnimator);
+
+                animationList.add(heightAnimator);
+                combineAnimationSet(animationList, listenerList,
+                        mDriverTemperature.getExpandAnimatons());
+                combineAnimationSet(animationList, listenerList,
+                        mPassengerTemperature.getExpandAnimatons());
+
+                // During expansion, the bottom panel animation should be delayed
+                combineAnimationSet(animationList, listenerList,
+                        mPanelTopRow.getExpandAnimation(PANEL_ANIMATION_DELAY_MS,
+                                mTopPanelMaxAlpha));
+                combineAnimationSet(animationList, listenerList,
+                        mPanelBottomRow.getExpandAnimation(PANEL_ANIMATION_LONG_DELAY_MS, 1f));
+                break;
+            default:
+        }
+
+        // If there are animations for the state change, play them all together and ensure
+        // the animation listeners are attached.
+        if (animationList.size() > 0) {
+            AnimatorSet animatorSet = new AnimatorSet();
+            animatorSet.playTogether(animationList);
+            for (Animator.AnimatorListener listener : listenerList) {
+                animatorSet.addListener(listener);
+            }
+            animatorSet.addListener(mAnimatorListener);
+            animatorSet.start();
+        }
+    }
+
+    private AnimatorSet.AnimatorListener mAnimatorListener = new AnimatorSet.AnimatorListener() {
+        @Override
+        public void onAnimationStart(Animator animation) {
+            mTransition.onTransitionStart();
+        }
+
+        @Override
+        public void onAnimationEnd(Animator animation) {
+            mTransition.onTransitionComplete();
+        }
+
+        @Override
+        public void onAnimationCancel(Animator animation) {}
+
+        @Override
+        public void onAnimationRepeat(Animator animation) {}
+    };
+
+    private ValueAnimator getPanelHeightAnimator(@HvacPanelState int startState,
+            @HvacPanelState int endState) {
+        int startHeight = getStateHeight(startState);
+        int endHeight = getStateHeight(endState);
+        if (startHeight == endHeight) {
+            return null;
+        }
+
+        ValueAnimator heightAnimator = new ValueAnimator().ofInt(startHeight, endHeight)
+                .setDuration(PANEL_ANIMATION_TIME_MS);
+        heightAnimator.addUpdateListener(mHeightUpdateListener);
+        return heightAnimator;
+    }
+
+    private int getStateHeight(@HvacPanelState int state) {
+        switch (state) {
+            case STATE_COLLAPSED:
+            case STATE_COLLAPSED_DIMMED:
+                return mPanelCollapsedHeight;
+            case STATE_FULL_EXPANDED:
+                return mPanelFullExpandedHeight;
+            default:
+                throw new IllegalArgumentException("No height mapped to HVAC State: " + state);
+        }
+    }
+
+    private void setAutoMode(boolean isOn) {
+        if (isOn) {
+            mPanelTopRow.setOnTouchListener(new View.OnTouchListener() {
+                @Override
+                public boolean onTouch(View v, MotionEvent event) {
+                    return true;
+                }
+            });
+            mAutoMode = true;
+            mPanelTopRow.disablePanel(true);
+            mTopPanelMaxAlpha = DISABLED_BUTTON_ALPHA;
+            mAutoButton.setImageDrawable(mAutoOnDrawable);
+        } else {
+            mPanelTopRow.disablePanel(false);
+            mTopPanelMaxAlpha = ENABLED_BUTTON_ALPHA;
+            mAutoButton.setImageDrawable(mAutoOffDrawable);
+        }
+        mHvacFanControlBackground.setAlpha(mTopPanelMaxAlpha);
+        mPanelTopRow.setAlpha(mTopPanelMaxAlpha);
+    }
+
+    private View.OnClickListener mPanelClickListener = new View.OnClickListener() {
+        @Override
+        public void onClick(View v) {
+            if (!mHvacIsOn) {
+                return;
+            }
+            switch (mCurrentState) {
+                case STATE_COLLAPSED:
+                    transitionState(STATE_COLLAPSED, STATE_FULL_EXPANDED);
+                    break;
+                case STATE_COLLAPSED_DIMMED:
+                    transitionState(mCurrentState, STATE_COLLAPSED);
+                    break;
+                default:
+            }
+        }
+    };
+
+    private View.OnClickListener mAutoButtonClickListener = new View.OnClickListener() {
+        @Override
+        public void onClick(View v) {
+            if (mAutoMode) {
+                mAutoMode = false;
+            } else {
+                mAutoMode = true;
+            }
+            mHvacController.setAutoMode(mAutoMode);
+            setAutoMode(mAutoMode);
+        }
+    };
+
+    private View.OnClickListener mOpenCloseToggleListener = new View.OnClickListener() {
+        @Override
+        public void onClick(View v) {
+            if (!mHvacIsOn) {
+                return;
+            }
+            if (mCurrentState == STATE_FULL_EXPANDED) {
+                transitionState(mCurrentState, STATE_COLLAPSED);
+            } else if (mCurrentState == STATE_COLLAPSED) {
+                transitionState(mCurrentState, STATE_FULL_EXPANDED);
+            }
+        }
+    };
+
+    private ValueAnimator.AnimatorUpdateListener mHeightUpdateListener
+            = new ValueAnimator.AnimatorUpdateListener() {
+        @Override
+        public void onAnimationUpdate(ValueAnimator animation) {
+            int height = (Integer) animation.getAnimatedValue();
+            int currentHeight = mPanel.getLayoutParams().height;
+            mPanel.getLayoutParams().height = height;
+            mPanel.setTop(mPanel.getTop() + height - currentHeight);
+            mPanel.requestLayout();
+        }
+    };
+
+    /**
+     * Handles the necessary setup/clean up before and after a state transition.
+     */
+    private class HvacPanelStateTransition {
+        private @HvacPanelState int mEndState;
+        private @HvacPanelState int mStartState;
+
+        public HvacPanelStateTransition(@HvacPanelState int startState,
+                @HvacPanelState int endState) {
+            mStartState = startState;
+            mEndState = endState;
+        }
+
+        public void onTransitionStart() {
+            mInAnimation = true;
+            if (mEndState == STATE_FULL_EXPANDED) {
+                mContainer.setAlpha(1);
+                mContainer.setVisibility(View.VISIBLE);
+            } else if (mStartState == STATE_FULL_EXPANDED) {
+                // Finished transitioning out of the fully expanded panel. Make the HVAC container
+                // and temperature bars not touchable so clicks on the screen bar are not intercepted.
+                disableTouchOnOverlay(mContainer);
+                disableTouchOnOverlay(mDriverTemperature);
+                disableTouchOnOverlay(mPassengerTemperature);
+            }
+        }
+
+        public void onTransitionComplete() {
+            if (mStartState == STATE_FULL_EXPANDED) {
+                mContainer.setVisibility(View.INVISIBLE);
+
+                enableTouchOnOverlay(mDriverTemperatureTouchOverlay);
+                enableTouchOnOverlay(mPassengerTemperatureTouchOverlay);
+            } else if (mEndState == STATE_FULL_EXPANDED) {
+                // Finished transitioning into the fully expanded HVAC panel, make the
+                // container and temperature bars touchable since it covers the screen.
+                enableTouchOnOverlay(mContainer);
+                enableTouchOnOverlay(mDriverTemperature);
+                enableTouchOnOverlay(mPassengerTemperature);
+
+                disableTouchOnOverlay(mDriverTemperatureTouchOverlay);
+                disableTouchOnOverlay(mPassengerTemperatureTouchOverlay);
+            }
+
+            // set new states
+            mCurrentState = mEndState;
+            mInAnimation = false;
+        }
+    }
+
+    private void disableTouchOnOverlay(View view) {
+        WindowManager.LayoutParams params
+                = (WindowManager.LayoutParams) view.getLayoutParams();
+        params.flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
+                | WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE
+                | WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS
+                & ~WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH;
+        mWindowManager.updateViewLayout(view, params);
+    }
+
+    private void enableTouchOnOverlay(View view) {
+        WindowManager.LayoutParams params
+                = (WindowManager.LayoutParams) view.getLayoutParams();
+        params.flags =
+                WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
+                        | WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS
+                        & ~WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH;
+        mWindowManager.updateViewLayout(view, params);
+    }
+
+}
diff --git a/src/com/android/car/hvac/controllers/SeatWarmerController.java b/src/com/android/car/hvac/controllers/SeatWarmerController.java
new file mode 100644 (file)
index 0000000..383f169
--- /dev/null
@@ -0,0 +1,81 @@
+/*
+ * 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.android.car.hvac.controllers;
+
+import com.android.car.hvac.HvacController;
+import com.android.car.hvac.ui.SeatWarmerButton;
+
+/**
+ * A controller to handle changes in the heated seat levels.
+ */
+public class SeatWarmerController {
+    private final SeatWarmerButton mPassengerSeatButton;
+    private final SeatWarmerButton mDriverSeatButton;
+
+    private final HvacController mHvacController;
+
+    public SeatWarmerController(SeatWarmerButton passengerSeatButton,
+            SeatWarmerButton driverSeatButton, HvacController hvacController) {
+        mDriverSeatButton = driverSeatButton;
+        mPassengerSeatButton = passengerSeatButton;
+
+        mHvacController = hvacController;
+        mHvacController.registerCallback(mCallback);
+
+        mPassengerSeatButton.setSeatWarmerClickListener(mPassengerSeatListener);
+        mDriverSeatButton.setSeatWarmerClickListener(mDriverSeatListener);
+    }
+
+    private final HvacController.Callback mCallback = new HvacController.Callback() {
+        @Override
+        public void onPassengerSeatWarmerChange(int level) {
+            // If the value of the heating is less than HEAT_OFF, it means the seats are
+            // being cooled, show heated seat button as off.
+            if (level < SeatWarmerButton.HEAT_OFF) {
+                mPassengerSeatButton.setHeatLevel(SeatWarmerButton.HEAT_OFF);
+            } else {
+                mPassengerSeatButton.setHeatLevel(level);
+            }
+        }
+
+        @Override
+        public void onDriverSeatWarmerChange(int level) {
+            // If the value of the heating is less than HEAT_OFF, it means the seats are
+            // being cooled, show heated seat button as off.
+            if (level < SeatWarmerButton.HEAT_OFF) {
+                mDriverSeatButton.setHeatLevel(SeatWarmerButton.HEAT_OFF);
+            } else {
+                mDriverSeatButton.setHeatLevel(level);
+            }
+        }
+    };
+
+    private final SeatWarmerButton.SeatWarmerButtonClickListener mPassengerSeatListener
+            = new SeatWarmerButton.SeatWarmerButtonClickListener() {
+        @Override
+        public void onSeatWarmerButtonClicked(@SeatWarmerButton.HeatingLevel int level) {
+            mHvacController.setPassengerSeatWarmerLevel(level);
+        }
+    };
+
+    private final SeatWarmerButton.SeatWarmerButtonClickListener mDriverSeatListener
+            = new SeatWarmerButton.SeatWarmerButtonClickListener() {
+        @Override
+        public void onSeatWarmerButtonClicked(@SeatWarmerButton.HeatingLevel int level) {
+            mHvacController.setDriverSeatWarmerLevel(level);
+        }
+    };
+}
diff --git a/src/com/android/car/hvac/controllers/TemperatureController.java b/src/com/android/car/hvac/controllers/TemperatureController.java
new file mode 100644 (file)
index 0000000..bb314d6
--- /dev/null
@@ -0,0 +1,70 @@
+/*
+ * 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.android.car.hvac.controllers;
+
+import com.android.car.hvac.HvacController;
+import com.android.car.hvac.ui.TemperatureBarOverlay;
+
+/**
+ * A controller that handles temperature updates for the driver and passenger.
+ */
+public class TemperatureController {
+    private final TemperatureBarOverlay mDriverTempBar;
+    private final TemperatureBarOverlay mPassengerTempBar;
+    private final HvacController mHvacController;
+
+    public TemperatureController(TemperatureBarOverlay passengerTemperatureBar,
+            TemperatureBarOverlay driverTemperatureBar, HvacController controller) {
+        mDriverTempBar = driverTemperatureBar;
+        mPassengerTempBar = passengerTemperatureBar;
+        mHvacController = controller;
+
+        mHvacController.registerCallback(mCallback);
+        mDriverTempBar.setTemperatureChangeListener(mDriverTempClickListener);
+        mPassengerTempBar.setTemperatureChangeListener(mPassengerTempClickListener);
+
+        mDriverTempBar.setTemperature(mHvacController.getDriverTemperature());
+        mPassengerTempBar.setTemperature(mHvacController.getPassengerTemperature());
+    }
+
+    private final HvacController.Callback mCallback = new HvacController.Callback() {
+        @Override
+        public void onPassengerTemperatureChange(float temp) {
+            mPassengerTempBar.setTemperature((int) temp);
+        }
+
+        @Override
+        public void onDriverTemperatureChange(float temp) {
+            mDriverTempBar.setTemperature((int) temp);
+        }
+    };
+
+    private final TemperatureBarOverlay.TemperatureAdjustClickListener mPassengerTempClickListener =
+            new TemperatureBarOverlay.TemperatureAdjustClickListener() {
+                @Override
+                public void onTemperatureChanged(int temperature) {
+                    mHvacController.setPassengerTemperature(temperature);
+                }
+            };
+
+    private final TemperatureBarOverlay.TemperatureAdjustClickListener mDriverTempClickListener =
+            new TemperatureBarOverlay.TemperatureAdjustClickListener() {
+                @Override
+                public void onTemperatureChanged(int temperature) {
+                    mHvacController.setDriverTemperature(temperature);
+                }
+            };
+}
diff --git a/src/com/android/car/hvac/ui/FanDirectionButtons.java b/src/com/android/car/hvac/ui/FanDirectionButtons.java
new file mode 100644 (file)
index 0000000..6863e61
--- /dev/null
@@ -0,0 +1,142 @@
+/*
+ * 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.android.car.hvac.ui;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.graphics.drawable.Drawable;
+import android.support.annotation.IntDef;
+import android.util.AttributeSet;
+import android.util.Pair;
+import android.view.View;
+import android.widget.ImageView;
+import android.widget.LinearLayout;
+import com.android.car.hvac.R;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * A set of buttons that controls the fan direction of the vehicle. Turning on one state will
+ * turn off the other states.
+ */
+public class FanDirectionButtons extends LinearLayout {
+    public static final int FAN_DIRECTION_FACE = 0;
+    public static final int FAN_DIRECTION_FACE_FLOOR = 1;
+    public static final int FAN_DIRECTION_FLOOR = 2;
+    public static final int FAN_DIRECTION_FLOOR_DEFROSTER = 3;
+
+    @IntDef({FAN_DIRECTION_FACE, FAN_DIRECTION_FACE_FLOOR,
+            FAN_DIRECTION_FLOOR, FAN_DIRECTION_FLOOR_DEFROSTER})
+    public @interface FanDirection {}
+
+    /**
+     * A listener that is notified when a fan direction button has been clicked.
+     */
+    public interface FanDirectionClickListener {
+        void onFanDirectionClicked(@FanDirection int direction);
+    }
+
+    private static final float UNSELECTED_BUTTON_ALPHA = 0.5f;
+    private static final float SELECTED_BUTTON_ALPHA = 1.0f;
+
+    private final Map<ImageView, Pair<Drawable, Drawable>> mFanMap = new HashMap<>();
+    private final Map<ImageView, Integer> mControlMap = new HashMap<>();
+
+    private FanDirectionClickListener mListener;
+
+    public FanDirectionButtons(Context context) {
+        super(context);
+        init();
+    }
+
+    public FanDirectionButtons(Context context, AttributeSet attrs) {
+        super(context, attrs);
+        init();
+    }
+
+    public FanDirectionButtons(Context context, AttributeSet attrs, int defStyleAttr) {
+        super(context, attrs, defStyleAttr);
+        init();
+    }
+
+    public void setFanDirectionClickListener(FanDirectionClickListener listener) {
+        mListener = listener;
+    }
+
+    private void init() {
+        inflate(getContext(), R.layout.fan_direction, this);
+    }
+
+    @Override
+    protected void onFinishInflate() {
+        super.onFinishInflate();
+        Resources res = getResources();
+
+        setOrientation(HORIZONTAL);
+
+        ImageView directionButton1 = (ImageView) findViewById(R.id.direction_1);
+        ImageView directionButton2 = (ImageView) findViewById(R.id.direction_2);
+        ImageView directionButton3 = (ImageView) findViewById(R.id.direction_3);
+        ImageView directionButton4 = (ImageView) findViewById(R.id.direction_4);
+
+        Drawable directionOn1 = res.getDrawable(R.drawable.ic_fan_direction_1_on);
+        Drawable directionOn2 = res.getDrawable(R.drawable.ic_fan_direction_2_on);
+        Drawable directionOn3 = res.getDrawable(R.drawable.ic_fan_direction_3_on);
+        Drawable directionOn4 = res.getDrawable(R.drawable.ic_fan_direction_4_on);
+
+        Drawable directionOff1 = res.getDrawable(R.drawable.ic_fan_direction_1_off);
+        Drawable directionOff2 = res.getDrawable(R.drawable.ic_fan_direction_2_off);
+        Drawable directionOff3 = res.getDrawable(R.drawable.ic_fan_direction_3_off);
+        Drawable directionOff4 = res.getDrawable(R.drawable.ic_fan_direction_4_off);
+
+        mFanMap.put(directionButton1, new Pair(directionOn1, directionOff1));
+        mFanMap.put(directionButton2, new Pair(directionOn2, directionOff2));
+        mFanMap.put(directionButton3, new Pair(directionOn3, directionOff3));
+        mFanMap.put(directionButton4, new Pair(directionOn4, directionOff4));
+
+        mControlMap.put(directionButton1, FAN_DIRECTION_FACE);
+        mControlMap.put(directionButton2, FAN_DIRECTION_FACE_FLOOR);
+        mControlMap.put(directionButton3, FAN_DIRECTION_FLOOR);
+        mControlMap.put(directionButton4, FAN_DIRECTION_FLOOR_DEFROSTER);
+
+        for (ImageView v : mFanMap.keySet()) {
+            v.setOnClickListener(mFanDirectionClickListener);
+        }
+    }
+
+    private final OnClickListener mFanDirectionClickListener = new OnClickListener() {
+        @Override
+        public void onClick(View v) {
+            resetFanToOff();
+            v.setAlpha(SELECTED_BUTTON_ALPHA);
+            if (mFanMap.containsKey(v)) {
+                ((ImageView) v).setImageDrawable(mFanMap.get(v).first);
+                v.setAlpha(SELECTED_BUTTON_ALPHA);
+                @FanDirection int direction = mControlMap.get(v);
+                mListener.onFanDirectionClicked(direction);
+            }
+        }
+    };
+
+    private void resetFanToOff() {
+        for (Map.Entry<ImageView, Pair<Drawable, Drawable>> entry : mFanMap.entrySet()) {
+            ImageView button = entry.getKey();
+            button.setImageDrawable(entry.getValue().second);
+            button.setAlpha(UNSELECTED_BUTTON_ALPHA);
+        }
+    }
+}
diff --git a/src/com/android/car/hvac/ui/FanSpeedBar.java b/src/com/android/car/hvac/ui/FanSpeedBar.java
new file mode 100644 (file)
index 0000000..f8936ec
--- /dev/null
@@ -0,0 +1,317 @@
+/*
+ * 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.android.car.hvac.ui;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.graphics.Color;
+import android.graphics.drawable.GradientDrawable;
+import android.util.AttributeSet;
+import android.view.View;
+import android.widget.RelativeLayout;
+import android.widget.TextView;
+import com.android.car.hvac.R;
+
+import java.util.ArrayList;
+
+/**
+ * Represents the fan speed bar. The bar is composed of a list of fan speed buttons. When a
+ * speed is selected, all lower levels will turn on and all higher levels will turn off. The
+ * currently selected speed can also be toggled on and off.
+ */
+public class FanSpeedBar extends RelativeLayout {
+    /**
+     * A listener that is notified when the buttons in the fan speed bar are clicked.
+     */
+    public interface FanSpeedButtonClickListener {
+        void onMaxButtonClicked();
+
+        void onOffButtonClicked();
+
+        void onFanSpeedSegmentClicked(int position);
+    }
+
+    private static final int BAR_SEGMENT_ANIMATION_DELAY_MS = 50;
+    private static final int BAR_SEGMENT_ANIMATION_MS = 100;
+    private static final int NUM_FAN_SPEED = 34;
+
+    private int mButtonEnabledTextColor;
+    private int mButtonDisabledTextColor;
+
+    private int mFanOffEnabledBgColor;
+    private int mFanMaxEnabledBgColor;
+
+    private float mCornerRadius;
+
+    private TextView mMaxButton;
+    private TextView mOffButton;
+
+    private FanSpeedBarSegment mFanSpeed1;
+    private FanSpeedBarSegment mFanSpeed2;
+    private FanSpeedBarSegment mFanSpeed3;
+    private FanSpeedBarSegment mFanSpeed4;
+
+    private FanSpeedButtonClickListener mListener;
+
+    private final ArrayList<FanSpeedBarSegment> mFanSpeedButtons = new ArrayList<>(NUM_FAN_SPEED);
+
+    public FanSpeedBar(Context context) {
+        super(context);
+        init();
+    }
+
+    public FanSpeedBar(Context context, AttributeSet attrs) {
+        super(context, attrs);
+        init();
+    }
+
+    public FanSpeedBar(Context context, AttributeSet attrs, int defStyleAttr) {
+        super(context, attrs, defStyleAttr);
+        init();
+    }
+
+    private void init() {
+        inflate(getContext(), R.layout.fan_speed, this);
+
+        Resources res = getContext().getResources();
+        // The fanspeed bar is set as height 72dp to match min tap target size. However it is
+        // inset by to make it appear thinner.
+        int barHeight = res.getDimensionPixelSize(R.dimen.hvac_fan_speed_bar_height);
+        int insetHeight = res.getDimensionPixelSize(R.dimen.hvac_fan_speed_bar_vertical_inset);
+        mCornerRadius = (barHeight - 2 * insetHeight) / 2;
+
+        mFanOffEnabledBgColor = res.getColor(R.color.hvac_fanspeed_off_enabled_bg);
+
+        mButtonEnabledTextColor = res.getColor(R.color.hvac_fanspeed_off_enabled_text_color);
+        mButtonDisabledTextColor = res.getColor(R.color.hvac_fanspeed_off_disabled_text_color);
+        mFanMaxEnabledBgColor = res.getColor(R.color.hvac_fanspeed_segment_color);
+    }
+
+    public void setFanspeedButtonClickListener(FanSpeedButtonClickListener clickListener) {
+        mListener = clickListener;
+    }
+
+    @Override
+    protected void onFinishInflate() {
+        super.onFinishInflate();
+
+        mFanSpeed1 = (FanSpeedBarSegment) findViewById(R.id.fan_speed_1);
+        mFanSpeed2 = (FanSpeedBarSegment) findViewById(R.id.fan_speed_2);
+        mFanSpeed3 = (FanSpeedBarSegment) findViewById(R.id.fan_speed_3);
+        mFanSpeed4 = (FanSpeedBarSegment) findViewById(R.id.fan_speed_4);
+
+        mFanSpeed1.setTag(R.id.TAG_FAN_SPEED_LEVEL, 1);
+        mFanSpeed2.setTag(R.id.TAG_FAN_SPEED_LEVEL, 2);
+        mFanSpeed3.setTag(R.id.TAG_FAN_SPEED_LEVEL, 3);
+        mFanSpeed4.setTag(R.id.TAG_FAN_SPEED_LEVEL, 4);
+
+        mFanSpeedButtons.add(mFanSpeed1);
+        mFanSpeedButtons.add(mFanSpeed2);
+        mFanSpeedButtons.add(mFanSpeed3);
+        mFanSpeedButtons.add(mFanSpeed4);
+
+        for (View view : mFanSpeedButtons) {
+            view.setOnClickListener(mFanSpeedBarClickListener);
+        }
+
+        mMaxButton = (TextView) findViewById(R.id.fan_max);
+        mMaxButton.setOnClickListener(new OnClickListener() {
+            @Override
+            public void onClick(View v) {
+                setMax();
+                if (mListener != null) {
+                  mListener.onMaxButtonClicked();
+                }
+            }
+        });
+
+        mOffButton = (TextView) findViewById(R.id.fan_off);
+        mOffButton.setOnClickListener(new OnClickListener() {
+            @Override
+            public void onClick(View v) {
+                setOff();
+                if (mListener != null) {
+                    mListener.onOffButtonClicked();
+                }
+            }
+        });
+
+        // Set the corner radius of the off/max button based on the height of the bar to get a
+        // semicircular border.
+        GradientDrawable offButtonBg = new GradientDrawable();
+        offButtonBg.setCornerRadii(new float[]{mCornerRadius, mCornerRadius, 0, 0,
+                0, 0, mCornerRadius, mCornerRadius});
+        mOffButton.setBackground(offButtonBg);
+        mOffButton.setTextColor(mButtonDisabledTextColor);
+
+        GradientDrawable maxButtonBg = new GradientDrawable();
+        maxButtonBg.setCornerRadii(new float[]{0, 0, mCornerRadius, mCornerRadius,
+                mCornerRadius, mCornerRadius, 0, 0});
+        mMaxButton.setBackground(maxButtonBg);
+        mMaxButton.setTextColor(mButtonDisabledTextColor);
+    }
+
+    public void setMax() {
+        int numFanSpeed = mFanSpeedButtons.size();
+        int delay = 0;
+
+        for (int i = 0; i < numFanSpeed; i++) {
+            if (!mFanSpeedButtons.get(i).isTurnedOn()) {
+                mFanSpeedButtons.get(i).playTurnOnAnimation(BAR_SEGMENT_ANIMATION_MS, delay);
+                delay += BAR_SEGMENT_ANIMATION_DELAY_MS;
+            }
+        }
+        setOffButtonEnabled(false);
+        setMaxButtonEnabled(true);
+    }
+
+    private void setMaxButtonEnabled(boolean enabled) {
+        GradientDrawable background = (GradientDrawable) mMaxButton.getBackground();
+        if (enabled) {
+            background.setColor(mFanMaxEnabledBgColor);
+            mMaxButton.setTextColor(mButtonEnabledTextColor);
+        } else {
+            background.setColor(Color.TRANSPARENT);
+            mMaxButton.setTextColor(mButtonDisabledTextColor);
+        }
+    }
+
+
+    private void setOffButtonEnabled(boolean enabled) {
+        GradientDrawable background = (GradientDrawable) mOffButton.getBackground();
+        if (enabled) {
+            background.setColor(mFanOffEnabledBgColor);
+            mOffButton.setTextColor(mButtonEnabledTextColor);
+        } else {
+            background.setColor(Color.TRANSPARENT);
+            mOffButton.setTextColor(mButtonDisabledTextColor);
+        }
+    }
+
+    public void setOff() {
+        setOffButtonEnabled(true);
+        setMaxButtonEnabled(false);
+
+        int numFanSpeed = mFanSpeedButtons.size();
+        int delay = 0;
+        for (int i = numFanSpeed - 1; i >= 0; i--) {
+            if (mFanSpeedButtons.get(i).isTurnedOn()) {
+                mFanSpeedButtons.get(i).playTurnOffAnimation(BAR_SEGMENT_ANIMATION_MS, delay);
+                delay += BAR_SEGMENT_ANIMATION_DELAY_MS;
+            }
+        }
+    }
+
+    /**
+     * Sets the fan speed segments to on off based on the position. Note the changes do not animate,
+     * if animation is required use {@link FanSpeedBar#animateToSpeedSegment(int)}
+     */
+    public void setSpeedSegment(int position) {
+        for (int i = 0; i < mFanSpeedButtons.size(); i++) {
+            // For segments lower than the position, they should be turned on.
+            mFanSpeedButtons.get(i).setTurnedOn(i < position ? true : false);
+        }
+    }
+
+    /**
+     * Animates the fan speed bar to a specific position. Turning on all positions before it
+     * and turning off all positions after it.
+     */
+    public void animateToSpeedSegment(int position) {
+        setOffButtonEnabled(false);
+        setMaxButtonEnabled(false);
+
+        int fanSpeedCount = mFanSpeedButtons.size();
+        int fanSpeedIndex = position - 1;
+
+        if (fanSpeedIndex < 0) {
+            fanSpeedIndex = 0;
+        } else if (fanSpeedIndex > fanSpeedCount) {
+            fanSpeedIndex = fanSpeedCount - 1;
+        }
+
+        int delay = 0;
+        if (mFanSpeedButtons.get(fanSpeedIndex).isTurnedOn()) {
+            // If selected position is already turned on, then make sure each segment
+            // after is turned off.
+            for (int i = fanSpeedCount - 1; i > fanSpeedIndex; i--) {
+                if (mFanSpeedButtons.get(i).isTurnedOn()) {
+                    mFanSpeedButtons.get(i).playTurnOffAnimation(BAR_SEGMENT_ANIMATION_MS, delay);
+                    delay += BAR_SEGMENT_ANIMATION_DELAY_MS;
+                }
+            }
+        } else {
+            // If the selected position is turned off, turn on all positions before it and itself on.
+            for (int i = 0; i <= fanSpeedIndex; i++) {
+                if (!mFanSpeedButtons.get(i).isTurnedOn()) {
+                    mFanSpeedButtons.get(i).playTurnOnAnimation(BAR_SEGMENT_ANIMATION_MS, delay);
+                    delay += BAR_SEGMENT_ANIMATION_DELAY_MS;
+                }
+            }
+        }
+    }
+
+    private final OnClickListener mFanSpeedBarClickListener = new OnClickListener() {
+        @Override
+        public void onClick(View v) {
+            int level = (int) v.getTag(R.id.TAG_FAN_SPEED_LEVEL);
+
+            setOffButtonEnabled(false);
+            setMaxButtonEnabled(false);
+
+            int fanSpeedCount = mFanSpeedButtons.size();
+            int fanSpeedIndex = level - 1;
+
+            // If selected speed is the last segment in the bar, turn it off it's currently on.
+            if (fanSpeedIndex == fanSpeedCount - 1
+                    && mFanSpeedButtons.get(fanSpeedIndex).isTurnedOn()) {
+                mFanSpeedButtons.get(fanSpeedIndex)
+                        .playTurnOffAnimation(BAR_SEGMENT_ANIMATION_MS, 0);
+                return;
+            }
+
+            // If the selected speed is on, and the next fan speed is not on. Then turn off
+            // the selected speed.
+            if (fanSpeedIndex < fanSpeedCount - 1
+                    && mFanSpeedButtons.get(fanSpeedIndex).isTurnedOn()
+                    && !mFanSpeedButtons.get(fanSpeedIndex + 1).isTurnedOn()) {
+                mFanSpeedButtons.get(fanSpeedIndex)
+                        .playTurnOffAnimation(BAR_SEGMENT_ANIMATION_MS, 0);
+                return;
+            }
+
+            int delay = 0;
+            for (int i = 0; i < level; i++) {
+                if (!mFanSpeedButtons.get(i).isTurnedOn()) {
+                    mFanSpeedButtons.get(i).playTurnOnAnimation(BAR_SEGMENT_ANIMATION_MS, delay);
+                    delay += BAR_SEGMENT_ANIMATION_DELAY_MS;
+                }
+            }
+
+            delay = 0;
+            for (int i = fanSpeedCount - 1; i >= level; i--) {
+                if (mFanSpeedButtons.get(i).isTurnedOn()) {
+                    mFanSpeedButtons.get(i).playTurnOffAnimation(BAR_SEGMENT_ANIMATION_MS, delay);
+                    delay += BAR_SEGMENT_ANIMATION_DELAY_MS;
+                }
+            }
+
+            if (mListener != null) {
+                mListener.onFanSpeedSegmentClicked(level);
+            }
+        }
+    };
+}
diff --git a/src/com/android/car/hvac/ui/FanSpeedBarSegment.java b/src/com/android/car/hvac/ui/FanSpeedBarSegment.java
new file mode 100644 (file)
index 0000000..0f67b78
--- /dev/null
@@ -0,0 +1,107 @@
+/*
+ * 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.android.car.hvac.ui;
+
+import android.animation.ValueAnimator;
+import android.content.Context;
+import android.content.res.Resources;
+import android.graphics.drawable.GradientDrawable;
+import android.util.AttributeSet;
+import android.widget.ImageView;
+import com.android.car.hvac.R;
+
+/**
+ * Represents a single bar in the fan speed bar.
+ */
+public class FanSpeedBarSegment extends ImageView {
+    private boolean mTurnedOn;
+
+    private final int mDotExpandedSize;
+    private final int mDotSize;
+
+    private final ValueAnimator mDotWidthExpandAnimator;
+
+    private final ValueAnimator.AnimatorUpdateListener mExpandListener
+            = new ValueAnimator.AnimatorUpdateListener() {
+        @Override
+        public void onAnimationUpdate(ValueAnimator animation) {
+            int size = (int) animation.getAnimatedValue();
+            GradientDrawable drawable = (GradientDrawable) getDrawable();
+            drawable.setCornerRadius(size / 2);
+            drawable.setSize(size, size);
+        }
+    };
+
+    public FanSpeedBarSegment(Context context) {
+        super(context);
+    }
+
+    public FanSpeedBarSegment(Context context, AttributeSet attrs) {
+        super(context, attrs);
+    }
+
+    public FanSpeedBarSegment(Context context, AttributeSet attrs, int defStyleAttr) {
+        super(context, attrs, defStyleAttr);
+    }
+
+    {
+        setScaleType(ScaleType.CENTER);
+
+        Resources res = getResources();
+        mDotExpandedSize = res.getDimensionPixelSize(R.dimen.hvac_fan_speed_dot_expanded_size);
+        mDotSize = res.getDimensionPixelSize(R.dimen.hvac_fan_speed_dot_size);
+        mDotWidthExpandAnimator = ValueAnimator.ofInt(mDotSize, mDotExpandedSize);
+        mDotWidthExpandAnimator.addUpdateListener(mExpandListener);
+
+        GradientDrawable dot = new GradientDrawable();
+        dot.setColor(res.getColor(R.color.hvac_fanspeed_segment_color));
+        dot.setSize(mDotSize, mDotSize);
+        dot.setCornerRadius(mDotSize / 2);
+        setImageDrawable(dot);
+    }
+
+    public void playTurnOnAnimation(int duration, int delayMs) {
+        mDotWidthExpandAnimator.setStartDelay(delayMs);
+        mDotWidthExpandAnimator.setDuration(duration);
+        mDotWidthExpandAnimator.start();
+        mTurnedOn = true;
+    }
+
+    public void playTurnOffAnimation(int duration, int delayMs) {
+        mDotWidthExpandAnimator.setStartDelay(delayMs);
+        mDotWidthExpandAnimator.setDuration(duration);
+        mDotWidthExpandAnimator.reverse();
+        mTurnedOn = false;
+    }
+
+    public void setTurnedOn(boolean isOn) {
+        mTurnedOn = isOn;
+        GradientDrawable drawable = (GradientDrawable) getDrawable();
+        if (mTurnedOn) {
+            drawable.setCornerRadius(0);
+            // Setting size -1, makes the drawable grow to the size of the image view.
+            drawable.setSize(-1, -1);
+        } else {
+            drawable.setCornerRadius(mDotSize / 2);
+            drawable.setSize(mDotSize, mDotSize);
+        }
+        setImageDrawable(drawable);
+    }
+
+    public boolean isTurnedOn() {
+        return mTurnedOn;
+    }
+}
\ No newline at end of file
diff --git a/src/com/android/car/hvac/ui/HvacPanelRow.java b/src/com/android/car/hvac/ui/HvacPanelRow.java
new file mode 100644 (file)
index 0000000..92ada19
--- /dev/null
@@ -0,0 +1,117 @@
+/*
+ * 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.android.car.hvac.ui;
+
+import android.animation.AnimatorSet;
+import android.animation.ValueAnimator;
+import android.content.Context;
+import android.content.res.Resources;
+import android.util.AttributeSet;
+import android.view.MotionEvent;
+import android.widget.LinearLayout;
+
+import com.android.car.hvac.R;
+
+/**
+ * A row in the center HVAC panel.
+ */
+public class HvacPanelRow extends LinearLayout {
+    private static final int EXPAND_ANIMATION_TIME_MS = 300;
+    private static final int EXPAND_ANIMATION_ALPHA_TIME_MS = 100;
+
+    private final int mAnimationHeightShift;
+
+    private boolean mDisableClickOnChildren;
+
+    public HvacPanelRow(Context context) {
+        super(context);
+    }
+
+    public HvacPanelRow(Context context, AttributeSet attrs) {
+        super(context, attrs);
+    }
+
+    public HvacPanelRow(Context context, AttributeSet attrs, int defStyleAttr) {
+        super(context, attrs, defStyleAttr);
+    }
+
+    {
+        Resources res = getResources();
+        mAnimationHeightShift
+                = res.getDimensionPixelSize(R.dimen.hvac_panel_row_animation_height_shift);
+    }
+
+    public AnimatorSet getExpandAnimation(long delayMs, float endAlpha) {
+        return getTransitionAnimation(delayMs, endAlpha, true /* expanding */);
+    }
+
+    public AnimatorSet getCollapseAnimation(long delayMs, float startAlpha) {
+        return getTransitionAnimation(delayMs, startAlpha, false /* expanding */);
+    }
+
+    /**
+     * Disable/enable touch events on all child views of this panel
+     */
+    public void disablePanel(boolean disable) {
+        mDisableClickOnChildren = disable;
+    }
+
+    @Override
+    public boolean onInterceptTouchEvent(MotionEvent ev) {
+        return mDisableClickOnChildren;
+    }
+
+    private AnimatorSet getTransitionAnimation(long delayMs, float maxAlpha, boolean expanding) {
+        ValueAnimator alphaAnimator;
+        ValueAnimator translationYAnimator;
+
+        if (expanding) {
+            alphaAnimator = ValueAnimator.ofFloat(0, maxAlpha);
+            translationYAnimator = ValueAnimator.ofFloat(mAnimationHeightShift, 0);
+        } else {
+            alphaAnimator = ValueAnimator.ofFloat(maxAlpha, 0);
+            translationYAnimator = ValueAnimator.ofFloat(0, mAnimationHeightShift);
+        }
+
+        alphaAnimator.setStartDelay(delayMs);
+        alphaAnimator.setDuration(EXPAND_ANIMATION_ALPHA_TIME_MS);
+        alphaAnimator.addUpdateListener(mAlphaUpdateListener);
+        translationYAnimator.addUpdateListener(mTranslationYUpdateListener);
+        translationYAnimator.setDuration(EXPAND_ANIMATION_TIME_MS);
+
+        AnimatorSet set = new AnimatorSet();
+        set.playTogether(alphaAnimator, translationYAnimator);
+        return set;
+    }
+
+    private final ValueAnimator.AnimatorUpdateListener mTranslationYUpdateListener
+            = new ValueAnimator.AnimatorUpdateListener() {
+        @Override
+        public void onAnimationUpdate(ValueAnimator animation) {
+            float value = (float) animation.getAnimatedValue();
+            setTranslationY(value);
+        }
+    };
+
+    private final ValueAnimator.AnimatorUpdateListener mAlphaUpdateListener
+            = new ValueAnimator.AnimatorUpdateListener() {
+        @Override
+        public void onAnimationUpdate(ValueAnimator animation) {
+            float alpha = (float) animation.getAnimatedValue();
+            setAlpha(alpha);
+        }
+    };
+}
diff --git a/src/com/android/car/hvac/ui/PressAndHoldTouchListener.java b/src/com/android/car/hvac/ui/PressAndHoldTouchListener.java
new file mode 100644 (file)
index 0000000..da85c0a
--- /dev/null
@@ -0,0 +1,68 @@
+/*
+ * 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.android.car.hvac.ui;
+
+import android.os.Handler;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.View.OnClickListener;
+import android.view.View.OnTouchListener;
+
+/**
+ * A wrapper for a click listener that repeats the click when a press and hold action takes place.
+ */
+public class PressAndHoldTouchListener implements OnTouchListener {
+    private static final int REPEAT_ACTION_DELAY_MS = 100;
+    private static final int FIRST_ACTION_DELAY_MS = 300;
+
+    private Handler mHandler = new Handler();
+
+    private final OnClickListener clickListener;
+
+    private View mView;
+
+    public PressAndHoldTouchListener(OnClickListener clickListener) {
+        this.clickListener = clickListener;
+    }
+
+    public boolean onTouch(View view, MotionEvent motionEvent) {
+        switch (motionEvent.getAction()) {
+            case MotionEvent.ACTION_DOWN:
+                mHandler.removeCallbacks(mRepeatedAction);
+                mHandler.postDelayed(mRepeatedAction, FIRST_ACTION_DELAY_MS);
+                mView = view;
+                mView.setPressed(true);
+                clickListener.onClick(view);
+                return true;
+            case MotionEvent.ACTION_CANCEL:
+            case MotionEvent.ACTION_UP:
+                mHandler.removeCallbacks(mRepeatedAction);
+                mView.setPressed(false);
+                mView = null;
+                return true;
+            default:
+                return false;
+        }
+    }
+
+    private Runnable mRepeatedAction = new Runnable() {
+        @Override
+        public void run() {
+            mHandler.postDelayed(this, REPEAT_ACTION_DELAY_MS);
+            clickListener.onClick(mView);  //
+        }
+    };
+}
\ No newline at end of file
diff --git a/src/com/android/car/hvac/ui/SeatWarmerButton.java b/src/com/android/car/hvac/ui/SeatWarmerButton.java
new file mode 100644 (file)
index 0000000..344e919
--- /dev/null
@@ -0,0 +1,121 @@
+/*
+ * 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.android.car.hvac.ui;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.graphics.drawable.Drawable;
+import android.support.annotation.IntDef;
+import android.util.AttributeSet;
+import android.view.View;
+import android.widget.ImageView;
+import com.android.car.hvac.R;
+
+/**
+ * A seat warmer button that has 3 heating levels.
+ */
+public class SeatWarmerButton extends ImageView {
+    public static final int HEAT_OFF = 0;
+    public static final int HEAT_LEVEL_ONE = 1;
+    public static final int HEAT_LEVEL_TWO = 2;
+    public static final int HEAT_LEVEL_THREE = 3;
+
+    public interface SeatWarmerButtonClickListener{
+        void onSeatWarmerButtonClicked(@HeatingLevel int level);
+    }
+
+    @IntDef({HEAT_OFF, HEAT_LEVEL_ONE, HEAT_LEVEL_TWO, HEAT_LEVEL_THREE})
+    public @interface HeatingLevel {}
+
+    private @HeatingLevel int mCurrentHeatSetting;
+
+    private final Drawable mStateDrawables[] = new Drawable[4];
+
+    private SeatWarmerButtonClickListener mListener;
+
+    public SeatWarmerButton(Context context) {
+        super(context);
+        init();
+    }
+
+    public SeatWarmerButton(Context context, AttributeSet attrs) {
+        super(context, attrs);
+        init();
+    }
+
+    public SeatWarmerButton(Context context, AttributeSet attrs, int defStyleAttr) {
+        super(context, attrs, defStyleAttr);
+        init();
+    }
+
+    private void init() {
+        Resources res = getResources();
+        mStateDrawables[0] = res.getDrawable(R.drawable.ic_seat_heat_off);
+        mStateDrawables[1] = res.getDrawable(R.drawable.ic_seat_heat_level_1);
+        mStateDrawables[2] = res.getDrawable(R.drawable.ic_seat_heat_level_2);
+        mStateDrawables[3] = res.getDrawable(R.drawable.ic_seat_heat_level_3);
+
+        setOnClickListener(new OnClickListener() {
+            @Override
+            public void onClick(View v) {
+                mCurrentHeatSetting = getNextHeatSetting(mCurrentHeatSetting);
+                Drawable d = getDrawableForState(mCurrentHeatSetting);
+                setImageDrawable(d);
+                if (mListener != null) {
+                    mListener.onSeatWarmerButtonClicked(mCurrentHeatSetting);
+                }
+            }
+        });
+    }
+
+    public void setSeatWarmerClickListener(SeatWarmerButtonClickListener listener) {
+        mListener = listener;
+    }
+
+    public void setHeatLevel(@HeatingLevel int level) {
+        mCurrentHeatSetting = level;
+        setImageDrawable(getDrawableForState(mCurrentHeatSetting));
+    }
+
+    private Drawable getDrawableForState(@HeatingLevel int level) {
+        switch (level) {
+            case HEAT_LEVEL_ONE:
+                return mStateDrawables[1];
+            case HEAT_LEVEL_TWO:
+                return mStateDrawables[2];
+            case HEAT_LEVEL_THREE:
+                return mStateDrawables[3];
+            case HEAT_OFF:
+            default:
+                return mStateDrawables[0];
+        }
+    }
+
+    private @HeatingLevel int getNextHeatSetting(@HeatingLevel int level) {
+        switch (level) {
+            case HEAT_OFF:
+                return HEAT_LEVEL_ONE;
+            case HEAT_LEVEL_ONE:
+                return HEAT_LEVEL_TWO;
+            case HEAT_LEVEL_TWO:
+                return HEAT_LEVEL_THREE;
+            case HEAT_LEVEL_THREE:
+                return HEAT_OFF;
+            default:
+                return HEAT_OFF;
+        }
+    }
+}
diff --git a/src/com/android/car/hvac/ui/TemperatureBarOverlay.java b/src/com/android/car/hvac/ui/TemperatureBarOverlay.java
new file mode 100644 (file)
index 0000000..892ad6f
--- /dev/null
@@ -0,0 +1,437 @@
+/*
+ * 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.android.car.hvac.ui;
+
+import android.animation.Animator;
+import android.animation.AnimatorSet;
+import android.animation.ObjectAnimator;
+import android.animation.ValueAnimator;
+import android.content.Context;
+import android.content.res.Resources;
+import android.graphics.drawable.GradientDrawable;
+import android.util.AttributeSet;
+import android.view.View;
+import android.widget.FrameLayout;
+import android.widget.ImageView;
+import android.widget.TextView;
+import com.android.car.hvac.R;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * An expandable temperature control bar. Note this UI is meant to only support Fahrenheit.
+ */
+public class TemperatureBarOverlay extends FrameLayout {
+    /**
+     * A listener that observes clicks on the temperature bar.
+     */
+    public interface TemperatureAdjustClickListener {
+        void onTemperatureChanged(int temperature);
+    }
+
+    private static final int EXPAND_ANIMATION_TIME_MS = 500;
+    private static final int COLLAPSE_ANIMATION_TIME_MS = 200;
+
+    private static final int TEXT_ALPHA_ANIMATION_TIME_DELAY_MS = 400;
+    private static final int TEXT_ALPHA_FADE_OUT_ANIMATION_TIME_MS = 100;
+    private static final int TEXT_ALPHA_FADE_IN_ANIMATION_TIME_MS = 300;
+
+    private static final int COLOR_CHANGE_ANIMATION_TIME_MS = 200;
+
+    private static final float BUTTON_ALPHA_COLLAPSED = 0f;
+    private static final float BUTTON_ALPHA_EXPANDED = 1.0f;
+
+    private static final int DEFAULT_TEMPERATURE = 32;
+    private static final int MAX_TEMPERATURE = 85;
+    private static final int MIN_TEMPERATURE = 60;
+
+    private String mInvalidTemperature;
+
+    private int mTempColor1;
+    private int mTempColor2;
+    private int mTempColor3;
+    private int mTempColor4;
+    private int mTempColor5;
+
+    private int mOffColor;
+
+    private ImageView mIncreaseButton;
+    private ImageView mDecreaseButton;
+    private TextView mText;
+    private TextView mFloatingText;
+    private TextView mOffText;
+    private View mTemperatureBar;
+    private View mCloseButton;
+
+    private int mTemperature = DEFAULT_TEMPERATURE;
+
+    private int mCollapsedWidth;
+    private int mExpandedWidth;
+    private int mCollapsedHeight;
+    private int mExpandedHeight;
+    private int mCollapsedYShift;
+    private int mExpandedYShift;
+
+    private boolean mIsOpen;
+    private boolean mIsOn = true;
+
+    private TemperatureAdjustClickListener mListener;
+
+    public TemperatureBarOverlay(Context context) {
+        super(context);
+    }
+
+    public TemperatureBarOverlay(Context context, AttributeSet attrs) {
+        super(context, attrs);
+    }
+
+    public TemperatureBarOverlay(Context context, AttributeSet attrs, int defStyleAttr) {
+        super(context, attrs, defStyleAttr);
+    }
+
+    @Override
+    protected void onFinishInflate() {
+        super.onFinishInflate();
+
+        Resources res = getResources();
+
+        mCollapsedHeight = res.getDimensionPixelSize(R.dimen.temperature_bar_collapsed_height);
+        mExpandedHeight = res.getDimensionPixelSize(R.dimen.temperature_bar_expanded_height);
+        // Push the collapsed circle all the way down to the bottom of the screen and leave
+        // half of it visible.
+        mCollapsedYShift
+                = res.getDimensionPixelSize(R.dimen.car_hvac_panel_full_expanded_height)
+                - (mCollapsedHeight / 2);
+        mExpandedYShift = res.getDimensionPixelSize(R.dimen.hvac_panel_row_margin);
+
+        mCollapsedWidth = res.getDimensionPixelSize(R.dimen.temperature_bar_width_collapsed);
+        mExpandedWidth = res.getDimensionPixelSize(R.dimen.temperature_bar_width_expanded);
+
+        mInvalidTemperature = getContext().getString(R.string.hvac_invalid_temperature);
+
+        mTempColor1 = res.getColor(R.color.temperature_1);
+        mTempColor2 = res.getColor(R.color.temperature_2);
+        mTempColor3 = res.getColor(R.color.temperature_3);
+        mTempColor4 = res.getColor(R.color.temperature_4);
+        mTempColor5 = res.getColor(R.color.temperature_5);
+
+        mOffColor = res.getColor(R.color.hvac_temperature_off_text_bg_color);
+
+        mIncreaseButton = (ImageView) findViewById(R.id.increase_button);
+        mDecreaseButton = (ImageView) findViewById(R.id.decrease_button);
+
+        mFloatingText = (TextView) findViewById(R.id.floating_temperature_text);
+        mText = (TextView) findViewById(R.id.temperature_text);
+        mOffText = (TextView) findViewById(R.id.temperature_off_text);
+
+        mTemperatureBar = findViewById(R.id.temperature_bar);
+        mTemperatureBar.setTranslationY(mCollapsedYShift);
+
+        mCloseButton = findViewById(R.id.close_button);
+
+        mText.setText(getContext().getString(R.string.hvac_temperature_template,
+                mInvalidTemperature));
+        mFloatingText.setText(getContext()
+                .getString(R.string.hvac_temperature_template,
+                        mInvalidTemperature));
+
+        mIncreaseButton.setOnTouchListener(new PressAndHoldTouchListener(temperatureClickListener));
+        mDecreaseButton.setOnTouchListener(new PressAndHoldTouchListener(temperatureClickListener));
+
+        if (!mIsOpen) {
+            mIncreaseButton.setAlpha(BUTTON_ALPHA_COLLAPSED);
+            mDecreaseButton.setAlpha(BUTTON_ALPHA_COLLAPSED);
+            mText.setAlpha(BUTTON_ALPHA_COLLAPSED);
+
+            mDecreaseButton.setVisibility(GONE);
+            mIncreaseButton.setVisibility(GONE);
+            mText.setVisibility(GONE);
+        }
+    }
+
+    public void setTemperatureChangeListener(TemperatureAdjustClickListener listener) {
+        mListener =  listener;
+    }
+
+    public void setBarOnClickListener(OnClickListener l) {
+        mFloatingText.setOnClickListener(l);
+        mTemperatureBar.setOnClickListener(l);
+    }
+
+    public void setCloseButtonOnClickListener(OnClickListener l) {
+        mCloseButton.setOnClickListener(l);
+    }
+
+    public AnimatorSet getExpandAnimatons() {
+        List<Animator> list = new ArrayList();
+        AnimatorSet animation = new AnimatorSet();
+        if (mIsOpen) {
+            return animation;
+        }
+
+        list.add(getAlphaAnimator(mIncreaseButton, false /* fade */, EXPAND_ANIMATION_TIME_MS));
+        list.add(getAlphaAnimator(mDecreaseButton, false /* fade */, EXPAND_ANIMATION_TIME_MS));
+        list.add(getAlphaAnimator(mText, false /* fade */, EXPAND_ANIMATION_TIME_MS));
+        list.add(getAlphaAnimator(mFloatingText, true /* fade */,
+                TEXT_ALPHA_FADE_OUT_ANIMATION_TIME_MS));
+
+        ValueAnimator widthAnimator = new ValueAnimator().ofInt(mCollapsedWidth, mExpandedWidth)
+                .setDuration(EXPAND_ANIMATION_TIME_MS);
+        widthAnimator.addUpdateListener(mWidthUpdateListener);
+        list.add(widthAnimator);
+
+        ValueAnimator heightAnimator = new ValueAnimator().ofInt(mCollapsedHeight,
+                mExpandedHeight)
+                .setDuration(EXPAND_ANIMATION_TIME_MS);
+        heightAnimator.addUpdateListener(mHeightUpdateListener);
+        list.add(heightAnimator);
+
+
+        ValueAnimator translationYAnimator
+                = new ValueAnimator().ofFloat(mCollapsedYShift, mExpandedYShift);
+        translationYAnimator.addUpdateListener(mTranslationYListener);
+        list.add(translationYAnimator);
+
+        animation.playTogether(list);
+        animation.addListener(mStateListener);
+
+        return animation;
+    }
+
+    public AnimatorSet getCollapseAnimations() {
+
+        List<Animator> list = new ArrayList();
+        AnimatorSet animation = new AnimatorSet();
+
+        if (!mIsOpen) {
+            return animation;
+        }
+        list.add(getAlphaAnimator(mIncreaseButton, true /* fade */, COLLAPSE_ANIMATION_TIME_MS));
+        list.add(getAlphaAnimator(mDecreaseButton, true /* fade */, COLLAPSE_ANIMATION_TIME_MS));
+        list.add(getAlphaAnimator(mText, true /* fade */, COLLAPSE_ANIMATION_TIME_MS));
+
+        ObjectAnimator floatingTextAnimator = getAlphaAnimator(mFloatingText,
+                false /* fade */, TEXT_ALPHA_FADE_IN_ANIMATION_TIME_MS);
+        floatingTextAnimator.setStartDelay(TEXT_ALPHA_ANIMATION_TIME_DELAY_MS);
+
+        list.add(floatingTextAnimator);
+
+        ValueAnimator widthAnimator = new ValueAnimator().ofInt(mExpandedWidth, mCollapsedWidth)
+                .setDuration(COLLAPSE_ANIMATION_TIME_MS);
+        widthAnimator.addUpdateListener(mWidthUpdateListener);
+        list.add(widthAnimator);
+
+        ValueAnimator heightAnimator = new ValueAnimator().ofInt(mExpandedHeight, mCollapsedHeight)
+                .setDuration(COLLAPSE_ANIMATION_TIME_MS);
+        heightAnimator.addUpdateListener(mHeightUpdateListener);
+        list.add(heightAnimator);
+
+        ValueAnimator translationYAnimator
+                = new ValueAnimator().ofFloat(mExpandedYShift, mCollapsedYShift);
+        translationYAnimator.addUpdateListener(mTranslationYListener);
+        list.add(translationYAnimator);
+
+        animation.playTogether(list);
+        animation.addListener(mStateListener);
+
+        return animation;
+    }
+
+    private ValueAnimator.AnimatorListener mStateListener = new ValueAnimator.AnimatorListener() {
+        @Override
+        public void onAnimationStart(Animator animation) {
+            if (!mIsOpen) {
+                mDecreaseButton.setVisibility(VISIBLE);
+                mIncreaseButton.setVisibility(VISIBLE);
+                mText.setVisibility(VISIBLE);
+                mCloseButton.setVisibility(VISIBLE);
+            } else {
+                mCloseButton.setVisibility(GONE);
+            }
+        }
+
+        @Override
+        public void onAnimationEnd(Animator animation) {
+            if (mIsOpen) {
+                //Finished closing, make sure the buttons are now gone,
+                //so they are no longer touchable
+                mDecreaseButton.setVisibility(GONE);
+                mIncreaseButton.setVisibility(GONE);
+                mText.setVisibility(GONE);
+                mIsOpen = false;
+            } else {
+                //Finished opening
+                mIsOpen = true;
+            }
+        }
+
+        @Override
+        public void onAnimationCancel(Animator animation) {
+        }
+
+        @Override
+        public void onAnimationRepeat(Animator animation) {
+        }
+    };
+
+    private void changeTemperatureColor(int startColor, int endColor) {
+        if (endColor != startColor) {
+            ValueAnimator animator = ValueAnimator.ofArgb(startColor, endColor);
+            animator.addUpdateListener(mTemperatureColorListener);
+            animator.setDuration(COLOR_CHANGE_ANIMATION_TIME_MS);
+            animator.start();
+        } else {
+            ((GradientDrawable) mTemperatureBar.getBackground()).setColor(endColor);
+        }
+    }
+
+    private final View.OnClickListener temperatureClickListener = new View.OnClickListener() {
+        @Override
+        public void onClick(View v) {
+            int startColor = getTemperatureColor(mTemperature);
+
+            if (v == mIncreaseButton && mTemperature < MAX_TEMPERATURE) {
+                mTemperature++;
+            } else if (v == mDecreaseButton && mTemperature > MIN_TEMPERATURE) {
+                mTemperature--;
+            }
+            int endColor = getTemperatureColor(mTemperature);
+            changeTemperatureColor(startColor, endColor);
+
+            mText.setText(getContext().getString(R.string.hvac_temperature_template, mTemperature));
+            mFloatingText.setText(getContext()
+                    .getString(R.string.hvac_temperature_template, mTemperature));
+            mListener.onTemperatureChanged(mTemperature);
+        }
+    };
+
+    public void setTemperature(int temperature) {
+        int startColor = getTemperatureColor(mTemperature);
+        int endColor = getTemperatureColor(temperature);
+        mTemperature = temperature;
+        String temperatureString;
+
+        if (mTemperature < MIN_TEMPERATURE || mTemperature > MAX_TEMPERATURE) {
+            temperatureString = mInvalidTemperature;
+        } else {
+            temperatureString = String.valueOf(mTemperature);
+        }
+
+        mText.setText(getContext().getString(R.string.hvac_temperature_template,
+                temperatureString));
+        mFloatingText.setText(getContext()
+                .getString(R.string.hvac_temperature_template, temperatureString));
+
+        // Only animate the color if the button is currently enabled.
+        if (mIsOn) {
+            changeTemperatureColor(startColor, endColor);
+        }
+    }
+
+    /**
+     * Sets whether or not the temperature bar is on. If it is off, it should show "off" instead
+     * of the temperature.
+     */
+    public void setIsOn(boolean isOn) {
+        mIsOn = isOn;
+        GradientDrawable temperatureBall
+                = (GradientDrawable) mTemperatureBar.getBackground();
+        if (mIsOn) {
+            mFloatingText.setVisibility(VISIBLE);
+            mOffText.setVisibility(GONE);
+            temperatureBall.setColor(getTemperatureColor(mTemperature));
+            setAlpha(1.0f);
+        } else {
+            mOffText.setVisibility(VISIBLE);
+            mFloatingText.setVisibility(GONE);
+            temperatureBall.setColor(mOffColor);
+            setAlpha(.2f);
+        }
+    }
+
+    private int getTemperatureColor(int temperature) {
+        if (temperature >= 78) {
+            return mTempColor1;
+        } else if (temperature >= 74 && temperature < 78) {
+            return mTempColor2;
+        } else if (temperature >= 70 && temperature < 74) {
+            return mTempColor3;
+        } else if (temperature >= 66 && temperature < 70) {
+            return mTempColor4;
+        } else {
+            return mTempColor5;
+        }
+    }
+
+    private final ValueAnimator.AnimatorUpdateListener mTranslationYListener
+            = new ValueAnimator.AnimatorUpdateListener() {
+        @Override
+        public void onAnimationUpdate(ValueAnimator animation) {
+            float translation = (float) animation.getAnimatedValue();
+            mTemperatureBar.setTranslationY(translation);
+        }
+    };
+
+    private final ValueAnimator.AnimatorUpdateListener mWidthUpdateListener
+            = new ValueAnimator.AnimatorUpdateListener() {
+        @Override
+        public void onAnimationUpdate(ValueAnimator animation) {
+            int width = (Integer) animation.getAnimatedValue();
+            mTemperatureBar.getLayoutParams().width = width;
+            mTemperatureBar.requestLayout();
+        }
+    };
+
+    private final ValueAnimator.AnimatorUpdateListener mHeightUpdateListener
+            = new ValueAnimator.AnimatorUpdateListener() {
+        @Override
+        public void onAnimationUpdate(ValueAnimator animation) {
+            int height = (Integer) animation.getAnimatedValue();
+            int currentHeight = mTemperatureBar.getLayoutParams().height;
+            mTemperatureBar.getLayoutParams().height = height;
+            mTemperatureBar.setTop(mTemperatureBar.getTop() + height - currentHeight);
+            mTemperatureBar.requestLayout();
+        }
+    };
+
+    private final ValueAnimator.AnimatorUpdateListener mTemperatureColorListener
+            = new ValueAnimator.AnimatorUpdateListener() {
+        @Override
+        public void onAnimationUpdate(ValueAnimator animation) {
+            int color = (Integer) animation.getAnimatedValue();
+            ((GradientDrawable) mTemperatureBar.getBackground()).setColor(color);
+        }
+    };
+
+    private ObjectAnimator getAlphaAnimator(View view, boolean fade) {
+        return getAlphaAnimator(view, fade, EXPAND_ANIMATION_TIME_MS);
+    }
+
+    private ObjectAnimator getAlphaAnimator(View view, boolean fade, int duration) {
+
+        float startingAlpha = BUTTON_ALPHA_COLLAPSED;
+        float endingAlpha = BUTTON_ALPHA_EXPANDED;
+
+        if (fade) {
+            startingAlpha = BUTTON_ALPHA_EXPANDED;
+            endingAlpha = BUTTON_ALPHA_COLLAPSED;
+        }
+
+        return ObjectAnimator.ofFloat(view, View.ALPHA,
+                startingAlpha, endingAlpha).setDuration(duration);
+    }
+}
diff --git a/src/com/android/car/hvac/ui/ToggleButton.java b/src/com/android/car/hvac/ui/ToggleButton.java
new file mode 100644 (file)
index 0000000..ae5a3bf
--- /dev/null
@@ -0,0 +1,94 @@
+/*
+ * 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.android.car.hvac.ui;
+
+import android.content.Context;
+import android.graphics.drawable.Drawable;
+import android.util.AttributeSet;
+import android.view.View;
+import android.widget.ImageButton;
+
+/**
+ * A toggle button that has two states, each with a different drawable icon.
+ */
+public class ToggleButton extends ImageButton {
+    /**
+     * A listener that is notified when the button is toggled.
+     */
+    public interface ToggleListener {
+        void onToggled(boolean isOn);
+    }
+
+    private boolean mIsOn;
+
+    private Drawable mDrawableOff;
+    private Drawable mDrawableOn;
+    private ToggleListener mListener;
+
+    public ToggleButton(Context context) {
+        super(context);
+    }
+
+    public ToggleButton(Context context, AttributeSet attrs) {
+        super(context, attrs);
+    }
+
+    public ToggleButton(Context context, AttributeSet attrs, int defStyleAttr) {
+        super(context, attrs, defStyleAttr);
+    }
+
+    @Override
+    public void onFinishInflate() {
+        super.onFinishInflate();
+        setOnClickListener(new OnClickListener() {
+            @Override
+            public void onClick(View v) {
+                if (mIsOn) {
+                    setImageDrawable(mDrawableOff);
+                    if (mListener != null) {
+                        mListener.onToggled(false);
+                    }
+                    mIsOn = false;
+                } else {
+                    setImageDrawable(mDrawableOn);
+                    if (mListener != null) {
+                        mListener.onToggled(true);
+                    }
+                    mIsOn = true;
+                }
+            }
+        });
+    }
+
+    public void setToggleListener(ToggleListener listener) {
+        mListener = listener;
+    }
+
+    public void setToggleIcons(Drawable on, Drawable off) {
+        mDrawableOff = off;
+        mDrawableOn = on;
+        setImageDrawable(mIsOn ? mDrawableOn : mDrawableOff);
+    }
+
+    public void setIsOn(boolean on) {
+        mIsOn = on;
+        if (mIsOn) {
+            setImageDrawable(mDrawableOn);
+        } else {
+            setImageDrawable(mDrawableOff);
+        }
+    }
+}