Merge "Updated CarSensorManager documentation and annotation."
diff --git a/car-lib/Android.bp b/car-lib/Android.bp
index 287949e..c037284 100644
--- a/car-lib/Android.bp
+++ b/car-lib/Android.bp
@@ -110,6 +110,17 @@
     ],
 }
 
+genrule {
+    name: "android-car-last-released-test-api",
+    srcs: [
+        "api/test-released/*.txt",
+    ],
+    cmd: "cp -f $$(echo $(in) | tr \" \" \"\\n\" | sort -n | tail -1) $(genDir)/last-released-test-api.txt",
+    out: [
+        "last-released-test-api.txt",
+    ],
+}
+
 droiddoc {
     name: "android.car-stubs-docs",
     defaults: ["android.car-docs-default"],
@@ -164,6 +175,26 @@
 }
 
 droiddoc {
+    name: "android.car-test-stubs-docs",
+    defaults: ["android.car-docs-default"],
+    api_tag_name: "ANDROID_CAR_SYSTEM",
+    api_filename: "api.txt",
+    removed_api_filename: "removed.txt",
+    args: "-hide 113 -hide 110 -nodocs -stubpackages android.car* " +
+        "-showAnnotation android.annotation.TestApi ",
+    installable: false,
+    check_api: {
+        current: {
+            api_file: "api/test-current.txt",
+            removed_api_file: "api/test-removed.txt",
+            args: " -error 2 -error 3 -error 4 -error 5 -error 6 -error 7 -error 8 -error 9 -error 10 -error 11 " +
+                  " -error 12 -error 13 -error 14 -error 15 -error 16 -error 17 -error 18 -error 19 -error 20 " +
+                  " -error 21 -error 23 -error 24 -error 25 -hide 113 ",
+        },
+    },
+}
+
+droiddoc {
     name: "android.car-stub-docs",
     srcs: [
         "src/**/*.java",
@@ -214,3 +245,19 @@
     },
     compile_dex: true,
 }
+
+java_library_static {
+    name: "android.car-test-stubs",
+    srcs: [
+        ":android.car-test-stubs-docs",
+    ],
+    libs: [
+        "android.car",
+    ],
+    product_variables: {
+        pdk: {
+            enabled: false,
+        },
+    },
+    compile_dex: true,
+}
diff --git a/car-lib/api/test-current.txt b/car-lib/api/test-current.txt
new file mode 100644
index 0000000..c5c41b6
--- /dev/null
+++ b/car-lib/api/test-current.txt
@@ -0,0 +1,8 @@
+package android.car.content.pm {
+
+  public final class CarPackageManager {
+    method public void setEnableActivityBlocking(boolean);
+  }
+
+}
+
diff --git a/car-lib/api/test-removed.txt b/car-lib/api/test-removed.txt
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/car-lib/api/test-removed.txt
diff --git a/car_product/build/car.mk b/car_product/build/car.mk
index 3e41d70..1e67b47 100644
--- a/car_product/build/car.mk
+++ b/car_product/build/car.mk
@@ -127,5 +127,8 @@
 PRODUCT_HIDDENAPI_STUBS_SYSTEM := \
     android.car-system-stubs
 
+PRODUCT_HIDDENAPI_STUBS_TEST := \
+    android.car-test-stubs
+
 INCLUDED_ANDROID_CAR_TO_PRODUCT_BOOT_JARS := yes
 endif
diff --git a/car_product/overlay/frameworks/base/packages/SystemUI/res-keyguard/drawable/ic_backspace.xml b/car_product/overlay/frameworks/base/packages/CarSystemUI/res-keyguard/drawable/ic_backspace.xml
similarity index 100%
rename from car_product/overlay/frameworks/base/packages/SystemUI/res-keyguard/drawable/ic_backspace.xml
rename to car_product/overlay/frameworks/base/packages/CarSystemUI/res-keyguard/drawable/ic_backspace.xml
diff --git a/car_product/overlay/frameworks/base/packages/SystemUI/res-keyguard/drawable/ic_done.xml b/car_product/overlay/frameworks/base/packages/CarSystemUI/res-keyguard/drawable/ic_done.xml
similarity index 100%
rename from car_product/overlay/frameworks/base/packages/SystemUI/res-keyguard/drawable/ic_done.xml
rename to car_product/overlay/frameworks/base/packages/CarSystemUI/res-keyguard/drawable/ic_done.xml
diff --git a/car_product/overlay/frameworks/base/packages/SystemUI/res-keyguard/drawable/keyguard_button_background.xml b/car_product/overlay/frameworks/base/packages/CarSystemUI/res-keyguard/drawable/keyguard_button_background.xml
similarity index 100%
rename from car_product/overlay/frameworks/base/packages/SystemUI/res-keyguard/drawable/keyguard_button_background.xml
rename to car_product/overlay/frameworks/base/packages/CarSystemUI/res-keyguard/drawable/keyguard_button_background.xml
diff --git a/car_product/overlay/frameworks/base/packages/SystemUI/res-keyguard/layout-land/keyguard_pattern_view.xml b/car_product/overlay/frameworks/base/packages/CarSystemUI/res-keyguard/layout-land/keyguard_pattern_view.xml
similarity index 100%
rename from car_product/overlay/frameworks/base/packages/SystemUI/res-keyguard/layout-land/keyguard_pattern_view.xml
rename to car_product/overlay/frameworks/base/packages/CarSystemUI/res-keyguard/layout-land/keyguard_pattern_view.xml
diff --git a/car_product/overlay/frameworks/base/packages/SystemUI/res-keyguard/layout-land/keyguard_pin_view.xml b/car_product/overlay/frameworks/base/packages/CarSystemUI/res-keyguard/layout-land/keyguard_pin_view.xml
similarity index 100%
rename from car_product/overlay/frameworks/base/packages/SystemUI/res-keyguard/layout-land/keyguard_pin_view.xml
rename to car_product/overlay/frameworks/base/packages/CarSystemUI/res-keyguard/layout-land/keyguard_pin_view.xml
diff --git a/car_product/overlay/frameworks/base/packages/SystemUI/res-keyguard/layout/keyguard_bouncer.xml b/car_product/overlay/frameworks/base/packages/CarSystemUI/res-keyguard/layout/keyguard_bouncer.xml
similarity index 100%
rename from car_product/overlay/frameworks/base/packages/SystemUI/res-keyguard/layout/keyguard_bouncer.xml
rename to car_product/overlay/frameworks/base/packages/CarSystemUI/res-keyguard/layout/keyguard_bouncer.xml
diff --git a/car_product/overlay/frameworks/base/packages/CarSystemUI/res-keyguard/layout/keyguard_message_area.xml b/car_product/overlay/frameworks/base/packages/CarSystemUI/res-keyguard/layout/keyguard_message_area.xml
new file mode 100644
index 0000000..c230414
--- /dev/null
+++ b/car_product/overlay/frameworks/base/packages/CarSystemUI/res-keyguard/layout/keyguard_message_area.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+**
+** Copyright 2018, 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.
+*/
+-->
+
+<com.android.keyguard.KeyguardMessageArea
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:gravity="center"
+    style="@style/Keyguard.TextView"
+    android:id="@+id/keyguard_message_area"
+    android:singleLine="true"
+    android:ellipsize="marquee"
+    android:focusable="true"
+    android:layout_marginBottom="@dimen/car_padding_4"
+    android:textSize="@dimen/car_body2_size" />
diff --git a/car_product/overlay/frameworks/base/packages/SystemUI/res-keyguard/layout/keyguard_num_pad_key.xml b/car_product/overlay/frameworks/base/packages/CarSystemUI/res-keyguard/layout/keyguard_num_pad_key.xml
similarity index 100%
rename from car_product/overlay/frameworks/base/packages/SystemUI/res-keyguard/layout/keyguard_num_pad_key.xml
rename to car_product/overlay/frameworks/base/packages/CarSystemUI/res-keyguard/layout/keyguard_num_pad_key.xml
diff --git a/car_product/overlay/frameworks/base/packages/SystemUI/res-keyguard/layout/keyguard_password_view.xml b/car_product/overlay/frameworks/base/packages/CarSystemUI/res-keyguard/layout/keyguard_password_view.xml
similarity index 100%
rename from car_product/overlay/frameworks/base/packages/SystemUI/res-keyguard/layout/keyguard_password_view.xml
rename to car_product/overlay/frameworks/base/packages/CarSystemUI/res-keyguard/layout/keyguard_password_view.xml
diff --git a/car_product/overlay/frameworks/base/packages/SystemUI/res-keyguard/layout/keyguard_pattern_view.xml b/car_product/overlay/frameworks/base/packages/CarSystemUI/res-keyguard/layout/keyguard_pattern_view.xml
similarity index 100%
rename from car_product/overlay/frameworks/base/packages/SystemUI/res-keyguard/layout/keyguard_pattern_view.xml
rename to car_product/overlay/frameworks/base/packages/CarSystemUI/res-keyguard/layout/keyguard_pattern_view.xml
diff --git a/car_product/overlay/frameworks/base/packages/SystemUI/res-keyguard/layout/keyguard_pin_view.xml b/car_product/overlay/frameworks/base/packages/CarSystemUI/res-keyguard/layout/keyguard_pin_view.xml
similarity index 100%
rename from car_product/overlay/frameworks/base/packages/SystemUI/res-keyguard/layout/keyguard_pin_view.xml
rename to car_product/overlay/frameworks/base/packages/CarSystemUI/res-keyguard/layout/keyguard_pin_view.xml
diff --git a/car_product/overlay/frameworks/base/packages/CarSystemUI/res-keyguard/layout/num_pad_keys.xml b/car_product/overlay/frameworks/base/packages/CarSystemUI/res-keyguard/layout/num_pad_keys.xml
new file mode 100644
index 0000000..ca0595d
--- /dev/null
+++ b/car_product/overlay/frameworks/base/packages/CarSystemUI/res-keyguard/layout/num_pad_keys.xml
@@ -0,0 +1,83 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+  ~ Copyright (C) 2018 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"
+       xmlns:app="http://schemas.android.com/apk/res-auto">
+    <!-- Row 1 -->
+    <com.android.keyguard.NumPadKey
+        android:id="@+id/key1"
+        style="@style/NumPadKeyButton"
+        app:digit="@string/one" />
+    <com.android.keyguard.NumPadKey
+        android:id="@+id/key2"
+        style="@style/NumPadKeyButton.MiddleColumn"
+        app:digit="@string/two" />
+    <com.android.keyguard.NumPadKey
+        android:id="@+id/key3"
+        style="@style/NumPadKeyButton"
+        app:digit="@string/three" />
+
+    <!-- Row 2 -->
+    <com.android.keyguard.NumPadKey
+        android:id="@+id/key4"
+        style="@style/NumPadKeyButton"
+        app:digit="@string/four" />
+    <com.android.keyguard.NumPadKey
+        android:id="@+id/key5"
+        style="@style/NumPadKeyButton.MiddleColumn"
+        app:digit="@string/five" />
+    <com.android.keyguard.NumPadKey
+        android:id="@+id/key6"
+        style="@style/NumPadKeyButton"
+        app:digit="@string/six" />
+
+    <!-- Row 3 -->
+    <com.android.keyguard.NumPadKey
+        android:id="@+id/key7"
+        style="@style/NumPadKeyButton"
+        app:digit="@string/seven" />
+    <com.android.keyguard.NumPadKey
+        android:id="@+id/key8"
+        style="@style/NumPadKeyButton.MiddleColumn"
+        app:digit="@string/eight" />
+    <com.android.keyguard.NumPadKey
+        android:id="@+id/key9"
+        style="@style/NumPadKeyButton"
+        app:digit="@string/nine" />
+
+    <!-- Row 4 -->
+    <ImageButton
+        android:id="@+id/delete_button"
+        style="@style/NumPadKeyButton.LastRow"
+        android:gravity="center_vertical"
+        android:src="@drawable/ic_backspace"
+        android:clickable="true"
+        android:tint="@android:color/white"
+        android:background="@drawable/ripple_drawable"
+        android:contentDescription="@string/keyboardview_keycode_delete" />
+    <com.android.keyguard.NumPadKey
+        android:id="@+id/key0"
+        style="@style/NumPadKeyButton.LastRow.MiddleColumn"
+        app:digit="@string/zero" />
+    <ImageButton
+        android:id="@+id/key_enter"
+        style="@style/NumPadKeyButton.LastRow"
+        android:src="@drawable/ic_done"
+        android:tint="@android:color/white"
+        android:background="@drawable/ripple_drawable"
+        android:contentDescription="@string/keyboardview_keycode_enter" />
+</merge>
+
diff --git a/car_product/overlay/frameworks/base/packages/SystemUI/res-keyguard/values-h1000dp/dimens.xml b/car_product/overlay/frameworks/base/packages/CarSystemUI/res-keyguard/values-h1000dp/dimens.xml
similarity index 100%
rename from car_product/overlay/frameworks/base/packages/SystemUI/res-keyguard/values-h1000dp/dimens.xml
rename to car_product/overlay/frameworks/base/packages/CarSystemUI/res-keyguard/values-h1000dp/dimens.xml
diff --git a/car_product/overlay/frameworks/base/packages/CarSystemUI/res-keyguard/values-land/dimens.xml b/car_product/overlay/frameworks/base/packages/CarSystemUI/res-keyguard/values-land/dimens.xml
new file mode 100644
index 0000000..805a134
--- /dev/null
+++ b/car_product/overlay/frameworks/base/packages/CarSystemUI/res-keyguard/values-land/dimens.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  Copyright 2018, 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="num_pad_key_margin_horizontal">@dimen/car_padding_5</dimen>
+    <dimen name="num_pad_key_margin_bottom">@dimen/car_padding_4</dimen>
+</resources>
diff --git a/car_product/overlay/frameworks/base/packages/SystemUI/res-keyguard/values/colors.xml b/car_product/overlay/frameworks/base/packages/CarSystemUI/res-keyguard/values/colors.xml
similarity index 100%
rename from car_product/overlay/frameworks/base/packages/SystemUI/res-keyguard/values/colors.xml
rename to car_product/overlay/frameworks/base/packages/CarSystemUI/res-keyguard/values/colors.xml
diff --git a/car_product/overlay/frameworks/base/packages/SystemUI/res-keyguard/values/dimens.xml b/car_product/overlay/frameworks/base/packages/CarSystemUI/res-keyguard/values/dimens.xml
similarity index 100%
rename from car_product/overlay/frameworks/base/packages/SystemUI/res-keyguard/values/dimens.xml
rename to car_product/overlay/frameworks/base/packages/CarSystemUI/res-keyguard/values/dimens.xml
diff --git a/car_product/overlay/frameworks/base/packages/SystemUI/res-keyguard/values/integers.xml b/car_product/overlay/frameworks/base/packages/CarSystemUI/res-keyguard/values/integers.xml
similarity index 100%
rename from car_product/overlay/frameworks/base/packages/SystemUI/res-keyguard/values/integers.xml
rename to car_product/overlay/frameworks/base/packages/CarSystemUI/res-keyguard/values/integers.xml
diff --git a/car_product/overlay/frameworks/base/packages/SystemUI/res-keyguard/values/strings.xml b/car_product/overlay/frameworks/base/packages/CarSystemUI/res-keyguard/values/strings.xml
similarity index 100%
rename from car_product/overlay/frameworks/base/packages/SystemUI/res-keyguard/values/strings.xml
rename to car_product/overlay/frameworks/base/packages/CarSystemUI/res-keyguard/values/strings.xml
diff --git a/car_product/overlay/frameworks/base/packages/SystemUI/res-keyguard/values/styles.xml b/car_product/overlay/frameworks/base/packages/CarSystemUI/res-keyguard/values/styles.xml
similarity index 100%
rename from car_product/overlay/frameworks/base/packages/SystemUI/res-keyguard/values/styles.xml
rename to car_product/overlay/frameworks/base/packages/CarSystemUI/res-keyguard/values/styles.xml
diff --git a/car_product/overlay/frameworks/base/packages/SystemUI/res/values-h600dp/dimens.xml b/car_product/overlay/frameworks/base/packages/CarSystemUI/res/values-h600dp/dimens.xml
similarity index 100%
rename from car_product/overlay/frameworks/base/packages/SystemUI/res/values-h600dp/dimens.xml
rename to car_product/overlay/frameworks/base/packages/CarSystemUI/res/values-h600dp/dimens.xml
diff --git a/car_product/overlay/frameworks/base/packages/SystemUI/res/values-sw600dp/dimens.xml b/car_product/overlay/frameworks/base/packages/CarSystemUI/res/values-sw600dp/dimens.xml
similarity index 100%
rename from car_product/overlay/frameworks/base/packages/SystemUI/res/values-sw600dp/dimens.xml
rename to car_product/overlay/frameworks/base/packages/CarSystemUI/res/values-sw600dp/dimens.xml
diff --git a/car_product/overlay/frameworks/base/packages/SystemUI/res/values-w1024dp/dimens.xml b/car_product/overlay/frameworks/base/packages/CarSystemUI/res/values-w1024dp/dimens.xml
similarity index 100%
rename from car_product/overlay/frameworks/base/packages/SystemUI/res/values-w1024dp/dimens.xml
rename to car_product/overlay/frameworks/base/packages/CarSystemUI/res/values-w1024dp/dimens.xml
diff --git a/car_product/overlay/frameworks/base/packages/SystemUI/res/values-w550dp-land/dimens.xml b/car_product/overlay/frameworks/base/packages/CarSystemUI/res/values-w550dp-land/dimens.xml
similarity index 100%
rename from car_product/overlay/frameworks/base/packages/SystemUI/res/values-w550dp-land/dimens.xml
rename to car_product/overlay/frameworks/base/packages/CarSystemUI/res/values-w550dp-land/dimens.xml
diff --git a/car_product/overlay/frameworks/base/packages/SystemUI/res/values/config.xml b/car_product/overlay/frameworks/base/packages/CarSystemUI/res/values/config.xml
similarity index 100%
rename from car_product/overlay/frameworks/base/packages/SystemUI/res/values/config.xml
rename to car_product/overlay/frameworks/base/packages/CarSystemUI/res/values/config.xml
diff --git a/car_product/overlay/frameworks/base/packages/SystemUI/res/values/dimens.xml b/car_product/overlay/frameworks/base/packages/CarSystemUI/res/values/dimens.xml
similarity index 100%
rename from car_product/overlay/frameworks/base/packages/SystemUI/res/values/dimens.xml
rename to car_product/overlay/frameworks/base/packages/CarSystemUI/res/values/dimens.xml
diff --git a/service/AndroidManifest.xml b/service/AndroidManifest.xml
index 641ead9..1f221ac 100644
--- a/service/AndroidManifest.xml
+++ b/service/AndroidManifest.xml
@@ -23,7 +23,7 @@
     <original-package android:name="com.android.car" />
      <permission-group
         android:name="android.car.permission-group.CAR_MONITORING"
-        android:icon="@drawable/car_ic_mode"
+        android:icon="@drawable/perm_group_car"
         android:description="@string/car_permission_desc"
         android:label="@string/car_permission_label" />
     <permission
diff --git a/service/res/drawable-hdpi/car_ic_mode.png b/service/res/drawable-hdpi/car_ic_mode.png
deleted file mode 100644
index a8f719f..0000000
--- a/service/res/drawable-hdpi/car_ic_mode.png
+++ /dev/null
Binary files differ
diff --git a/service/res/drawable-mdpi/car_ic_mode.png b/service/res/drawable-mdpi/car_ic_mode.png
deleted file mode 100644
index 38a9747..0000000
--- a/service/res/drawable-mdpi/car_ic_mode.png
+++ /dev/null
Binary files differ
diff --git a/service/res/drawable-xhdpi/car_ic_mode.png b/service/res/drawable-xhdpi/car_ic_mode.png
deleted file mode 100644
index 58a1aca..0000000
--- a/service/res/drawable-xhdpi/car_ic_mode.png
+++ /dev/null
Binary files differ
diff --git a/service/res/drawable/perm_group_car.xml b/service/res/drawable/perm_group_car.xml
new file mode 100644
index 0000000..79e3f03
--- /dev/null
+++ b/service/res/drawable/perm_group_car.xml
@@ -0,0 +1,24 @@
+<!--
+    Copyright (C) 2018 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="24dp"
+        android:height="24dp"
+        android:viewportHeight="192.0"
+        android:viewportWidth="192.0">
+
+    <path android:fillColor="#FFFFFFFF"
+          android:pathData="M36.9,180.4l5.6,5.6L96,164l53.4,22l5.6,-5.6L96,73L36.9,180.4zM184,145.8L105.7,11.6C103.7,8.1 100,6 96,6c-4,0 -7.7,2.1 -9.8,5.6L7.5,146.5c-2,3.5 -2.1,7.3 0,10.8c2,3.5 5.8,5.7 9.8,5.7H38l54.6,-99h6.8l54.6,99h20.3c6.2,0 11.7,-4.6 11.7,-10.8C186,149.8 185.3,147.6 184,145.8z"/>
+</vector>
diff --git a/service/res/values/config.xml b/service/res/values/config.xml
index 1d72dca..3b05e29 100644
--- a/service/res/values/config.xml
+++ b/service/res/values/config.xml
@@ -24,6 +24,14 @@
           dynamic audio routing is disabled and audio works in legacy mode. It may be useful
           during initial development where audio hal does not support bus based addressing yet. -->
     <bool name="audioUseDynamicRouting">false</bool>
+
+    <!--  Configuration to use the unified audio configuration.
+          This flag has no effect if audioUseDynamicRouting is set to false.
+          When audioUseDynamicRouting is enabled
+          - car_volume_groups.xml will be picked if this flag is false
+          - car_audio_configuration.xml will be used otherwise. -->
+    <bool name="audioUseUnifiedConfiguration">false</bool>
+
     <!--  Configuration to persist master mute state. If this is set to true,
           Android will restore the master mute state on boot. -->
     <bool name="audioPersistMasterMuteState">true</bool>
diff --git a/service/res/xml/car_audio_configuration.xml b/service/res/xml/car_audio_configuration.xml
index 2f82233..e3fd8f6 100644
--- a/service/res/xml/car_audio_configuration.xml
+++ b/service/res/xml/car_audio_configuration.xml
@@ -44,7 +44,7 @@
                 </group>
                 <group>
                     <device car:address="bus1_navigation_out">
-                        <context car:context="notification"/>
+                        <context car:context="navigation"/>
                     </device>
                     <device car:address="bus2_voice_command_out">
                         <context car:context="voice_command"/>
@@ -70,7 +70,7 @@
                 <group>
                     <device car:address="bus100_rear_seat">
                         <context car:context="music"/>
-                        <context car:context="notification"/>
+                        <context car:context="navigation"/>
                         <context car:context="voice_command"/>
                         <context car:context="call_ring"/>
                         <context car:context="call"/>
diff --git a/service/res/xml/car_volume_groups.xml b/service/res/xml/car_volume_groups.xml
index e2fa6b2..c900329 100644
--- a/service/res/xml/car_volume_groups.xml
+++ b/service/res/xml/car_volume_groups.xml
@@ -18,13 +18,12 @@
   This configuration is replaced by car_audio_configuration.xml
 
   Notes on backward compatibility
-    - A new isDeprecated=true attr is added to the stock car_volume_groups.xml
-    - Existing car_volume_groups.xml overlays from OEM, without isDeprecated
-      attr being set, should default to isDeprecated=false
-    - If isDeprecated=true, CarAudioService loads the new
-      car_audio_configuration.xml
-    - Otherwise, CarAudioService loads car_volume_groups.xml queries
-      IAudioControl HAL (getBusForContext)
+  - A new audioUseUnifiedConfiguration flag is added, and the default value
+  is false
+  - If OEM does not explicitly set audioUseUnifiedConfiguration to be true,
+  car_volume_groups.xml will be used, CarAudioService also queries
+  IAudioControl HAL (getBusForContext)
+  - Otherwise, CarAudioService loads the new car_audio_configuration.xml
 
   ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 
diff --git a/service/src/com/android/car/CarProjectionService.java b/service/src/com/android/car/CarProjectionService.java
index f47362c..5bc8352 100644
--- a/service/src/com/android/car/CarProjectionService.java
+++ b/service/src/com/android/car/CarProjectionService.java
@@ -140,7 +140,7 @@
             public void onServiceDisconnected(ComponentName className) {
                 // Service has crashed.
                 Log.w(CarLog.TAG_PROJECTION, "Service disconnected: " + className);
-                synchronized (CarProjectionService.this) {
+                synchronized (mLock) {
                     mRegisteredService = null;
                 }
                 unbindServiceIfBound();
@@ -163,7 +163,7 @@
     public void registerProjectionRunner(Intent serviceIntent) {
         // We assume one active projection app running in the system at one time.
         synchronized (mLock) {
-            if (serviceIntent.filterEquals(mRegisteredService)) {
+            if (serviceIntent.filterEquals(mRegisteredService) && mBound) {
                 return;
             }
             if (mRegisteredService != null) {
@@ -203,6 +203,7 @@
                 return;
             }
             mBound = false;
+            mRegisteredService = null;
         }
         mContext.unbindService(mConnection);
     }
@@ -529,6 +530,8 @@
             writer.println("Wireless clients: " +  mWirelessClients.size());
             writer.println("Current wifi mode: " + mWifiMode);
             writer.println("SoftApCallback: " + mSoftApCallback);
+            writer.println("Bound to projection app: " + mBound);
+            writer.println("Registered Service: " + mRegisteredService);
         }
     }
 
diff --git a/service/src/com/android/car/audio/CarAudioDeviceInfo.java b/service/src/com/android/car/audio/CarAudioDeviceInfo.java
index 2e35c56..f45f48e 100644
--- a/service/src/com/android/car/audio/CarAudioDeviceInfo.java
+++ b/service/src/com/android/car/audio/CarAudioDeviceInfo.java
@@ -38,6 +38,26 @@
  */
 /* package */ class CarAudioDeviceInfo {
 
+    /**
+     * Parse device address. Expected format is BUS%d_%s, address, usage hint
+     * @return valid address (from 0 to positive) or -1 for invalid address.
+     */
+    static int parseDeviceAddress(String address) {
+        String[] words = address.split("_");
+        int addressParsed = -1;
+        if (words[0].toLowerCase().startsWith("bus")) {
+            try {
+                addressParsed = Integer.parseInt(words[0].substring(3));
+            } catch (NumberFormatException e) {
+                //ignore
+            }
+        }
+        if (addressParsed < 0) {
+            return -1;
+        }
+        return addressParsed;
+    }
+
     private final AudioDeviceInfo mAudioDeviceInfo;
     private final int mBusNumber;
     private final int mSampleRate;
@@ -143,26 +163,6 @@
         }
     }
 
-    /**
-     * Parse device address. Expected format is BUS%d_%s, address, usage hint
-     * @return valid address (from 0 to positive) or -1 for invalid address.
-     */
-    private int parseDeviceAddress(String address) {
-        String[] words = address.split("_");
-        int addressParsed = -1;
-        if (words[0].toLowerCase().startsWith("bus")) {
-            try {
-                addressParsed = Integer.parseInt(words[0].substring(3));
-            } catch (NumberFormatException e) {
-                //ignore
-            }
-        }
-        if (addressParsed < 0) {
-            return -1;
-        }
-        return addressParsed;
-    }
-
     private int getMaxSampleRate(AudioDeviceInfo info) {
         int[] sampleRates = info.getSampleRates();
         if (sampleRates == null || sampleRates.length == 0) {
@@ -247,12 +247,12 @@
                 + " minGain: " + mMinGain;
     }
 
-    void dump(PrintWriter writer) {
-        writer.printf("Bus Number (%d) / address (%s)\n ",
-                mBusNumber, mAudioDeviceInfo.getAddress());
-        writer.printf("\tsample rate / encoding format / channel count: %d %d %d\n",
-                getSampleRate(), getEncodingFormat(), getChannelCount());
-        writer.printf("\tGain in millibel (min / max / default/ current): %d %d %d %d\n",
-                mMinGain, mMaxGain, mDefaultGain, mCurrentGain);
+    void dump(String indent, PrintWriter writer) {
+        writer.printf("%sCarAudioDeviceInfo Bus(%d: %s)\n ",
+                indent, mBusNumber, mAudioDeviceInfo.getAddress());
+        writer.printf("%s\tsample rate / encoding format / channel count: %d %d %d\n",
+                indent, getSampleRate(), getEncodingFormat(), getChannelCount());
+        writer.printf("%s\tGain values (min / max / default/ current): %d %d %d %d\n",
+                indent, mMinGain, mMaxGain, mDefaultGain, mCurrentGain);
     }
 }
diff --git a/service/src/com/android/car/audio/CarAudioDynamicRouting.java b/service/src/com/android/car/audio/CarAudioDynamicRouting.java
new file mode 100644
index 0000000..73b810c
--- /dev/null
+++ b/service/src/com/android/car/audio/CarAudioDynamicRouting.java
@@ -0,0 +1,162 @@
+/*
+ * Copyright (C) 2018 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.audio;
+
+import android.hardware.automotive.audiocontrol.V1_0.ContextNumber;
+import android.media.AudioAttributes;
+import android.media.AudioFormat;
+import android.media.AudioManager;
+import android.media.audiopolicy.AudioMix;
+import android.media.audiopolicy.AudioMixingRule;
+import android.media.audiopolicy.AudioPolicy;
+import android.util.Log;
+import android.util.SparseIntArray;
+
+import com.android.car.CarLog;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * Builds dynamic audio routing in a car from audio zone configuration.
+ */
+/* package */ class CarAudioDynamicRouting {
+
+    static final int[] CONTEXT_NUMBERS = new int[] {
+            ContextNumber.MUSIC,
+            ContextNumber.NAVIGATION,
+            ContextNumber.VOICE_COMMAND,
+            ContextNumber.CALL_RING,
+            ContextNumber.CALL,
+            ContextNumber.ALARM,
+            ContextNumber.NOTIFICATION,
+            ContextNumber.SYSTEM_SOUND
+    };
+
+    static final SparseIntArray USAGE_TO_CONTEXT = new SparseIntArray();
+
+    static final int DEFAULT_AUDIO_USAGE = AudioAttributes.USAGE_MEDIA;
+
+    // For legacy stream type based volume control.
+    // Values in STREAM_TYPES and STREAM_TYPE_USAGES should be aligned.
+    static final int[] STREAM_TYPES = new int[] {
+            AudioManager.STREAM_MUSIC,
+            AudioManager.STREAM_ALARM,
+            AudioManager.STREAM_RING
+    };
+    static final int[] STREAM_TYPE_USAGES = new int[] {
+            AudioAttributes.USAGE_MEDIA,
+            AudioAttributes.USAGE_ALARM,
+            AudioAttributes.USAGE_NOTIFICATION_RINGTONE
+    };
+
+    static {
+        USAGE_TO_CONTEXT.put(AudioAttributes.USAGE_UNKNOWN, ContextNumber.MUSIC);
+        USAGE_TO_CONTEXT.put(AudioAttributes.USAGE_MEDIA, ContextNumber.MUSIC);
+        USAGE_TO_CONTEXT.put(AudioAttributes.USAGE_VOICE_COMMUNICATION, ContextNumber.CALL);
+        USAGE_TO_CONTEXT.put(AudioAttributes.USAGE_VOICE_COMMUNICATION_SIGNALLING,
+                ContextNumber.CALL);
+        USAGE_TO_CONTEXT.put(AudioAttributes.USAGE_ALARM, ContextNumber.ALARM);
+        USAGE_TO_CONTEXT.put(AudioAttributes.USAGE_NOTIFICATION, ContextNumber.NOTIFICATION);
+        USAGE_TO_CONTEXT.put(AudioAttributes.USAGE_NOTIFICATION_RINGTONE, ContextNumber.CALL_RING);
+        USAGE_TO_CONTEXT.put(AudioAttributes.USAGE_NOTIFICATION_COMMUNICATION_REQUEST,
+                ContextNumber.NOTIFICATION);
+        USAGE_TO_CONTEXT.put(AudioAttributes.USAGE_NOTIFICATION_COMMUNICATION_INSTANT,
+                ContextNumber.NOTIFICATION);
+        USAGE_TO_CONTEXT.put(AudioAttributes.USAGE_NOTIFICATION_COMMUNICATION_DELAYED,
+                ContextNumber.NOTIFICATION);
+        USAGE_TO_CONTEXT.put(AudioAttributes.USAGE_NOTIFICATION_EVENT, ContextNumber.NOTIFICATION);
+        USAGE_TO_CONTEXT.put(AudioAttributes.USAGE_ASSISTANCE_ACCESSIBILITY,
+                ContextNumber.VOICE_COMMAND);
+        USAGE_TO_CONTEXT.put(AudioAttributes.USAGE_ASSISTANCE_NAVIGATION_GUIDANCE,
+                ContextNumber.NAVIGATION);
+        USAGE_TO_CONTEXT.put(AudioAttributes.USAGE_ASSISTANCE_SONIFICATION,
+                ContextNumber.SYSTEM_SOUND);
+        USAGE_TO_CONTEXT.put(AudioAttributes.USAGE_GAME, ContextNumber.MUSIC);
+        USAGE_TO_CONTEXT.put(AudioAttributes.USAGE_VIRTUAL_SOURCE, ContextNumber.INVALID);
+        USAGE_TO_CONTEXT.put(AudioAttributes.USAGE_ASSISTANT, ContextNumber.VOICE_COMMAND);
+    }
+
+    private final CarAudioZone[] mCarAudioZones;
+
+    CarAudioDynamicRouting(CarAudioZone[] carAudioZones) {
+        mCarAudioZones = carAudioZones;
+    }
+
+    void setupAudioDynamicRouting(AudioPolicy.Builder builder) {
+        for (CarAudioZone zone : mCarAudioZones) {
+            for (CarVolumeGroup group : zone.getVolumeGroups()) {
+                setupAudioDynamicRoutingForGroup(group, builder);
+            }
+        }
+    }
+
+    /**
+     * Enumerates all physical buses in a given volume group and attach the mixing rules.
+     * @param group {@link CarVolumeGroup} instance to enumerate the buses with
+     * @param builder {@link AudioPolicy.Builder} to attach the mixing rules
+     */
+    private void setupAudioDynamicRoutingForGroup(CarVolumeGroup group,
+            AudioPolicy.Builder builder) {
+        // Note that one can not register audio mix for same bus more than once.
+        for (int busNumber : group.getBusNumbers()) {
+            boolean hasContext = false;
+            CarAudioDeviceInfo info = group.getCarAudioDeviceInfoForBus(busNumber);
+            AudioFormat mixFormat = new AudioFormat.Builder()
+                    .setSampleRate(info.getSampleRate())
+                    .setEncoding(info.getEncodingFormat())
+                    .setChannelMask(info.getChannelCount())
+                    .build();
+            AudioMixingRule.Builder mixingRuleBuilder = new AudioMixingRule.Builder();
+            for (int contextNumber : group.getContextsForBus(busNumber)) {
+                hasContext = true;
+                int[] usages = getUsagesForContext(contextNumber);
+                for (int usage : usages) {
+                    mixingRuleBuilder.addRule(
+                            new AudioAttributes.Builder().setUsage(usage).build(),
+                            AudioMixingRule.RULE_MATCH_ATTRIBUTE_USAGE);
+                }
+                Log.d(CarLog.TAG_AUDIO, "Bus number: " + busNumber
+                        + " contextNumber: " + contextNumber
+                        + " sampleRate: " + info.getSampleRate()
+                        + " channels: " + info.getChannelCount()
+                        + " usages: " + Arrays.toString(usages));
+            }
+            if (hasContext) {
+                // It's a valid case that an audio output bus is defined in
+                // audio_policy_configuration and no context is assigned to it.
+                // In such case, do not build a policy mix with zero rules.
+                AudioMix audioMix = new AudioMix.Builder(mixingRuleBuilder.build())
+                        .setFormat(mixFormat)
+                        .setDevice(info.getAudioDeviceInfo())
+                        .setRouteFlags(AudioMix.ROUTE_FLAG_RENDER)
+                        .build();
+                builder.addMix(audioMix);
+            }
+        }
+    }
+
+    private int[] getUsagesForContext(int contextNumber) {
+        final List<Integer> usages = new ArrayList<>();
+        for (int i = 0; i < CarAudioDynamicRouting.USAGE_TO_CONTEXT.size(); i++) {
+            if (CarAudioDynamicRouting.USAGE_TO_CONTEXT.valueAt(i) == contextNumber) {
+                usages.add(CarAudioDynamicRouting.USAGE_TO_CONTEXT.keyAt(i));
+            }
+        }
+        return usages.stream().mapToInt(i -> i).toArray();
+    }
+}
diff --git a/service/src/com/android/car/audio/CarAudioService.java b/service/src/com/android/car/audio/CarAudioService.java
index b3743dc..324c5ac 100644
--- a/service/src/com/android/car/audio/CarAudioService.java
+++ b/service/src/com/android/car/audio/CarAudioService.java
@@ -27,7 +27,6 @@
 import android.content.Intent;
 import android.content.IntentFilter;
 import android.content.pm.PackageManager;
-import android.hardware.automotive.audiocontrol.V1_0.ContextNumber;
 import android.hardware.automotive.audiocontrol.V1_0.IAudioControl;
 import android.media.AudioAttributes;
 import android.media.AudioDeviceInfo;
@@ -40,8 +39,6 @@
 import android.media.AudioPlaybackConfiguration;
 import android.media.AudioPortConfig;
 import android.media.AudioSystem;
-import android.media.audiopolicy.AudioMix;
-import android.media.audiopolicy.AudioMixingRule;
 import android.media.audiopolicy.AudioPolicy;
 import android.os.IBinder;
 import android.os.Looper;
@@ -51,7 +48,6 @@
 import android.text.TextUtils;
 import android.util.Log;
 import android.util.SparseArray;
-import android.util.SparseIntArray;
 import android.view.KeyEvent;
 
 import com.android.car.BinderInterfaceContainer;
@@ -63,7 +59,6 @@
 import java.io.PrintWriter;
 import java.util.ArrayList;
 import java.util.Arrays;
-import java.util.HashSet;
 import java.util.List;
 import java.util.NoSuchElementException;
 import java.util.Set;
@@ -81,35 +76,6 @@
     // Search for "DUCK_VSHAPE" in PLaybackActivityMonitor.java to see where this happens.
     private static boolean sUseCarAudioFocus = true;
 
-    private static final int DEFAULT_AUDIO_USAGE = AudioAttributes.USAGE_MEDIA;
-
-    private static final int[] CONTEXT_NUMBERS = new int[] {
-            ContextNumber.MUSIC,
-            ContextNumber.NAVIGATION,
-            ContextNumber.VOICE_COMMAND,
-            ContextNumber.CALL_RING,
-            ContextNumber.CALL,
-            ContextNumber.ALARM,
-            ContextNumber.NOTIFICATION,
-            ContextNumber.SYSTEM_SOUND
-    };
-
-    private static final SparseIntArray USAGE_TO_CONTEXT = new SparseIntArray();
-
-    // For legacy stream type based volume control.
-    // Values in STREAM_TYPES and STREAM_TYPE_USAGES should be aligned.
-    private static final int[] STREAM_TYPES = new int[] {
-            AudioManager.STREAM_MUSIC,
-            AudioManager.STREAM_ALARM,
-            AudioManager.STREAM_RING
-    };
-    private static final int[] STREAM_TYPE_USAGES = new int[] {
-            AudioAttributes.USAGE_MEDIA,
-            AudioAttributes.USAGE_ALARM,
-            AudioAttributes.USAGE_NOTIFICATION_RINGTONE
-    };
-
-
     // Key to persist master mute state in system settings
     private static final String VOLUME_SETTINGS_KEY_MASTER_MUTE = "android.car.MASTER_MUTE";
 
@@ -118,15 +84,6 @@
     private static final String VOLUME_SETTINGS_KEY_FOR_GROUP_PREFIX = "android.car.VOLUME_GROUP/";
 
     /**
-     * Gets the key to persist volume for a volume group in settings, in primary zone
-     *
-     * @see {@link #getVolumeSettingsKeyForGroup(int, int)}
-     */
-    static String getVolumeSettingsKeyForGroup(int groupId) {
-        return getVolumeSettingsKeyForGroup(CarAudioManager.PRIMARY_AUDIO_ZONE, groupId);
-    }
-
-    /**
      * Gets the key to persist volume for a volume group in settings
      *
      * @param zoneId The audio zone id
@@ -138,42 +95,14 @@
         return VOLUME_SETTINGS_KEY_FOR_GROUP_PREFIX + maskedGroupId;
     }
 
-    static {
-        USAGE_TO_CONTEXT.put(AudioAttributes.USAGE_UNKNOWN, ContextNumber.MUSIC);
-        USAGE_TO_CONTEXT.put(AudioAttributes.USAGE_MEDIA, ContextNumber.MUSIC);
-        USAGE_TO_CONTEXT.put(AudioAttributes.USAGE_VOICE_COMMUNICATION, ContextNumber.CALL);
-        USAGE_TO_CONTEXT.put(AudioAttributes.USAGE_VOICE_COMMUNICATION_SIGNALLING,
-                ContextNumber.CALL);
-        USAGE_TO_CONTEXT.put(AudioAttributes.USAGE_ALARM, ContextNumber.ALARM);
-        USAGE_TO_CONTEXT.put(AudioAttributes.USAGE_NOTIFICATION, ContextNumber.NOTIFICATION);
-        USAGE_TO_CONTEXT.put(AudioAttributes.USAGE_NOTIFICATION_RINGTONE, ContextNumber.CALL_RING);
-        USAGE_TO_CONTEXT.put(AudioAttributes.USAGE_NOTIFICATION_COMMUNICATION_REQUEST,
-                ContextNumber.NOTIFICATION);
-        USAGE_TO_CONTEXT.put(AudioAttributes.USAGE_NOTIFICATION_COMMUNICATION_INSTANT,
-                ContextNumber.NOTIFICATION);
-        USAGE_TO_CONTEXT.put(AudioAttributes.USAGE_NOTIFICATION_COMMUNICATION_DELAYED,
-                ContextNumber.NOTIFICATION);
-        USAGE_TO_CONTEXT.put(AudioAttributes.USAGE_NOTIFICATION_EVENT, ContextNumber.NOTIFICATION);
-        USAGE_TO_CONTEXT.put(AudioAttributes.USAGE_ASSISTANCE_ACCESSIBILITY,
-                ContextNumber.VOICE_COMMAND);
-        USAGE_TO_CONTEXT.put(AudioAttributes.USAGE_ASSISTANCE_NAVIGATION_GUIDANCE,
-                ContextNumber.NAVIGATION);
-        USAGE_TO_CONTEXT.put(AudioAttributes.USAGE_ASSISTANCE_SONIFICATION,
-                ContextNumber.SYSTEM_SOUND);
-        USAGE_TO_CONTEXT.put(AudioAttributes.USAGE_GAME, ContextNumber.MUSIC);
-        USAGE_TO_CONTEXT.put(AudioAttributes.USAGE_VIRTUAL_SOURCE, ContextNumber.INVALID);
-        USAGE_TO_CONTEXT.put(AudioAttributes.USAGE_ASSISTANT, ContextNumber.VOICE_COMMAND);
-    }
-
     private final Object mImplLock = new Object();
 
     private final Context mContext;
     private final TelephonyManager mTelephonyManager;
     private final AudioManager mAudioManager;
     private final boolean mUseDynamicRouting;
+    private final boolean mUseUnifiedConfiguration;
     private final boolean mPersistMasterMuteState;
-    private final SparseIntArray mContextToBus = new SparseIntArray();
-    private final SparseArray<CarAudioDeviceInfo> mCarAudioDeviceInfos = new SparseArray<>();
 
     private final AudioPolicy.AudioPolicyVolumeCallback mAudioPolicyVolumeCallback =
             new AudioPolicy.AudioPolicyVolumeCallback() {
@@ -226,6 +155,7 @@
     private final BroadcastReceiver mLegacyVolumeChangedReceiver = new BroadcastReceiver() {
         @Override
         public void onReceive(Context context, Intent intent) {
+            final int zoneId = CarAudioManager.PRIMARY_AUDIO_ZONE;
             switch (intent.getAction()) {
                 case AudioManager.VOLUME_CHANGED_ACTION:
                     int streamType = intent.getIntExtra(AudioManager.EXTRA_VOLUME_STREAM_TYPE, -1);
@@ -233,11 +163,11 @@
                     if (groupId == -1) {
                         Log.w(CarLog.TAG_AUDIO, "Unknown stream type: " + streamType);
                     } else {
-                        callbackGroupVolumeChange(CarAudioManager.PRIMARY_AUDIO_ZONE, groupId, 0);
+                        callbackGroupVolumeChange(zoneId, groupId, 0);
                     }
                     break;
                 case AudioManager.MASTER_MUTE_CHANGED_ACTION:
-                    callbackMasterMuteChange(CarAudioManager.PRIMARY_AUDIO_ZONE, 0);
+                    callbackMasterMuteChange(zoneId, 0);
                     break;
             }
         }
@@ -245,13 +175,15 @@
 
     private AudioPolicy mAudioPolicy;
     private CarAudioFocus mFocusHandler;
-    private CarVolumeGroup[] mCarVolumeGroups;
+    private CarAudioZone[] mCarAudioZones;
 
     public CarAudioService(Context context) {
         mContext = context;
         mTelephonyManager = (TelephonyManager) mContext.getSystemService(Context.TELEPHONY_SERVICE);
         mAudioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
         mUseDynamicRouting = mContext.getResources().getBoolean(R.bool.audioUseDynamicRouting);
+        mUseUnifiedConfiguration = mContext.getResources().getBoolean(
+                R.bool.audioUseUnifiedConfiguration);
         mPersistMasterMuteState = mContext.getResources().getBoolean(
                 R.bool.audioPersistMasterMuteState);
     }
@@ -263,12 +195,32 @@
     @Override
     public void init() {
         synchronized (mImplLock) {
-            if (!mUseDynamicRouting) {
-                Log.i(CarLog.TAG_AUDIO, "Audio dynamic routing not configured, run in legacy mode");
-                setupLegacyVolumeChangedListener();
+            if (mUseDynamicRouting) {
+                // Enumerate all output bus device ports
+                AudioDeviceInfo[] deviceInfos = mAudioManager.getDevices(
+                        AudioManager.GET_DEVICES_OUTPUTS);
+                if (deviceInfos.length == 0) {
+                    Log.e(CarLog.TAG_AUDIO, "No output device available, ignore");
+                    return;
+                }
+                SparseArray<CarAudioDeviceInfo> busToCarAudioDeviceInfo = new SparseArray<>();
+                for (AudioDeviceInfo info : deviceInfos) {
+                    Log.v(CarLog.TAG_AUDIO, String.format("output id=%d address=%s type=%s",
+                            info.getId(), info.getAddress(), info.getType()));
+                    if (info.getType() == AudioDeviceInfo.TYPE_BUS) {
+                        final CarAudioDeviceInfo carInfo = new CarAudioDeviceInfo(info);
+                        // See also the audio_policy_configuration.xml,
+                        // the bus number should be no less than zero.
+                        if (carInfo.getBusNumber() >= 0) {
+                            busToCarAudioDeviceInfo.put(carInfo.getBusNumber(), carInfo);
+                            Log.i(CarLog.TAG_AUDIO, "Valid bus found " + carInfo);
+                        }
+                    }
+                }
+                setupDynamicRouting(busToCarAudioDeviceInfo);
             } else {
-                setupDynamicRouting();
-                setupVolumeGroups();
+                Log.i(CarLog.TAG_AUDIO, "Audio dynamic routing not enabled, run in legacy mode");
+                setupLegacyVolumeChangedListener();
             }
 
             // Restore master mute state if applicable
@@ -302,13 +254,14 @@
     public void dump(PrintWriter writer) {
         writer.println("*CarAudioService*");
         writer.println("\tRun in legacy mode? " + (!mUseDynamicRouting));
+        writer.println("\tUse unified configuration? " + mUseUnifiedConfiguration);
         writer.println("\tPersist master mute state? " + mPersistMasterMuteState);
         writer.println("\tMaster muted? " + mAudioManager.isMasterMute());
         // Empty line for comfortable reading
         writer.println();
         if (mUseDynamicRouting) {
-            for (CarVolumeGroup group : mCarVolumeGroups) {
-                group.dump(writer);
+            for (CarAudioZone zone : mCarAudioZones) {
+                zone.dump("\t", writer);
             }
         }
     }
@@ -324,11 +277,12 @@
             callbackGroupVolumeChange(zoneId, groupId, flags);
             // For legacy stream type based volume control
             if (!mUseDynamicRouting) {
-                mAudioManager.setStreamVolume(STREAM_TYPES[groupId], index, flags);
+                mAudioManager.setStreamVolume(
+                        CarAudioDynamicRouting.STREAM_TYPES[groupId], index, flags);
                 return;
             }
 
-            CarVolumeGroup group = getCarVolumeGroup(groupId);
+            CarVolumeGroup group = getCarVolumeGroup(zoneId, groupId);
             group.setCurrentGainIndex(index);
         }
     }
@@ -382,10 +336,11 @@
 
             // For legacy stream type based volume control
             if (!mUseDynamicRouting) {
-                return mAudioManager.getStreamMaxVolume(STREAM_TYPES[groupId]);
+                return mAudioManager.getStreamMaxVolume(
+                        CarAudioDynamicRouting.STREAM_TYPES[groupId]);
             }
 
-            CarVolumeGroup group = getCarVolumeGroup(groupId);
+            CarVolumeGroup group = getCarVolumeGroup(zoneId, groupId);
             return group.getMaxGainIndex();
         }
     }
@@ -400,10 +355,11 @@
 
             // For legacy stream type based volume control
             if (!mUseDynamicRouting) {
-                return mAudioManager.getStreamMinVolume(STREAM_TYPES[groupId]);
+                return mAudioManager.getStreamMinVolume(
+                        CarAudioDynamicRouting.STREAM_TYPES[groupId]);
             }
 
-            CarVolumeGroup group = getCarVolumeGroup(groupId);
+            CarVolumeGroup group = getCarVolumeGroup(zoneId, groupId);
             return group.getMinGainIndex();
         }
     }
@@ -418,19 +374,20 @@
 
             // For legacy stream type based volume control
             if (!mUseDynamicRouting) {
-                return mAudioManager.getStreamVolume(STREAM_TYPES[groupId]);
+                return mAudioManager.getStreamVolume(
+                        CarAudioDynamicRouting.STREAM_TYPES[groupId]);
             }
 
-            CarVolumeGroup group = getCarVolumeGroup(groupId);
+            CarVolumeGroup group = getCarVolumeGroup(zoneId, groupId);
             return group.getCurrentGainIndex();
         }
     }
 
-    private CarVolumeGroup getCarVolumeGroup(int groupId) {
-        Preconditions.checkNotNull(mCarVolumeGroups);
-        Preconditions.checkArgument(groupId >= 0 && groupId < mCarVolumeGroups.length,
-                "groupId out of range: " + groupId);
-        return mCarVolumeGroups[groupId];
+    private CarVolumeGroup getCarVolumeGroup(int zoneId, int groupId) {
+        Preconditions.checkNotNull(mCarAudioZones);
+        Preconditions.checkArgumentInRange(zoneId, 0, mCarAudioZones.length - 1,
+                "zoneId out of range: " + zoneId);
+        return mCarAudioZones[zoneId].getVolumeGroup(groupId);
     }
 
     private void setupLegacyVolumeChangedListener() {
@@ -440,174 +397,37 @@
         mContext.registerReceiver(mLegacyVolumeChangedReceiver, intentFilter);
     }
 
-    private void setupDynamicRouting() {
-        final IAudioControl audioControl = getAudioControl();
-        if (audioControl == null) {
-            throw new RuntimeException(
-                "Dynamic routing requested but audioControl HAL not available");
-        }
-
-        AudioPolicy audioPolicy = getDynamicAudioPolicy(audioControl);
-        int r = mAudioManager.registerAudioPolicy(audioPolicy);
-        if (r != AudioManager.SUCCESS) {
-            throw new RuntimeException("registerAudioPolicy failed " + r);
-        }
-
-        mAudioPolicy = audioPolicy;
-    }
-
-    private void setupVolumeGroups() {
-        Preconditions.checkArgument(mCarAudioDeviceInfos.size() > 0,
-                "No bus device is configured to setup volume groups");
-        final CarVolumeGroupsHelper helper = new CarVolumeGroupsHelper(
-                mContext, R.xml.car_volume_groups);
-        mCarVolumeGroups = helper.loadVolumeGroups();
-        for (CarVolumeGroup group : mCarVolumeGroups) {
-            for (int contextNumber : group.getContexts()) {
-                int busNumber = mContextToBus.get(contextNumber);
-                group.bind(contextNumber, busNumber, mCarAudioDeviceInfos.get(busNumber));
-            }
-
-            // Now that we have all our contexts, ensure the HAL gets our intial value
-            group.setCurrentGainIndex(group.getCurrentGainIndex());
-
-            Log.v(CarLog.TAG_AUDIO, "Processed volume group: " + group);
-        }
-        // Perform validation after all volume groups are processed
-        if (!validateVolumeGroups()) {
-            throw new RuntimeException("Invalid volume groups configuration");
-        }
-    }
-
-    /**
-     * Constraints applied here:
-     *
-     * - One context should not appear in two groups
-     * - All contexts are assigned
-     * - One bus should not appear in two groups
-     * - All gain controllers in the same group have same step value
-     *
-     * Note that it is fine that there are buses not appear in any group, those buses may be
-     * reserved for other usages.
-     * Step value validation is done in {@link CarVolumeGroup#bind(int, int, CarAudioDeviceInfo)}
-     *
-     * See also the car_volume_groups.xml configuration
-     */
-    private boolean validateVolumeGroups() {
-        Set<Integer> contextSet = new HashSet<>();
-        Set<Integer> busNumberSet = new HashSet<>();
-        for (CarVolumeGroup group : mCarVolumeGroups) {
-            // One context should not appear in two groups
-            for (int context : group.getContexts()) {
-                if (contextSet.contains(context)) {
-                    Log.e(CarLog.TAG_AUDIO, "Context appears in two groups: " + context);
-                    return false;
-                }
-                contextSet.add(context);
-            }
-
-            // One bus should not appear in two groups
-            for (int busNumber : group.getBusNumbers()) {
-                if (busNumberSet.contains(busNumber)) {
-                    Log.e(CarLog.TAG_AUDIO, "Bus appears in two groups: " + busNumber);
-                    return false;
-                }
-                busNumberSet.add(busNumber);
-            }
-        }
-
-        // All contexts are assigned
-        if (contextSet.size() != CONTEXT_NUMBERS.length) {
-            Log.e(CarLog.TAG_AUDIO, "Some contexts are not assigned to group");
-            Log.e(CarLog.TAG_AUDIO, "Assigned contexts "
-                    + Arrays.toString(contextSet.toArray(new Integer[contextSet.size()])));
-            Log.e(CarLog.TAG_AUDIO, "All contexts " + Arrays.toString(CONTEXT_NUMBERS));
-            return false;
-        }
-
-        return true;
-    }
-
-    @Nullable
-    private AudioPolicy getDynamicAudioPolicy(@NonNull IAudioControl audioControl) {
-        AudioPolicy.Builder builder = new AudioPolicy.Builder(mContext);
+    private void setupDynamicRouting(SparseArray<CarAudioDeviceInfo> busToCarAudioDeviceInfo) {
+        final AudioPolicy.Builder builder = new AudioPolicy.Builder(mContext);
         builder.setLooper(Looper.getMainLooper());
 
-        // Enumerate all output bus device ports
-        AudioDeviceInfo[] deviceInfos = mAudioManager.getDevices(AudioManager.GET_DEVICES_OUTPUTS);
-        if (deviceInfos.length == 0) {
-            Log.e(CarLog.TAG_AUDIO, "getDynamicAudioPolicy, no output device available, ignore");
-            return null;
-        }
-        for (AudioDeviceInfo info : deviceInfos) {
-            Log.v(CarLog.TAG_AUDIO, String.format("output id=%d address=%s type=%s",
-                    info.getId(), info.getAddress(), info.getType()));
-            if (info.getType() == AudioDeviceInfo.TYPE_BUS) {
-                final CarAudioDeviceInfo carInfo = new CarAudioDeviceInfo(info);
-                // See also the audio_policy_configuration.xml and getBusForContext in
-                // audio control HAL, the bus number should be no less than zero.
-                if (carInfo.getBusNumber() >= 0) {
-                    mCarAudioDeviceInfos.put(carInfo.getBusNumber(), carInfo);
-                    Log.i(CarLog.TAG_AUDIO, "Valid bus found " + carInfo);
-                }
+        final CarAudioZonesLoader zonesLoader;
+        if (mUseUnifiedConfiguration) {
+            zonesLoader = new CarAudioZonesHelper(mContext, R.xml.car_audio_configuration,
+                    busToCarAudioDeviceInfo);
+        } else {
+            // In legacy mode, context -> bus mapping is done by querying IAudioControl HAL.
+            final IAudioControl audioControl = getAudioControl();
+            if (audioControl == null) {
+                throw new RuntimeException(
+                        "Dynamic routing requested but audioControl HAL not available");
             }
+            zonesLoader = new CarAudioZonesHelperLegacy(mContext, R.xml.car_volume_groups,
+                    busToCarAudioDeviceInfo, audioControl);
+        }
+        mCarAudioZones = zonesLoader.loadAudioZones();
+        for (CarAudioZone zone : mCarAudioZones) {
+            if (!zone.validateVolumeGroups()) {
+                throw new RuntimeException("Invalid volume groups configuration");
+            }
+            // Ensure HAL gets our initial value
+            zone.synchronizeCurrentGainIndex();
+            Log.v(CarLog.TAG_AUDIO, "Processed audio zone: " + zone);
         }
 
-        // Map context to physical bus
-        try {
-            for (int contextNumber : CONTEXT_NUMBERS) {
-                int busNumber = audioControl.getBusForContext(contextNumber);
-                mContextToBus.put(contextNumber, busNumber);
-                CarAudioDeviceInfo info = mCarAudioDeviceInfos.get(busNumber);
-                if (info == null) {
-                    Log.w(CarLog.TAG_AUDIO, "No bus configured for context: " + contextNumber);
-                }
-            }
-        } catch (RemoteException e) {
-            Log.e(CarLog.TAG_AUDIO, "Error mapping context to physical bus", e);
-        }
-
-        // Enumerate all physical buses and build the routing policy.
-        // Note that one can not register audio mix for same bus more than once.
-        for (int i = 0; i < mCarAudioDeviceInfos.size(); i++) {
-            int busNumber = mCarAudioDeviceInfos.keyAt(i);
-            boolean hasContext = false;
-            CarAudioDeviceInfo info = mCarAudioDeviceInfos.valueAt(i);
-            AudioFormat mixFormat = new AudioFormat.Builder()
-                    .setSampleRate(info.getSampleRate())
-                    .setEncoding(info.getEncodingFormat())
-                    .setChannelMask(info.getChannelCount())
-                    .build();
-            AudioMixingRule.Builder mixingRuleBuilder = new AudioMixingRule.Builder();
-            for (int j = 0; j < mContextToBus.size(); j++) {
-                if (mContextToBus.valueAt(j) == busNumber) {
-                    hasContext = true;
-                    int contextNumber = mContextToBus.keyAt(j);
-                    int[] usages = getUsagesForContext(contextNumber);
-                    for (int usage : usages) {
-                        mixingRuleBuilder.addRule(
-                                new AudioAttributes.Builder().setUsage(usage).build(),
-                                AudioMixingRule.RULE_MATCH_ATTRIBUTE_USAGE);
-                    }
-                    Log.i(CarLog.TAG_AUDIO, "Bus number: " + busNumber
-                            + " contextNumber: " + contextNumber
-                            + " sampleRate: " + info.getSampleRate()
-                            + " channels: " + info.getChannelCount()
-                            + " usages: " + Arrays.toString(usages));
-                }
-            }
-            if (hasContext) {
-                // It's a valid case that an audio output bus is defined in
-                // audio_policy_configuration and no context is assigned to it.
-                // In such case, do not build a policy mix with zero rules.
-                AudioMix audioMix = new AudioMix.Builder(mixingRuleBuilder.build())
-                        .setFormat(mixFormat)
-                        .setDevice(info.getAudioDeviceInfo())
-                        .setRouteFlags(AudioMix.ROUTE_FLAG_RENDER)
-                        .build();
-                builder.addMix(audioMix);
-            }
-        }
+        // Setup dynamic routing rules by usage
+        final CarAudioDynamicRouting dynamicRouting = new CarAudioDynamicRouting(mCarAudioZones);
+        dynamicRouting.setupAudioDynamicRouting(builder);
 
         // Attach the {@link AudioPolicyVolumeCallback}
         builder.setAudioPolicyVolumeCallback(mAudioPolicyVolumeCallback);
@@ -621,34 +441,23 @@
             builder.setIsAudioFocusPolicy(true);
         }
 
-        // Instantiate the AudioPolicy
-        final AudioPolicy audioPolicy = builder.build();
-
+        mAudioPolicy = builder.build();
         if (sUseCarAudioFocus) {
             // Connect the AudioPolicy and the focus listener
-            mFocusHandler.setOwningPolicy(this, audioPolicy);
+            mFocusHandler.setOwningPolicy(this, mAudioPolicy);
         }
 
-        // Send the completed AudioPolicy back for use
-        return audioPolicy;
-    }
-
-    // This is public so it can be used by our CarAudioFocus policy implementation, but since
-    // "context" is a HAL level concept, this API should not be visible through the
-    // CarAudioManager interface.
-    // Returns 0 (INVALID) context if an unrecognized audio usage is passed in.
-    public int getContextForUsage(int audioUsage) {
-        return USAGE_TO_CONTEXT.get(audioUsage);
-    }
-
-    private int[] getUsagesForContext(int contextNumber) {
-        final List<Integer> usages = new ArrayList<>();
-        for (int i = 0; i < USAGE_TO_CONTEXT.size(); i++) {
-            if (USAGE_TO_CONTEXT.valueAt(i) == contextNumber) {
-                usages.add(USAGE_TO_CONTEXT.keyAt(i));
-            }
+        int r = mAudioManager.registerAudioPolicy(mAudioPolicy);
+        if (r != AudioManager.SUCCESS) {
+            throw new RuntimeException("registerAudioPolicy failed " + r);
         }
-        return usages.stream().mapToInt(i -> i).toArray();
+    }
+
+    /**
+     * @return Context number for a given audio usage, 0 if the given usage is unrecognized.
+     */
+    int getContextForUsage(int audioUsage) {
+        return CarAudioDynamicRouting.USAGE_TO_CONTEXT.get(audioUsage);
     }
 
     @Override
@@ -721,7 +530,7 @@
                 }
             }
 
-            return sourceAddresses.toArray(new String[sourceAddresses.size()]);
+            return sourceAddresses.toArray(new String[0]);
         }
     }
 
@@ -836,11 +645,12 @@
     public int getVolumeGroupCount(int zoneId) {
         synchronized (mImplLock) {
             enforcePermission(Car.PERMISSION_CAR_CONTROL_AUDIO_VOLUME);
-
             // For legacy stream type based volume control
-            if (!mUseDynamicRouting) return STREAM_TYPES.length;
+            if (!mUseDynamicRouting) return CarAudioDynamicRouting.STREAM_TYPES.length;
 
-            return mCarVolumeGroups == null ? 0 : mCarVolumeGroups.length;
+            Preconditions.checkArgumentInRange(zoneId, 0, mCarAudioZones.length - 1,
+                    "zoneId out of range: " + zoneId);
+            return mCarAudioZones[zoneId].getVolumeGroupCount();
         }
     }
 
@@ -848,13 +658,12 @@
     public int getVolumeGroupIdForUsage(int zoneId, @AudioAttributes.AttributeUsage int usage) {
         synchronized (mImplLock) {
             enforcePermission(Car.PERMISSION_CAR_CONTROL_AUDIO_VOLUME);
+            Preconditions.checkArgumentInRange(zoneId, 0, mCarAudioZones.length - 1,
+                    "zoneId out of range: " + zoneId);
 
-            if (mCarVolumeGroups == null) {
-                return -1;
-            }
-
-            for (int i = 0; i < mCarVolumeGroups.length; i++) {
-                int[] contexts = mCarVolumeGroups[i].getContexts();
+            CarVolumeGroup[] groups = mCarAudioZones[zoneId].getVolumeGroups();
+            for (int i = 0; i < groups.length; i++) {
+                int[] contexts = groups[i].getContexts();
                 for (int context : contexts) {
                     if (getContextForUsage(usage) == context) {
                         return i;
@@ -872,16 +681,16 @@
 
             // For legacy stream type based volume control
             if (!mUseDynamicRouting) {
-                return new int[] { STREAM_TYPE_USAGES[groupId] };
+                return new int[] { CarAudioDynamicRouting.STREAM_TYPE_USAGES[groupId] };
             }
 
-            CarVolumeGroup group = getCarVolumeGroup(groupId);
+            CarVolumeGroup group = getCarVolumeGroup(zoneId, groupId);
             Set<Integer> contexts =
                     Arrays.stream(group.getContexts()).boxed().collect(Collectors.toSet());
             final List<Integer> usages = new ArrayList<>();
-            for (int i = 0; i < USAGE_TO_CONTEXT.size(); i++) {
-                if (contexts.contains(USAGE_TO_CONTEXT.valueAt(i))) {
-                    usages.add(USAGE_TO_CONTEXT.keyAt(i));
+            for (int i = 0; i < CarAudioDynamicRouting.USAGE_TO_CONTEXT.size(); i++) {
+                if (contexts.contains(CarAudioDynamicRouting.USAGE_TO_CONTEXT.valueAt(i))) {
+                    usages.add(CarAudioDynamicRouting.USAGE_TO_CONTEXT.keyAt(i));
                 }
             }
             return usages.stream().mapToInt(i -> i).toArray();
@@ -918,8 +727,10 @@
      * Multiple usages may share one {@link AudioDevicePort}
      */
     private @Nullable AudioDevicePort getAudioPort(@AudioAttributes.AttributeUsage int usage) {
-        final int groupId = getVolumeGroupIdForUsage(CarAudioManager.PRIMARY_AUDIO_ZONE, usage);
-        final CarVolumeGroup group = Preconditions.checkNotNull(mCarVolumeGroups[groupId],
+        int zoneId = CarAudioManager.PRIMARY_AUDIO_ZONE;
+        final int groupId = getVolumeGroupIdForUsage(zoneId, usage);
+        final CarVolumeGroup group = Preconditions.checkNotNull(
+                mCarAudioZones[zoneId].getVolumeGroup(groupId),
                 "Can not find CarVolumeGroup by usage: "
                         + AudioAttributes.usageToString(usage));
         return group.getAudioDevicePortForContext(getContextForUsage(usage));
@@ -945,7 +756,7 @@
                 return playbacks.get(playbacks.size() - 1).getAudioAttributes().getUsage();
             } else {
                 // TODO(b/72695246): Otherwise, get audio usage from foreground activity/window
-                return DEFAULT_AUDIO_USAGE;
+                return CarAudioDynamicRouting.DEFAULT_AUDIO_USAGE;
             }
         }
     }
@@ -957,8 +768,8 @@
      */
     private int getVolumeGroupIdForStreamType(int streamType) {
         int groupId = -1;
-        for (int i = 0; i < STREAM_TYPES.length; i++) {
-            if (streamType == STREAM_TYPES[i]) {
+        for (int i = 0; i < CarAudioDynamicRouting.STREAM_TYPES.length; i++) {
+            if (streamType == CarAudioDynamicRouting.STREAM_TYPES[i]) {
                 groupId = i;
                 break;
             }
@@ -977,4 +788,8 @@
         }
         return null;
     }
+
+    interface CarAudioZonesLoader {
+        CarAudioZone[] loadAudioZones();
+    }
 }
diff --git a/service/src/com/android/car/audio/CarAudioZone.java b/service/src/com/android/car/audio/CarAudioZone.java
new file mode 100644
index 0000000..b827906
--- /dev/null
+++ b/service/src/com/android/car/audio/CarAudioZone.java
@@ -0,0 +1,146 @@
+/*
+ * Copyright (C) 2018 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.audio;
+
+import android.car.media.CarAudioManager;
+import android.util.Log;
+
+import com.android.car.CarLog;
+import com.android.internal.util.Preconditions;
+
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * A class encapsulates an audio zone in car.
+ *
+ * An audio zone can contain multiple {@link CarVolumeGroup}s, and each zone has its own
+ * {@link CarAudioFocus} instance. Additionally, there may be dedicated hardware volume keys
+ * attached to each zone.
+ *
+ * See also the unified car_audio_configuration.xml
+ */
+/* package */ class CarAudioZone {
+
+    private final int mId;
+    private final String mName;
+    private final List<CarVolumeGroup> mVolumeGroups;
+
+    CarAudioZone(int id, String name) {
+        mId = id;
+        mName = name;
+        mVolumeGroups = new ArrayList<>();
+    }
+
+    int getId() {
+        return mId;
+    }
+
+    String getName() {
+        return mName;
+    }
+
+    boolean isPrimaryZone() {
+        return mId == CarAudioManager.PRIMARY_AUDIO_ZONE;
+    }
+
+    void addVolumeGroup(CarVolumeGroup volumeGroup) {
+        mVolumeGroups.add(volumeGroup);
+    }
+
+    CarVolumeGroup getVolumeGroup(int groupId) {
+        Preconditions.checkArgumentInRange(groupId, 0, mVolumeGroups.size() - 1,
+                "groupId(" + groupId + ") is out of range");
+        return mVolumeGroups.get(groupId);
+    }
+
+    int getVolumeGroupCount() {
+        return mVolumeGroups.size();
+    }
+
+    /**
+     * @return Snapshot of available {@link CarVolumeGroup}s in array.
+     */
+    CarVolumeGroup[] getVolumeGroups() {
+        return mVolumeGroups.toArray(new CarVolumeGroup[0]);
+    }
+
+    /**
+     * Constraints applied here:
+     *
+     * - One context should not appear in two groups
+     * - All contexts are assigned
+     * - One bus should not appear in two groups
+     * - All gain controllers in the same group have same step value
+     *
+     * Note that it is fine that there are buses not appear in any group, those buses may be
+     * reserved for other usages.
+     * Step value validation is done in {@link CarVolumeGroup#bind(int, int, CarAudioDeviceInfo)}
+     */
+    boolean validateVolumeGroups() {
+        Set<Integer> contextSet = new HashSet<>();
+        Set<Integer> busNumberSet = new HashSet<>();
+        for (CarVolumeGroup group : mVolumeGroups) {
+            // One context should not appear in two groups
+            for (int context : group.getContexts()) {
+                if (contextSet.contains(context)) {
+                    Log.e(CarLog.TAG_AUDIO, "Context appears in two groups: " + context);
+                    return false;
+                }
+                contextSet.add(context);
+            }
+
+            // One bus should not appear in two groups
+            for (int busNumber : group.getBusNumbers()) {
+                if (busNumberSet.contains(busNumber)) {
+                    Log.e(CarLog.TAG_AUDIO, "Bus appears in two groups: " + busNumber);
+                    return false;
+                }
+                busNumberSet.add(busNumber);
+            }
+        }
+
+        // All contexts are assigned
+        if (contextSet.size() != CarAudioDynamicRouting.CONTEXT_NUMBERS.length) {
+            Log.e(CarLog.TAG_AUDIO, "Some contexts are not assigned to group");
+            Log.e(CarLog.TAG_AUDIO, "Assigned contexts "
+                    + Arrays.toString(contextSet.toArray(new Integer[0])));
+            Log.e(CarLog.TAG_AUDIO,
+                    "All contexts " + Arrays.toString(CarAudioDynamicRouting.CONTEXT_NUMBERS));
+            return false;
+        }
+
+        return true;
+    }
+
+    void synchronizeCurrentGainIndex() {
+        for (CarVolumeGroup group : mVolumeGroups) {
+            group.setCurrentGainIndex(group.getCurrentGainIndex());
+        }
+    }
+
+    void dump(String indent, PrintWriter writer) {
+        writer.printf("%sCarAudioZone(%s:%d) isPrimary? %b\n", indent, mName, mId, isPrimaryZone());
+        for (CarVolumeGroup group : mVolumeGroups) {
+            group.dump(indent + "\t", writer);
+        }
+        writer.println();
+    }
+}
diff --git a/service/src/com/android/car/audio/CarAudioZonesHelper.java b/service/src/com/android/car/audio/CarAudioZonesHelper.java
new file mode 100644
index 0000000..ba7b93c
--- /dev/null
+++ b/service/src/com/android/car/audio/CarAudioZonesHelper.java
@@ -0,0 +1,202 @@
+/*
+ * Copyright (C) 2018 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.audio;
+
+import android.annotation.NonNull;
+import android.annotation.XmlRes;
+import android.car.media.CarAudioManager;
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.content.res.XmlResourceParser;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.util.SparseArray;
+import android.util.Xml;
+
+import com.android.car.CarLog;
+import com.android.car.R;
+
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * A helper class loads all audio zones from the configuration XML file.
+ */
+/* package */ class CarAudioZonesHelper implements CarAudioService.CarAudioZonesLoader {
+
+    private static final String TAG_ROOT = "carAudioConfiguration";
+    private static final String TAG_AUDIO_ZONES = "zones";
+    private static final String TAG_AUDIO_ZONE = "zone";
+    private static final String TAG_VOLUME_GROUPS = "volumeGroups";
+    private static final String TAG_VOLUME_GROUP = "group";
+    private static final String TAG_AUDIO_DEVICE = "device";
+    private static final String TAG_CONTEXT = "context";
+    private static final int SUPPORTED_VERSION = 1;
+
+    private final Context mContext;
+    private final int mXmlConfiguration;
+    private final SparseArray<CarAudioDeviceInfo> mBusToCarAudioDeviceInfo;
+
+    private int mNextSecondaryZoneId;
+
+    CarAudioZonesHelper(Context context, @XmlRes int xmlConfiguration,
+            @NonNull SparseArray<CarAudioDeviceInfo> busToCarAudioDeviceInfo) {
+        mContext = context;
+        mXmlConfiguration = xmlConfiguration;
+        mBusToCarAudioDeviceInfo = busToCarAudioDeviceInfo;
+
+        mNextSecondaryZoneId = CarAudioManager.PRIMARY_AUDIO_ZONE + 1;
+    }
+
+    @Override
+    public CarAudioZone[] loadAudioZones() {
+        List<CarAudioZone> carAudioZones = new ArrayList<>();
+        try (XmlResourceParser parser = mContext.getResources().getXml(mXmlConfiguration)) {
+            AttributeSet attrs = Xml.asAttributeSet(parser);
+            int type;
+            // Traverse to the first start tag, <carAudioConfiguration> in this case
+            while ((type = parser.next()) != XmlResourceParser.END_DOCUMENT
+                    && type != XmlResourceParser.START_TAG) {
+                // ignored
+            }
+            if (!TAG_ROOT.equals(parser.getName())) {
+                throw new RuntimeException("Meta-data does not start with " + TAG_ROOT);
+            }
+
+            // Version check
+            TypedArray c = mContext.getResources().obtainAttributes(
+                    attrs, R.styleable.carAudioConfiguration);
+            final int versionNumber = c.getInt(R.styleable.carAudioConfiguration_version, -1);
+            if (versionNumber != SUPPORTED_VERSION) {
+                throw new RuntimeException("Support version:"
+                        + SUPPORTED_VERSION + " only, got version:" + versionNumber);
+            }
+            c.recycle();
+
+            // And follows with the <zones> tag
+            while ((type = parser.next()) != XmlResourceParser.END_DOCUMENT
+                    && type != XmlResourceParser.START_TAG) {
+                // ignored
+            }
+            if (!TAG_AUDIO_ZONES.equals(parser.getName())) {
+                throw new RuntimeException("Configuration should begin with a <zones> tag");
+            }
+            int outerDepth = parser.getDepth();
+            while ((type = parser.next()) != XmlResourceParser.END_DOCUMENT
+                    && (type != XmlResourceParser.END_TAG || parser.getDepth() > outerDepth)) {
+                if (type == XmlResourceParser.END_TAG) {
+                    continue;
+                }
+                if (TAG_AUDIO_ZONE.equals(parser.getName())) {
+                    carAudioZones.add(parseAudioZone(attrs, parser));
+                }
+            }
+        } catch (Exception e) {
+            Log.e(CarLog.TAG_AUDIO, "Error parsing unified car audio configuration", e);
+
+        }
+        return carAudioZones.toArray(new CarAudioZone[0]);
+    }
+
+    private CarAudioZone parseAudioZone(AttributeSet attrs, XmlResourceParser parser)
+            throws XmlPullParserException, IOException {
+        TypedArray c = mContext.getResources().obtainAttributes(
+                attrs, R.styleable.carAudioConfiguration);
+        final boolean isPrimary = c.getBoolean(R.styleable.carAudioConfiguration_isPrimary, false);
+        final String zoneName = c.getString(R.styleable.carAudioConfiguration_name);
+        c.recycle();
+
+        CarAudioZone zone = new CarAudioZone(
+                isPrimary ? CarAudioManager.PRIMARY_AUDIO_ZONE : getNextSecondaryZoneId(),
+                zoneName);
+        int type;
+        // Traverse to the first start tag, <volumeGroups> in this case
+        while ((type = parser.next()) != XmlResourceParser.END_DOCUMENT
+                && type != XmlResourceParser.START_TAG) {
+            // ignored
+        }
+
+        if (!TAG_VOLUME_GROUPS.equals(parser.getName())) {
+            throw new RuntimeException("Audio zone does not start with <volumeGroups> tag");
+        }
+        int outerDepth = parser.getDepth();
+        int groupId = 0;
+        while ((type = parser.next()) != XmlResourceParser.END_DOCUMENT
+                && (type != XmlResourceParser.END_TAG || parser.getDepth() > outerDepth)) {
+            if (type == XmlResourceParser.END_TAG) {
+                continue;
+            }
+            if (TAG_VOLUME_GROUP.equals(parser.getName())) {
+                zone.addVolumeGroup(parseVolumeGroup(zone.getId(), groupId, attrs, parser));
+                groupId += 1;
+            }
+        }
+        return zone;
+    }
+
+    private CarVolumeGroup parseVolumeGroup(
+            int zoneId, int groupId, AttributeSet attrs, XmlResourceParser parser)
+            throws XmlPullParserException, IOException {
+        final CarVolumeGroup group = new CarVolumeGroup(mContext, zoneId, groupId);
+        int type;
+        int outerDepth = parser.getDepth();
+        while ((type = parser.next()) != XmlResourceParser.END_DOCUMENT
+                && (type != XmlResourceParser.END_TAG || parser.getDepth() > outerDepth)) {
+            if (type == XmlResourceParser.END_TAG) {
+                continue;
+            }
+            if (TAG_AUDIO_DEVICE.equals(parser.getName())) {
+                TypedArray c = mContext.getResources().obtainAttributes(
+                        attrs, R.styleable.carAudioConfiguration);
+                final String address = c.getString(R.styleable.carAudioConfiguration_address);
+                parseVolumeGroupContexts(group,
+                        CarAudioDeviceInfo.parseDeviceAddress(address), attrs, parser);
+                c.recycle();
+            }
+        }
+        return group;
+    }
+
+    private void parseVolumeGroupContexts(
+            CarVolumeGroup group, int busNumber, AttributeSet attrs, XmlResourceParser parser)
+            throws XmlPullParserException, IOException {
+        int type;
+        int innerDepth = parser.getDepth();
+        while ((type = parser.next()) != XmlResourceParser.END_DOCUMENT
+                && (type != XmlResourceParser.END_TAG || parser.getDepth() > innerDepth)) {
+            if (type == XmlResourceParser.END_TAG) {
+                continue;
+            }
+            if (TAG_CONTEXT.equals(parser.getName())) {
+                TypedArray c = mContext.getResources().obtainAttributes(
+                        attrs, R.styleable.volumeGroups_context);
+                final int contextNumber = c.getInt(
+                        R.styleable.volumeGroups_context_context, -1);
+                c.recycle();
+                group.bind(contextNumber, busNumber, mBusToCarAudioDeviceInfo.get(busNumber));
+            }
+        }
+    }
+
+    private int getNextSecondaryZoneId() {
+        int zoneId = mNextSecondaryZoneId;
+        mNextSecondaryZoneId += 1;
+        return zoneId;
+    }
+}
diff --git a/service/src/com/android/car/audio/CarVolumeGroupsHelper.java b/service/src/com/android/car/audio/CarAudioZonesHelperLegacy.java
similarity index 63%
rename from service/src/com/android/car/audio/CarVolumeGroupsHelper.java
rename to service/src/com/android/car/audio/CarAudioZonesHelperLegacy.java
index eddcd4f..fb5481d 100644
--- a/service/src/com/android/car/audio/CarVolumeGroupsHelper.java
+++ b/service/src/com/android/car/audio/CarAudioZonesHelperLegacy.java
@@ -15,12 +15,18 @@
  */
 package com.android.car.audio;
 
+import android.annotation.NonNull;
 import android.annotation.XmlRes;
+import android.car.media.CarAudioManager;
 import android.content.Context;
 import android.content.res.TypedArray;
 import android.content.res.XmlResourceParser;
+import android.hardware.automotive.audiocontrol.V1_0.IAudioControl;
+import android.os.RemoteException;
 import android.util.AttributeSet;
 import android.util.Log;
+import android.util.SparseArray;
+import android.util.SparseIntArray;
 import android.util.Xml;
 
 import com.android.car.CarLog;
@@ -33,9 +39,12 @@
 import java.util.List;
 
 /**
- * A helper class loads all volume groups from the configuration XML file.
+ * A helper class loads volume groups from car_volume_groups.xml configuration into one zone.
+ *
+ * @deprecated This is replaced by {@link CarAudioZonesHelper}.
  */
-/* package */ class CarVolumeGroupsHelper {
+@Deprecated
+/* package */ class CarAudioZonesHelperLegacy implements CarAudioService.CarAudioZonesLoader {
 
     private static final String TAG_VOLUME_GROUPS = "volumeGroups";
     private static final String TAG_GROUP = "group";
@@ -43,16 +52,47 @@
 
     private final Context mContext;
     private final @XmlRes int mXmlConfiguration;
+    private final SparseIntArray mContextToBus;
+    private final SparseArray<CarAudioDeviceInfo> mBusToCarAudioDeviceInfo;
 
-    CarVolumeGroupsHelper(Context context, @XmlRes int xmlConfiguration) {
+    CarAudioZonesHelperLegacy(Context context, @XmlRes int xmlConfiguration,
+            @NonNull SparseArray<CarAudioDeviceInfo> busToCarAudioDeviceInfo,
+            @NonNull IAudioControl audioControl) {
         mContext = context;
         mXmlConfiguration = xmlConfiguration;
+        mBusToCarAudioDeviceInfo = busToCarAudioDeviceInfo;
+
+        // Initialize context => bus mapping once.
+        mContextToBus = new SparseIntArray();
+        try {
+            for (int contextNumber : CarAudioDynamicRouting.CONTEXT_NUMBERS) {
+                mContextToBus.put(contextNumber, audioControl.getBusForContext(contextNumber));
+            }
+        } catch (RemoteException e) {
+            Log.e(CarLog.TAG_AUDIO, "Failed to query IAudioControl HAL", e);
+            e.rethrowAsRuntimeException();
+        }
+    }
+
+    @Override
+    public CarAudioZone[] loadAudioZones() {
+        final CarAudioZone zone = new CarAudioZone(CarAudioManager.PRIMARY_AUDIO_ZONE,
+                "Primary zone");
+        for (CarVolumeGroup group : loadVolumeGroups()) {
+            zone.addVolumeGroup(group);
+            // Binding audio device to volume group.
+            for (int contextNumber : group.getContexts()) {
+                int busNumber = mContextToBus.get(contextNumber);
+                group.bind(contextNumber, busNumber, mBusToCarAudioDeviceInfo.get(busNumber));
+            }
+        }
+        return new CarAudioZone[] { zone };
     }
 
     /**
      * @return all {@link CarVolumeGroup} read from configuration.
      */
-    CarVolumeGroup[] loadVolumeGroups() {
+    private List<CarVolumeGroup> loadVolumeGroups() {
         List<CarVolumeGroup> carVolumeGroups = new ArrayList<>();
         try (XmlResourceParser parser = mContext.getResources().getXml(mXmlConfiguration)) {
             AttributeSet attrs = Xml.asAttributeSet(parser);
@@ -81,14 +121,13 @@
         } catch (Exception e) {
             Log.e(CarLog.TAG_AUDIO, "Error parsing volume groups configuration", e);
         }
-        return carVolumeGroups.toArray(new CarVolumeGroup[carVolumeGroups.size()]);
+        return carVolumeGroups;
     }
 
     private CarVolumeGroup parseVolumeGroup(int id, AttributeSet attrs, XmlResourceParser parser)
             throws XmlPullParserException, IOException {
-        int type;
-
         List<Integer> contexts = new ArrayList<>();
+        int type;
         int innerDepth = parser.getDepth();
         while ((type = parser.next()) != XmlResourceParser.END_DOCUMENT
                 && (type != XmlResourceParser.END_TAG || parser.getDepth() > innerDepth)) {
@@ -103,7 +142,7 @@
             }
         }
 
-        return new CarVolumeGroup(mContext, id,
+        return new CarVolumeGroup(mContext, CarAudioManager.PRIMARY_AUDIO_ZONE, id,
                 contexts.stream().mapToInt(i -> i).filter(i -> i >= 0).toArray());
     }
 }
diff --git a/service/src/com/android/car/audio/CarVolumeGroup.java b/service/src/com/android/car/audio/CarVolumeGroup.java
index 5cda3d2..eb3cc4f 100644
--- a/service/src/com/android/car/audio/CarVolumeGroup.java
+++ b/service/src/com/android/car/audio/CarVolumeGroup.java
@@ -29,7 +29,9 @@
 import com.android.internal.util.Preconditions;
 
 import java.io.PrintWriter;
+import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.List;
 
 /**
  * A class encapsulates a volume group in car.
@@ -41,10 +43,10 @@
 /* package */ final class CarVolumeGroup {
 
     private final ContentResolver mContentResolver;
+    private final int mZoneId;
     private final int mId;
-    private final int[] mContexts;
     private final SparseIntArray mContextToBus = new SparseIntArray();
-    private final SparseArray<CarAudioDeviceInfo> mBusToCarAudioDeviceInfos = new SparseArray<>();
+    private final SparseArray<CarAudioDeviceInfo> mBusToCarAudioDeviceInfo = new SparseArray<>();
 
     private int mDefaultGain = Integer.MIN_VALUE;
     private int mMaxGain = Integer.MIN_VALUE;
@@ -53,30 +55,78 @@
     private int mStoredGainIndex;
     private int mCurrentGainIndex = -1;
 
-    CarVolumeGroup(Context context, int id, @NonNull int[] contexts) {
+    /**
+     * Constructs a {@link CarVolumeGroup} instance
+     * @param context {@link Context} instance
+     * @param zoneId Audio zone this volume group belongs to
+     * @param id ID of this volume group
+     */
+    CarVolumeGroup(Context context, int zoneId, int id) {
         mContentResolver = context.getContentResolver();
+        mZoneId = zoneId;
         mId = id;
-        mContexts = contexts;
-
         mStoredGainIndex = Settings.Global.getInt(mContentResolver,
-                CarAudioService.getVolumeSettingsKeyForGroup(mId), -1);
+                CarAudioService.getVolumeSettingsKeyForGroup(mZoneId, mId), -1);
     }
 
-    int getId() {
-        return mId;
+    /**
+     * Constructs a {@link CarVolumeGroup} instance
+     * @param context {@link Context} instance
+     * @param zoneId Audio zone this volume group belongs to
+     * @param id ID of this volume group
+     * @param contexts Pre-populated array of car contexts, for legacy car_volume_groups.xml only
+     * @deprecated In favor of {@link #CarVolumeGroup(Context, int, int)}
+     */
+    @Deprecated
+    CarVolumeGroup(Context context, int zoneId, int id, @NonNull int[] contexts) {
+        this(context, zoneId, id);
+        // Deal with the pre-populated car audio contexts
+        for (int audioContext : contexts) {
+            mContextToBus.put(audioContext, -1);
+        }
     }
 
+    /**
+     * @param busNumber Physical bus number for the audio device port
+     * @return {@link CarAudioDeviceInfo} associated with a given bus number
+     */
+    CarAudioDeviceInfo getCarAudioDeviceInfoForBus(int busNumber) {
+        return mBusToCarAudioDeviceInfo.get(busNumber);
+    }
+
+    /**
+     * @return Array of context numbers in this {@link CarVolumeGroup}
+     */
     int[] getContexts() {
-        return mContexts;
+        final int[] contextNumbers = new int[mContextToBus.size()];
+        for (int i = 0; i < contextNumbers.length; i++) {
+            contextNumbers[i] = mContextToBus.keyAt(i);
+        }
+        return contextNumbers;
+    }
+
+    /**
+     * @param busNumber Physical bus number for the audio device port
+     * @return Array of context numbers assigned to a given bus number
+     */
+    int[] getContextsForBus(int busNumber) {
+        List<Integer> contextNumbers = new ArrayList<>();
+        for (int i = 0; i < mContextToBus.size(); i++) {
+            int value = mContextToBus.valueAt(i);
+            if (value == busNumber) {
+                contextNumbers.add(mContextToBus.keyAt(i));
+            }
+        }
+        return contextNumbers.stream().mapToInt(i -> i).toArray();
     }
 
     /**
      * @return Array of bus numbers in this {@link CarVolumeGroup}
      */
     int[] getBusNumbers() {
-        final int[] busNumbers = new int[mBusToCarAudioDeviceInfos.size()];
+        final int[] busNumbers = new int[mBusToCarAudioDeviceInfo.size()];
         for (int i = 0; i < busNumbers.length; i++) {
-            busNumbers[i] = mBusToCarAudioDeviceInfos.keyAt(i);
+            busNumbers[i] = mBusToCarAudioDeviceInfo.keyAt(i);
         }
         return busNumbers;
     }
@@ -92,7 +142,7 @@
      * @param info {@link CarAudioDeviceInfo} instance relates to the physical bus
      */
     void bind(int contextNumber, int busNumber, CarAudioDeviceInfo info) {
-        if (mBusToCarAudioDeviceInfos.size() == 0) {
+        if (mBusToCarAudioDeviceInfo.size() == 0) {
             mStepSize = info.getAudioGain().stepValue();
         } else {
             Preconditions.checkArgument(
@@ -101,7 +151,7 @@
         }
 
         mContextToBus.put(contextNumber, busNumber);
-        mBusToCarAudioDeviceInfos.put(busNumber, info);
+        mBusToCarAudioDeviceInfo.put(busNumber, info);
 
         if (info.getDefaultGain() > mDefaultGain) {
             // We're arbitrarily selecting the highest bus default gain as the group's default.
@@ -156,14 +206,14 @@
                         + gainInMillibels + "index "
                         + gainIndex);
 
-        for (int i = 0; i < mBusToCarAudioDeviceInfos.size(); i++) {
-            CarAudioDeviceInfo info = mBusToCarAudioDeviceInfos.valueAt(i);
+        for (int i = 0; i < mBusToCarAudioDeviceInfo.size(); i++) {
+            CarAudioDeviceInfo info = mBusToCarAudioDeviceInfo.valueAt(i);
             info.setCurrentGain(gainInMillibels);
         }
 
         mCurrentGainIndex = gainIndex;
         Settings.Global.putInt(mContentResolver,
-                CarAudioService.getVolumeSettingsKeyForGroup(mId), gainIndex);
+                CarAudioService.getVolumeSettingsKeyForGroup(mZoneId, mId), gainIndex);
     }
 
     // Given a group level gain index, return the computed gain in millibells
@@ -186,33 +236,35 @@
     @Nullable
     AudioDevicePort getAudioDevicePortForContext(int contextNumber) {
         final int busNumber = mContextToBus.get(contextNumber, -1);
-        if (busNumber < 0 || mBusToCarAudioDeviceInfos.get(busNumber) == null) {
+        if (busNumber < 0 || mBusToCarAudioDeviceInfo.get(busNumber) == null) {
             return null;
         }
-        return mBusToCarAudioDeviceInfos.get(busNumber).getAudioDevicePort();
+        return mBusToCarAudioDeviceInfo.get(busNumber).getAudioDevicePort();
     }
 
     @Override
     public String toString() {
         return "CarVolumeGroup id: " + mId
                 + " currentGainIndex: " + mCurrentGainIndex
-                + " contexts: " + Arrays.toString(mContexts)
+                + " contexts: " + Arrays.toString(getContexts())
                 + " buses: " + Arrays.toString(getBusNumbers());
     }
 
     /** Writes to dumpsys output */
-    void dump(PrintWriter writer) {
-        writer.println("CarVolumeGroup " + mId);
-        writer.printf("\tGain in millibel (min / max / default/ current): %d %d %d %d\n",
-                mMinGain, mMaxGain, mDefaultGain, getGainForIndex(mCurrentGainIndex));
-        writer.printf("\tGain in index (min / max / default / current): %d %d %d %d\n",
-                getMinGainIndex(), getMaxGainIndex(), getDefaultGainIndex(), mCurrentGainIndex);
+    void dump(String indent, PrintWriter writer) {
+        writer.printf("%sCarVolumeGroup(%d)\n", indent, mId);
+        writer.printf("%sGain values (min / max / default/ current): %d %d %d %d\n",
+                indent, mMinGain, mMaxGain,
+                mDefaultGain, getGainForIndex(mCurrentGainIndex));
+        writer.printf("%sGain indexes (min / max / default / current): %d %d %d %d\n",
+                indent, getMinGainIndex(), getMaxGainIndex(),
+                getDefaultGainIndex(), mCurrentGainIndex);
         for (int i = 0; i < mContextToBus.size(); i++) {
-            writer.printf("\tContext: %s -> Bus: %d\n",
+            writer.printf("%sContext: %s -> Bus: %d\n", indent,
                     ContextNumber.toString(mContextToBus.keyAt(i)), mContextToBus.valueAt(i));
         }
-        for (int i = 0; i < mBusToCarAudioDeviceInfos.size(); i++) {
-            mBusToCarAudioDeviceInfos.valueAt(i).dump(writer);
+        for (int i = 0; i < mBusToCarAudioDeviceInfo.size(); i++) {
+            mBusToCarAudioDeviceInfo.valueAt(i).dump(indent, writer);
         }
         // Empty line for comfortable reading
         writer.println();
diff --git a/tests/EmbeddedKitchenSinkApp/Android.mk b/tests/EmbeddedKitchenSinkApp/Android.mk
index e6f457e..db59ddf 100644
--- a/tests/EmbeddedKitchenSinkApp/Android.mk
+++ b/tests/EmbeddedKitchenSinkApp/Android.mk
@@ -43,7 +43,6 @@
 LOCAL_STATIC_ANDROID_LIBRARIES += \
     car-service-lib-for-test \
     car-apps-common \
-    androidx.car_car \
     androidx.car_car-cluster
 
 LOCAL_STATIC_JAVA_LIBRARIES += \
diff --git a/tests/EmbeddedKitchenSinkApp/res/drawable/ic_check_box_checked.xml b/tests/EmbeddedKitchenSinkApp/res/drawable/ic_check_box_checked.xml
index d64d4fc..0ef8e43 100644
--- a/tests/EmbeddedKitchenSinkApp/res/drawable/ic_check_box_checked.xml
+++ b/tests/EmbeddedKitchenSinkApp/res/drawable/ic_check_box_checked.xml
@@ -22,5 +22,5 @@
         android:viewportHeight="24.0">
     <path
         android:pathData="M19,3L5,3c-1.11,0 -2,0.9 -2,2v14c0,1.1 0.89,2 2,2h14c1.11,0 2,-0.9 2,-2L21,5c0,-1.1 -0.89,-2 -2,-2zM10,17l-5,-5 1.41,-1.41L10,14.17l7.59,-7.59L19,8l-9,9z"
-        android:fillColor="#000000"/>
+        android:fillColor="#FFFFFF"/>
 </vector>
diff --git a/tests/EmbeddedKitchenSinkApp/res/drawable/ic_check_box_unchecked.xml b/tests/EmbeddedKitchenSinkApp/res/drawable/ic_check_box_unchecked.xml
index 96246a3..b05cb48 100644
--- a/tests/EmbeddedKitchenSinkApp/res/drawable/ic_check_box_unchecked.xml
+++ b/tests/EmbeddedKitchenSinkApp/res/drawable/ic_check_box_unchecked.xml
@@ -22,5 +22,5 @@
         android:viewportHeight="24.0">
     <path
         android:pathData="M19,5v14H5V5h14m0,-2H5c-1.1,0 -2,0.9 -2,2v14c0,1.1 0.9,2 2,2h14c1.1,0 2,-0.9 2,-2V5c0,-1.1 -0.9,-2 -2,-2z"
-        android:fillColor="#000000"/>
+        android:fillColor="#FFFFFF"/>
 </vector>
diff --git a/tests/EmbeddedKitchenSinkApp/res/drawable/ic_voice_assistant_mic.xml b/tests/EmbeddedKitchenSinkApp/res/drawable/ic_voice_assistant_mic.xml
index 0468870..2171286 100644
--- a/tests/EmbeddedKitchenSinkApp/res/drawable/ic_voice_assistant_mic.xml
+++ b/tests/EmbeddedKitchenSinkApp/res/drawable/ic_voice_assistant_mic.xml
@@ -21,7 +21,7 @@
  android:viewportWidth="24"
  android:viewportHeight="24">
  <path
-  android:fillColor="#000000"
+  android:fillColor="#FFFFFF"
   android:pathData="M12 14c1.66 0 2.99-1.34 2.99-3L15 5c0-1.66-1.34-3-3-3S9 3.34 9 5v6c0 1.66 1.34 3
 3 3zm5.3-3c0 3-2.54 5.1-5.3 5.1S6.7 14 6.7 11H5c0 3.41 2.72 6.23 6
 6.72V21h2v-3.28c3.28-.48 6-3.3 6-6.72h-1.7z" />
diff --git a/tests/EmbeddedKitchenSinkApp/res/layout/kitchen_content.xml b/tests/EmbeddedKitchenSinkApp/res/layout/kitchen_content.xml
index 45b38dc..2ebc1c6 100644
--- a/tests/EmbeddedKitchenSinkApp/res/layout/kitchen_content.xml
+++ b/tests/EmbeddedKitchenSinkApp/res/layout/kitchen_content.xml
@@ -17,7 +17,6 @@
 <!-- We use this container to place kitchen app fragments. It insets the fragment contents -->
 <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
     android:id="@+id/kitchen_content"
-    android:background="#A8A9AA"
     android:layout_width="match_parent"
     android:layout_height="match_parent"
     android:paddingStart="56dp"
diff --git a/tests/EmbeddedKitchenSinkApp/res/layout/notification_fragment.xml b/tests/EmbeddedKitchenSinkApp/res/layout/notification_fragment.xml
index b9b8e8b..1dc40f6 100644
--- a/tests/EmbeddedKitchenSinkApp/res/layout/notification_fragment.xml
+++ b/tests/EmbeddedKitchenSinkApp/res/layout/notification_fragment.xml
@@ -65,4 +65,21 @@
         android:layout_height="wrap_content"
         android:text="Category: CATEGORY_MESSAGE"
         android:textSize="35sp"/>
+    <LinearLayout
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:orientation="horizontal" >
+         <Button
+        android:id="@+id/category_car_emerg_button"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:text="Category: CATEGORY_EMERG"
+        android:textSize="35sp"/>
+         <Button
+        android:id="@+id/category_car_warning_button"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:text="Category: CATEGORY_WARN"
+        android:textSize="35sp"/>
+    </LinearLayout>
 </LinearLayout>
diff --git a/tests/EmbeddedKitchenSinkApp/res/layout/storagewear.xml b/tests/EmbeddedKitchenSinkApp/res/layout/storagewear.xml
index cf98695..52a7763 100644
--- a/tests/EmbeddedKitchenSinkApp/res/layout/storagewear.xml
+++ b/tests/EmbeddedKitchenSinkApp/res/layout/storagewear.xml
@@ -23,7 +23,6 @@
         android:layout_height="wrap_content"
         android:text="Storage Wear Information"
         android:textSize="24dp"
-        android:textColor="#ff0000"
         android:minLines="5"/>
     <ListView
         android:id="@+id/storage_events_list"
@@ -58,7 +57,6 @@
         android:layout_height="wrap_content"
         android:text="Free Disk Space: 1 byte"
         android:textSize="24dp"
-        android:textColor="#ff0000"
         android:minLines="4"/>
     <ScrollView
         android:id="@+id/scroll_view"
@@ -72,7 +70,6 @@
             android:layout_height="fill_parent"
             android:text="No I/O activity on record"
             android:textSize="20dp"
-            android:textColor="#ff0000"
             android:minLines="10"/>
     </ScrollView>
 </LinearLayout>
diff --git a/tests/EmbeddedKitchenSinkApp/res/values/colors.xml b/tests/EmbeddedKitchenSinkApp/res/values/colors.xml
deleted file mode 100644
index 9a1ed89..0000000
--- a/tests/EmbeddedKitchenSinkApp/res/values/colors.xml
+++ /dev/null
@@ -1,20 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- * Copyright (c) 2017, 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="car_button_tint">#fffafafa</color>
-</resources>
\ No newline at end of file
diff --git a/tests/EmbeddedKitchenSinkApp/res/values/styles.xml b/tests/EmbeddedKitchenSinkApp/res/values/styles.xml
index f16d19d..1dcaa55 100644
--- a/tests/EmbeddedKitchenSinkApp/res/values/styles.xml
+++ b/tests/EmbeddedKitchenSinkApp/res/values/styles.xml
@@ -30,13 +30,10 @@
         <item name="android:layout_width">@dimen/overview_icon_size</item>
         <item name="android:layout_height">@dimen/overview_icon_size</item>
         <item name="android:padding">6dp</item>
-        <item name="android:background">?android:attr/selectableItemBackgroundBorderless</item>
-        <item name="android:tint">@color/car_button_tint</item>
         <item name="android:scaleType">fitCenter</item>
         <item name="android:clickable">true</item>
     </style>
 
-    <style name="KitchenSinkActivityTheme" parent="Theme.Car.Light.NoActionBar.Drawer">
-        <item name="android:colorPrimary">@android:color/transparent</item>
+    <style name="KitchenSinkActivityTheme" parent="Theme.NoActionBar.Drawer">
     </style>
 </resources>
diff --git a/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/KitchenSinkActivity.java b/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/KitchenSinkActivity.java
index eb425bd..acc651d 100644
--- a/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/KitchenSinkActivity.java
+++ b/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/KitchenSinkActivity.java
@@ -33,11 +33,12 @@
 import android.os.IBinder;
 import android.util.Log;
 
-import androidx.car.drawer.CarDrawerActivity;
 import androidx.car.drawer.CarDrawerAdapter;
 import androidx.car.drawer.DrawerItemViewHolder;
 import androidx.fragment.app.Fragment;
 
+import com.android.car.apps.common.DrawerActivity;
+
 import com.google.android.car.kitchensink.activityview.ActivityViewTestFragment;
 import com.google.android.car.kitchensink.alertdialog.AlertDialogTestFragment;
 import com.google.android.car.kitchensink.assistant.CarAssistantFragment;
@@ -66,7 +67,8 @@
 import java.util.ArrayList;
 import java.util.List;
 
-public class KitchenSinkActivity extends CarDrawerActivity {
+
+public class KitchenSinkActivity extends DrawerActivity {
     private static final String TAG = "KitchenSinkActivity";
 
     private interface ClickHandler {
@@ -209,14 +211,14 @@
     @Override
     protected void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
-        setToolbarElevation(0f);
-        setMainContent(R.layout.kitchen_content);
-        getDrawerController().setRootAdapter(new DrawerAdapter());
+        setContentView(R.layout.kitchen_content);
+
         // Connection to Car Service does not work for non-automotive yet.
         if (getPackageManager().hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE)) {
             initCarApi();
         }
         Log.i(TAG, "onCreate");
+        getDrawerController().setRootAdapter(new DrawerAdapter());
     }
 
     private void initCarApi() {
diff --git a/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/displayinfo/DisplayInfoFragment.java b/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/displayinfo/DisplayInfoFragment.java
index ac0bdae..e199821 100644
--- a/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/displayinfo/DisplayInfoFragment.java
+++ b/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/displayinfo/DisplayInfoFragment.java
@@ -32,7 +32,7 @@
 import com.google.android.car.kitchensink.R;
 
 /**
- * Shows alert dialogs
+ * Displays info about the display this is run on.
  */
 public class DisplayInfoFragment extends Fragment {
 
@@ -95,11 +95,18 @@
     }
 
     private void addDimenText(String dimenName) {
-        addTextView(dimenName + " : " + convertPixelsToDp(
-                getResources().getDimensionPixelSize(
-                        getResources().getIdentifier(
-                                dimenName, "dimen", getContext().getPackageName())),
-                getContext()));
+        String value;
+        try {
+            float dimen = convertPixelsToDp(
+                    getResources().getDimensionPixelSize(
+                            getResources().getIdentifier(
+                                    dimenName, "dimen", getContext().getPackageName())),
+                    getContext());
+            value = Float.toString(dimen);
+        } catch (Resources.NotFoundException e) {
+            value = "Resource Not Found";
+        }
+        addTextView(dimenName + " : " + value);
     }
 
     private void addTextView(String text) {
diff --git a/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/input/InputTestFragment.java b/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/input/InputTestFragment.java
index 5128816..34ea1cf 100644
--- a/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/input/InputTestFragment.java
+++ b/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/input/InputTestFragment.java
@@ -30,7 +30,6 @@
 import android.hardware.automotive.vehicle.V2_0.VehicleArea;
 import android.hardware.automotive.vehicle.V2_0.VehicleDisplay;
 import android.hardware.automotive.vehicle.V2_0.VehiclePropValue;
-import android.hardware.automotive.vehicle.V2_0.VehicleProperty;
 import android.hardware.automotive.vehicle.V2_0.VehiclePropertyGroup;
 import android.hardware.automotive.vehicle.V2_0.VehiclePropertyStatus;
 import android.hardware.automotive.vehicle.V2_0.VehiclePropertyType;
diff --git a/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/notification/NotificationFragment.java b/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/notification/NotificationFragment.java
index 7a26f93..d7230fc 100644
--- a/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/notification/NotificationFragment.java
+++ b/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/notification/NotificationFragment.java
@@ -45,6 +45,8 @@
         Button importanceDefaultButton = view.findViewById(R.id.importance_default_button);
         Button ongoingButton = view.findViewById(R.id.ongoing_button);
         Button messageButton = view.findViewById(R.id.category_message_button);
+        Button emerg = view.findViewById(R.id.category_car_emerg_button);
+        Button warn = view.findViewById(R.id.category_car_warning_button);
 
         NotificationManager manager =
                 (NotificationManager) getActivity().getSystemService(Context.NOTIFICATION_SERVICE);
@@ -184,6 +186,28 @@
             manager.notify(3, notification);
         });
 
+        emerg.setOnClickListener(v -> {
+
+            Notification notification = new Notification.Builder(getActivity(), CHANNEL_ID_1)
+                    .setContentTitle("OMG")
+                    .setContentText("This is of top importance")
+                    .setCategory(Notification.CATEGORY_CAR_EMERGENCY)
+                    .setSmallIcon(R.drawable.car_ic_mode)
+                    .build();
+            manager.notify(10, notification);
+        });
+
+        warn.setOnClickListener(v -> {
+
+            Notification notification = new Notification.Builder(getActivity(), CHANNEL_ID_1)
+                    .setContentTitle("OMG -ish ")
+                    .setContentText("This is of less importance but still")
+                    .setCategory(Notification.CATEGORY_CAR_WARNING)
+                    .setSmallIcon(R.drawable.car_ic_mode)
+                    .build();
+            manager.notify(11, notification);
+        });
+
         return view;
     }
 }
diff --git a/tests/GarageModeTestApp/res/layout/activity_content.xml b/tests/GarageModeTestApp/res/layout/activity_content.xml
deleted file mode 100644
index 4ae4c8d..0000000
--- a/tests/GarageModeTestApp/res/layout/activity_content.xml
+++ /dev/null
@@ -1,21 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2018 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.
--->
-<!-- We use this container to place app fragments. It insets the fragment contents -->
-<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
-    android:id="@+id/activity_content"
-    android:layout_width="match_parent"
-    android:layout_height="match_parent">
-</FrameLayout>
\ No newline at end of file
diff --git a/tests/GarageModeTestApp/res/layout/main_activity.xml b/tests/GarageModeTestApp/res/layout/main_activity.xml
new file mode 100644
index 0000000..b76e5b7
--- /dev/null
+++ b/tests/GarageModeTestApp/res/layout/main_activity.xml
@@ -0,0 +1,61 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2018 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.
+-->
+<androidx.drawerlayout.widget.DrawerLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    android:id="@+id/drawer_layout"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent">
+
+    <LinearLayout
+        android:background="@android:color/black"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:orientation="vertical">
+
+        <!-- The min height of the Toolbar needs to be set to ensure that the icons in it
+             are vertically centered. -->
+        <androidx.appcompat.widget.Toolbar
+            android:id="@+id/car_toolbar"
+            android:background="@android:color/black"
+            android:layout_width="match_parent"
+            android:layout_height="@dimen/car_app_bar_height"
+            android:layout_gravity="center_vertical"
+            android:minHeight="@dimen/car_app_bar_height"
+            style="?attr/toolbarStyle" />
+
+        <FrameLayout
+            android:id="@+id/activity_content"
+            android:layout_width="match_parent"
+            android:layout_height="match_parent" />
+    </LinearLayout>
+
+    <!-- Using a CosntraintLayout to specify a maxWidth. -->
+    <androidx.constraintlayout.widget.ConstraintLayout
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:layout_gravity="start"
+        android:layout_marginEnd="@dimen/car_margin"
+        android:maxWidth="@dimen/car_drawer_max_width">
+
+        <include
+            layout="@layout/car_drawer"
+            app:layout_constraintTop_toTopOf="parent"
+            app:layout_constraintBottom_toBottomOf="parent"
+            app:layout_constraintStart_toEndOf="parent"
+            app:layout_constraintStart_toStartOf="parent" />
+    </androidx.constraintlayout.widget.ConstraintLayout>
+</androidx.drawerlayout.widget.DrawerLayout>
diff --git a/tests/GarageModeTestApp/res/values/strings.xml b/tests/GarageModeTestApp/res/values/strings.xml
index 7ad5d70..4d713c5 100644
--- a/tests/GarageModeTestApp/res/values/strings.xml
+++ b/tests/GarageModeTestApp/res/values/strings.xml
@@ -51,4 +51,7 @@
     <item>5 minutes</item>
     <item>1 hour</item>
   </string-array>
+
+  <string name="car_drawer_open" translatable="false">Open drawer</string>
+  <string name="car_drawer_close" translatable="false">Close drawer</string>
 </resources>
diff --git a/tests/GarageModeTestApp/res/values/styles.xml b/tests/GarageModeTestApp/res/values/styles.xml
index 92cb1c5..642e7ea 100644
--- a/tests/GarageModeTestApp/res/values/styles.xml
+++ b/tests/GarageModeTestApp/res/values/styles.xml
@@ -14,7 +14,7 @@
      limitations under the License.
 -->
 <resources>
-  <style name="GarageModeTheme" parent="Theme.Car.Light.NoActionBar.Drawer">
+  <style name="GarageModeTheme" parent="Theme.Car.Dark.NoActionBar.Drawer">
     <item name="android:textSize">24sp</item>
   </style>
   <style name="SectionContainer">
@@ -54,4 +54,4 @@
     <item name="android:layout_margin">5dp</item>
     <item name="android:padding">3dp</item>
   </style>
-</resources>
\ No newline at end of file
+</resources>
diff --git a/tests/GarageModeTestApp/src/com/google/android/car/garagemode/testapp/MainActivity.java b/tests/GarageModeTestApp/src/com/google/android/car/garagemode/testapp/MainActivity.java
index 55b7c03..2019b7e 100644
--- a/tests/GarageModeTestApp/src/com/google/android/car/garagemode/testapp/MainActivity.java
+++ b/tests/GarageModeTestApp/src/com/google/android/car/garagemode/testapp/MainActivity.java
@@ -15,19 +15,28 @@
  */
 package com.google.android.car.garagemode.testapp;
 
+import android.content.res.Configuration;
 import android.os.Bundle;
+import android.view.MenuItem;
 
-import androidx.car.drawer.CarDrawerActivity;
+import androidx.appcompat.app.ActionBarDrawerToggle;
+import androidx.appcompat.app.AppCompatActivity;
+import androidx.appcompat.widget.Toolbar;
 import androidx.car.drawer.CarDrawerAdapter;
+import androidx.car.drawer.CarDrawerController;
 import androidx.car.drawer.DrawerItemViewHolder;
+import androidx.drawerlayout.widget.DrawerLayout;
 import androidx.fragment.app.Fragment;
 
 import java.util.ArrayList;
 import java.util.List;
 
-public class MainActivity extends CarDrawerActivity {
+public class MainActivity extends AppCompatActivity {
     private static final Logger LOG = new Logger("MainActivity");
 
+    private CarDrawerController mDrawerController;
+    private Toolbar mToolbar;
+
     private final List<MenuEntry> mMenuEntries = new ArrayList<MenuEntry>() {
         {
             add("Offcar testing", OffcarTestingFragment.class);
@@ -47,9 +56,47 @@
     @Override
     protected void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
-        setMainContent(R.layout.activity_content);
+        setContentView(R.layout.main_activity);
+
+        DrawerLayout drawerLayout = findViewById(R.id.drawer_layout);
+        ActionBarDrawerToggle drawerToggle = new ActionBarDrawerToggle(
+                /* activity= */ this,
+                drawerLayout,
+                R.string.car_drawer_open,
+                R.string.car_drawer_close);
+
+        mToolbar = findViewById(R.id.car_toolbar);
+        setSupportActionBar(mToolbar);
+        getSupportActionBar().setDisplayHomeAsUpEnabled(true);
+        getSupportActionBar().setHomeButtonEnabled(true);
+
         mMenuEntries.get(0).onClick();
-        getDrawerController().setRootAdapter(new DrawerAdapter());
+
+        mDrawerController = new CarDrawerController(drawerLayout, drawerToggle);
+        mDrawerController.setRootAdapter(new DrawerAdapter());
+    }
+
+    @Override
+    protected void onPostCreate(Bundle savedInstanceState) {
+        super.onPostCreate(savedInstanceState);
+        mDrawerController.syncState();
+    }
+
+    @Override
+    protected void onStop() {
+        super.onStop();
+        mDrawerController.closeDrawer();
+    }
+
+    @Override
+    public void onConfigurationChanged(Configuration newConfig) {
+        super.onConfigurationChanged(newConfig);
+        mDrawerController.onConfigurationChanged(newConfig);
+    }
+
+    @Override
+    public boolean onOptionsItemSelected(MenuItem item) {
+        return mDrawerController.onOptionsItemSelected(item) || super.onOptionsItemSelected(item);
     }
 
     private interface ClickHandler {
@@ -155,8 +202,7 @@
             }
 
             mMenuEntries.get(position).onClick();
-
-            getDrawerController().closeDrawer();
+            mDrawerController.closeDrawer();
         }
     }
 }