Merge changes I67883374,I945c79ac

* changes:
  Temporary disconnects for Bluetooth profiles.
  Make ConnectionParams an immutable object.
diff --git a/car-lib/src/android/car/trust/CarTrustAgentEnrollmentManager.java b/car-lib/src/android/car/trust/CarTrustAgentEnrollmentManager.java
index 48ee384..6b27b5e 100644
--- a/car-lib/src/android/car/trust/CarTrustAgentEnrollmentManager.java
+++ b/car-lib/src/android/car/trust/CarTrustAgentEnrollmentManager.java
@@ -25,9 +25,13 @@
 import android.car.CarManagerBase;
 import android.car.CarNotConnectedException;
 import android.content.Context;
+import android.os.Bundle;
 import android.os.Handler;
 import android.os.IBinder;
+import android.os.Looper;
+import android.os.Message;
 import android.os.RemoteException;
+import android.util.Log;
 
 import com.android.internal.annotations.GuardedBy;
 
@@ -48,8 +52,8 @@
  * <li> startEnrollmentAdvertising()
  * <li> wait for onEnrollmentAdvertisingStarted() or
  * <li> wait for onBleEnrollmentDeviceConnected() and check if the device connected is the right
- *  one.
- * <li> initiateEnrollmentHandhake()
+ * one.
+ * <li> initiateEnrollmentHandshake()
  * <li> wait for onAuthStringAvailable() to get the pairing code to display to the user
  * <li> enrollmentHandshakeAccepted() after user confirms the pairing code
  * <li> wait for onEscrowTokenAdded()
@@ -64,6 +68,19 @@
 @SystemApi
 public final class CarTrustAgentEnrollmentManager implements CarManagerBase {
     private static final String TAG = "CarTrustEnrollMgr";
+    private static final String KEY_HANDLE = "handle";
+    private static final String KEY_ACTIVE = "active";
+    private static final String KEY_SUCCESS = "success";
+    private static final int MSG_ENROLL_ADVERTISING_STARTED = 0;
+    private static final int MSG_ENROLL_ADVERTISING_FAILED = 1;
+    private static final int MSG_ENROLL_DEVICE_CONNECTED = 2;
+    private static final int MSG_ENROLL_DEVICE_DISCONNECTED = 3;
+    private static final int MSG_ENROLL_HANDSHAKE_FAILURE = 4;
+    private static final int MSG_ENROLL_AUTH_STRING_AVAILABLE = 5;
+    private static final int MSG_ENROLL_TOKEN_ADDED = 6;
+    private static final int MSG_ENROLL_TOKEN_REVOKED = 7;
+    private static final int MSG_ENROLL_TOKEN_STATE_CHANGED = 8;
+
     private final Context mContext;
     private final ICarTrustAgentEnrollment mEnrollmentService;
     private Object mListenerLock = new Object();
@@ -75,12 +92,14 @@
     private final ListenerToEnrollmentService mListenerToEnrollmentService =
             new ListenerToEnrollmentService(this);
     private final ListenerToBleService mListenerToBleService = new ListenerToBleService(this);
+    private final EventCallbackHandler mEventCallbackHandler;
 
 
     /** @hide */
     public CarTrustAgentEnrollmentManager(IBinder service, Context context, Handler handler) {
         mContext = context;
         mEnrollmentService = ICarTrustAgentEnrollment.Stub.asInterface(service);
+        mEventCallbackHandler = new EventCallbackHandler(this, handler.getLooper());
     }
 
     /** @hide */
@@ -284,6 +303,10 @@
         }
     }
 
+    private Handler getEventCallbackHandler() {
+        return mEventCallbackHandler;
+    }
+
     /**
      * Callback interface for Trusted device enrollment applications to implement.  The applications
      * get notified on various enrollment state change events.
@@ -292,7 +315,7 @@
         /**
          * Communicate about failure/timeouts in the handshake process.
          *
-         * @param device the remote device trying to enroll
+         * @param device    the remote device trying to enroll
          * @param errorCode information on what failed.
          */
         void onEnrollmentHandshakeFailure(BluetoothDevice device, int errorCode);
@@ -300,7 +323,7 @@
         /**
          * Present the pairing/authentication string to the user.
          *
-         * @param device the remote device trying to enroll
+         * @param device     the remote device trying to enroll
          * @param authString the authentication string to show to the user to confirm across
          *                   both devices
          */
@@ -358,9 +381,8 @@
         void onEnrollmentAdvertisingFailed(int errorCode);
     }
 
-    // TODO(ramperry) - call the client callbacks from the methods of this listener to the service
-    // callbacks.
-    private class ListenerToEnrollmentService extends ICarTrustAgentEnrollmentCallback.Stub {
+    private static final class ListenerToEnrollmentService extends
+            ICarTrustAgentEnrollmentCallback.Stub {
         private final WeakReference<CarTrustAgentEnrollmentManager> mMgr;
 
         ListenerToEnrollmentService(CarTrustAgentEnrollmentManager mgr) {
@@ -370,38 +392,87 @@
         /**
          * Communicate about failure/timeouts in the handshake process.
          */
+        @Override
         public void onEnrollmentHandshakeFailure(BluetoothDevice device, int errorCode) {
+            CarTrustAgentEnrollmentManager enrollmentManager = mMgr.get();
+            if (enrollmentManager == null) {
+                return;
+            }
+            enrollmentManager.getEventCallbackHandler().sendMessage(
+                    enrollmentManager.getEventCallbackHandler().obtainMessage(
+                            MSG_ENROLL_HANDSHAKE_FAILURE, new AuthInfo(device, null, errorCode)));
         }
 
         /**
          * Present the pairing/authentication string to the user.
          */
-        public void onAuthStringAvailable(BluetoothDevice device, byte[] authString) {
+        @Override
+        public void onAuthStringAvailable(BluetoothDevice device, String authString) {
+            CarTrustAgentEnrollmentManager enrollmentManager = mMgr.get();
+            if (enrollmentManager == null) {
+                return;
+            }
+            enrollmentManager.getEventCallbackHandler().sendMessage(
+                    enrollmentManager.getEventCallbackHandler().obtainMessage(
+                            MSG_ENROLL_AUTH_STRING_AVAILABLE, new AuthInfo(device, authString, 0)));
         }
 
         /**
          * Escrow token was received and the Trust Agent framework has generated a corresponding
          * handle.
          */
+        @Override
         public void onEscrowTokenAdded(long handle) {
+            CarTrustAgentEnrollmentManager enrollmentManager = mMgr.get();
+            if (enrollmentManager == null) {
+                return;
+            }
+            Message message = enrollmentManager.getEventCallbackHandler().obtainMessage(
+                    MSG_ENROLL_TOKEN_ADDED);
+            Bundle data = new Bundle();
+            data.putLong(KEY_HANDLE, handle);
+            message.setData(data);
+            enrollmentManager.getEventCallbackHandler().sendMessage(message);
         }
 
         /**
          * Escrow token corresponding to the given handle has been removed.
          */
+        @Override
         public void onTrustRevoked(long handle, boolean success) {
+            CarTrustAgentEnrollmentManager enrollmentManager = mMgr.get();
+            if (enrollmentManager == null) {
+                return;
+            }
+            Message message = enrollmentManager.getEventCallbackHandler().obtainMessage(
+                    MSG_ENROLL_TOKEN_REVOKED);
+            Bundle data = new Bundle();
+            data.putLong(KEY_HANDLE, handle);
+            data.putBoolean(KEY_SUCCESS, success);
+            message.setData(data);
+            enrollmentManager.getEventCallbackHandler().sendMessage(message);
         }
 
         /**
          * Escrow token's active state changed.
          */
+        @Override
         public void onEscrowTokenActiveStateChanged(long handle, boolean active) {
+            CarTrustAgentEnrollmentManager enrollmentManager = mMgr.get();
+            if (enrollmentManager == null) {
+                return;
+            }
+            Message message = enrollmentManager.getEventCallbackHandler().obtainMessage(
+                    MSG_ENROLL_TOKEN_STATE_CHANGED);
+            Bundle data = new Bundle();
+            data.putLong(KEY_HANDLE, handle);
+            data.putBoolean(KEY_ACTIVE, active);
+            message.setData(data);
+            enrollmentManager.getEventCallbackHandler().sendMessage(message);
         }
     }
 
-    // TODO(ramperry) - call the client callbacks from the methods of this listener to the service
-    // callbacks.
-    private class ListenerToBleService extends ICarTrustAgentBleCallback.Stub {
+    private static final class ListenerToBleService extends ICarTrustAgentBleCallback.Stub {
         private final WeakReference<CarTrustAgentEnrollmentManager> mMgr;
 
         ListenerToBleService(CarTrustAgentEnrollmentManager mgr) {
@@ -413,6 +484,13 @@
          * enrollment.
          */
         public void onEnrollmentAdvertisingStarted() {
+            CarTrustAgentEnrollmentManager enrollmentManager = mMgr.get();
+            if (enrollmentManager == null) {
+                return;
+            }
+            enrollmentManager.getEventCallbackHandler().sendMessage(
+                    enrollmentManager.getEventCallbackHandler().obtainMessage(
+                            MSG_ENROLL_ADVERTISING_STARTED));
         }
 
         /**
@@ -420,18 +498,179 @@
          * see AdvertiseCallback#ADVERTISE_FAILED_* for possible error codes.
          */
         public void onEnrollmentAdvertisingFailed(int errorCode) {
+            CarTrustAgentEnrollmentManager enrollmentManager = mMgr.get();
+            if (enrollmentManager == null) {
+                return;
+            }
+            enrollmentManager.getEventCallbackHandler().sendMessage(
+                    enrollmentManager.getEventCallbackHandler().obtainMessage(
+                            MSG_ENROLL_ADVERTISING_FAILED, errorCode));
         }
 
         /**
          * Called when a remote device is connected on BLE.
          */
         public void onBleEnrollmentDeviceConnected(BluetoothDevice device) {
+            CarTrustAgentEnrollmentManager enrollmentManager = mMgr.get();
+            if (enrollmentManager == null) {
+                return;
+            }
+            enrollmentManager.getEventCallbackHandler().sendMessage(
+                    enrollmentManager.getEventCallbackHandler().obtainMessage(
+                            MSG_ENROLL_DEVICE_CONNECTED, device));
         }
 
         /**
          * Called when a remote device is disconnected on BLE.
          */
         public void onBleEnrollmentDeviceDisconnected(BluetoothDevice device) {
+            CarTrustAgentEnrollmentManager enrollmentManager = mMgr.get();
+            if (enrollmentManager == null) {
+                return;
+            }
+            enrollmentManager.getEventCallbackHandler().sendMessage(
+                    enrollmentManager.getEventCallbackHandler().obtainMessage(
+                            MSG_ENROLL_DEVICE_DISCONNECTED, device));
+        }
+    }
+
+    /**
+     * Callback Handler to handle dispatching the enrollment state changes to the corresponding
+     * listeners
+     */
+    private static final class EventCallbackHandler extends Handler {
+        private final WeakReference<CarTrustAgentEnrollmentManager> mEnrollmentManager;
+
+        EventCallbackHandler(CarTrustAgentEnrollmentManager manager, Looper looper) {
+            super(looper);
+            mEnrollmentManager = new WeakReference<>(manager);
+        }
+
+        @Override
+        public void handleMessage(Message message) {
+            CarTrustAgentEnrollmentManager enrollmentManager = mEnrollmentManager.get();
+            if (enrollmentManager == null) {
+                return;
+            }
+            switch (message.what) {
+                case MSG_ENROLL_ADVERTISING_STARTED:
+                case MSG_ENROLL_ADVERTISING_FAILED:
+                case MSG_ENROLL_DEVICE_CONNECTED:
+                case MSG_ENROLL_DEVICE_DISCONNECTED:
+                    enrollmentManager.dispatchBleCallback(message);
+                    break;
+                case MSG_ENROLL_HANDSHAKE_FAILURE:
+                case MSG_ENROLL_AUTH_STRING_AVAILABLE:
+                case MSG_ENROLL_TOKEN_ADDED:
+                case MSG_ENROLL_TOKEN_REVOKED:
+                case MSG_ENROLL_TOKEN_STATE_CHANGED:
+                    enrollmentManager.dispatchEnrollmentCallback(message);
+                    break;
+                default:
+                    Log.e(TAG, "Unknown message:" + message.what);
+                    break;
+            }
+        }
+    }
+
+    /**
+     * Dispatch BLE related state change callbacks
+     *
+     * @param message Message to handle and dispatch
+     */
+    private void dispatchBleCallback(Message message) {
+        CarTrustAgentBleCallback bleCallback;
+        synchronized (mListenerLock) {
+            bleCallback = mBleCallback;
+        }
+        if (bleCallback == null) {
+            return;
+        }
+        switch (message.what) {
+            case MSG_ENROLL_ADVERTISING_STARTED:
+                bleCallback.onEnrollmentAdvertisingStarted();
+                break;
+            case MSG_ENROLL_ADVERTISING_FAILED:
+                bleCallback.onEnrollmentAdvertisingFailed((int) message.obj);
+                break;
+            case MSG_ENROLL_DEVICE_CONNECTED:
+                bleCallback.onBleEnrollmentDeviceConnected((BluetoothDevice) message.obj);
+                break;
+            case MSG_ENROLL_DEVICE_DISCONNECTED:
+                bleCallback.onBleEnrollmentDeviceDisconnected((BluetoothDevice) message.obj);
+                break;
+            default:
+                break;
+        }
+    }
+
+    /**
+     * Dispatch Enrollment related state changes to the listener.
+     *
+     * @param message Message to handle and dispatch
+     */
+    private void dispatchEnrollmentCallback(Message message) {
+        CarTrustAgentEnrollmentCallback enrollmentCallback;
+        synchronized (mListenerLock) {
+            enrollmentCallback = mEnrollmentCallback;
+        }
+        if (enrollmentCallback == null) {
+            return;
+        }
+        AuthInfo auth;
+        Bundle data;
+        switch (message.what) {
+            case MSG_ENROLL_HANDSHAKE_FAILURE:
+                auth = (AuthInfo) message.obj;
+                enrollmentCallback.onEnrollmentHandshakeFailure(auth.mDevice, auth.mErrorCode);
+                break;
+            case MSG_ENROLL_AUTH_STRING_AVAILABLE:
+                auth = (AuthInfo) message.obj;
+                if (auth.mDevice != null && auth.mAuthString != null) {
+                    enrollmentCallback.onAuthStringAvailable(auth.mDevice, auth.mAuthString);
+                }
+                break;
+            case MSG_ENROLL_TOKEN_ADDED:
+                data = message.getData();
+                if (data == null) {
+                    break;
+                }
+                enrollmentCallback.onEscrowTokenAdded(data.getLong(KEY_HANDLE));
+                break;
+            case MSG_ENROLL_TOKEN_REVOKED:
+                data = message.getData();
+                if (data == null) {
+                    break;
+                }
+                enrollmentCallback.onTrustRevoked(data.getLong(KEY_HANDLE),
+                        data.getBoolean(KEY_SUCCESS));
+                break;
+            case MSG_ENROLL_TOKEN_STATE_CHANGED:
+                data = message.getData();
+                if (data == null) {
+                    break;
+                }
+                enrollmentCallback.onEscrowTokenActiveStateChanged(data.getLong(KEY_HANDLE),
+                        data.getBoolean(KEY_ACTIVE));
+                break;
+            default:
+                break;
+        }
+    }
+
+    /**
+     * Container class to pass information through a Message to the handler.
+     */
+    private static class AuthInfo {
+        final BluetoothDevice mDevice;
+        @Nullable
+        final String mAuthString;
+        final int mErrorCode;
+
+        AuthInfo(BluetoothDevice device, @Nullable String authString, int errorCode) {
+            mDevice = device;
+            mAuthString = authString;
+            mErrorCode = errorCode;
         }
     }
 }
diff --git a/car-lib/src/android/car/trust/ICarTrustAgentEnrollmentCallback.aidl b/car-lib/src/android/car/trust/ICarTrustAgentEnrollmentCallback.aidl
index 9457fec..62cef9c 100644
--- a/car-lib/src/android/car/trust/ICarTrustAgentEnrollmentCallback.aidl
+++ b/car-lib/src/android/car/trust/ICarTrustAgentEnrollmentCallback.aidl
@@ -32,7 +32,7 @@
     /**
      * Present the pairing/authentication string to the user.
      */
-    void onAuthStringAvailable(in BluetoothDevice device, in byte[] authString);
+    void onAuthStringAvailable(in BluetoothDevice device, in String authString);
 
     /**
      * Escrow token was received and the Trust Agent framework has generated a corresponding handle.
diff --git a/car_product/build/car.mk b/car_product/build/car.mk
index bfe12d6..cdad5b0 100644
--- a/car_product/build/car.mk
+++ b/car_product/build/car.mk
@@ -47,9 +47,8 @@
 BOARD_SEPOLICY_DIRS += packages/services/Car/car_product/sepolicy/test
 endif
 
-PRODUCT_COPY_FILES := \
-    frameworks/av/media/libeffects/data/audio_effects.conf:system/etc/audio_effects.conf \
-    packages/services/Car/car_product/preloaded-classes-car:system/etc/preloaded-classes \
+PRODUCT_COPY_FILES += \
+    frameworks/av/media/libeffects/data/audio_effects.conf:system/etc/audio_effects.conf
 
 PRODUCT_PROPERTY_OVERRIDES += \
     ro.carrier=unknown \
diff --git a/car_product/preloaded-classes-car b/car_product/preloaded-classes-car
deleted file mode 100644
index 962099f..0000000
--- a/car_product/preloaded-classes-car
+++ /dev/null
@@ -1 +0,0 @@
-# Classes which are preloaded by com.android.internal.os.ZygoteInit.
diff --git a/service/res/values/config.xml b/service/res/values/config.xml
index 3b05e29..e8c520a 100644
--- a/service/res/values/config.xml
+++ b/service/res/values/config.xml
@@ -25,13 +25,6 @@
           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/values/strings.xml b/service/res/values/strings.xml
index 2a7bcaa..69b9467 100644
--- a/service/res/values/strings.xml
+++ b/service/res/values/strings.xml
@@ -14,8 +14,7 @@
      limitations under the License.
 -->
 <resources>
-    <string name="app_title">Car service</string>
-
+    <string name="app_title" translatable="false">Car service</string>
     <!--  For permissions -->
     <!-- Permission text: can access your car's information [CHAR LIMIT=NONE] -->
     <string name="car_permission_label">Car information</string>
diff --git a/service/res/xml/car_audio_configuration.xml b/service/res/xml/car_audio_configuration.xml
deleted file mode 100644
index e3fd8f6..0000000
--- a/service/res/xml/car_audio_configuration.xml
+++ /dev/null
@@ -1,88 +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.
--->
-
-<!--
-  Defines the audio configuration in a car, including
-    - Audio zones
-    - Display to audio zone mappings
-    - Context to audio bus mappings
-    - Volume groups
-  in the car environment.
--->
-<carAudioConfiguration
-        xmlns:car="http://schemas.android.com/apk/res-auto"
-        car:version="1">
-    <zones>
-        <zone car:name="primary zone" car:isPrimary="true">
-            <volumeGroups>
-                <group>
-                    <device car:address="bus0_media_out">
-                        <context car:context="music"/>
-                    </device>
-                    <device car:address="bus3_call_ring_out">
-                        <context car:context="call_ring"/>
-                    </device>
-                    <device car:address="bus6_notification_out">
-                        <context car:context="notification"/>
-                    </device>
-                    <device car:address="bus7_system_sound_out">
-                        <context car:context="system_sound"/>
-                    </device>
-                </group>
-                <group>
-                    <device car:address="bus1_navigation_out">
-                        <context car:context="navigation"/>
-                    </device>
-                    <device car:address="bus2_voice_command_out">
-                        <context car:context="voice_command"/>
-                    </device>
-                </group>
-                <group>
-                    <device car:address="bus4_call_out">
-                        <context car:context="call"/>
-                    </device>
-                </group>
-                <group>
-                    <device car:address="bus5_alarm_out">
-                        <context car:context="alarm"/>
-                    </device>
-                </group>
-            </volumeGroups>
-            <displays>
-                <display car:display="primary_display"/>
-            </displays>
-        </zone>
-        <zone car:name="rear seat zone">
-            <volumeGroups>
-                <group>
-                    <device car:address="bus100_rear_seat">
-                        <context car:context="music"/>
-                        <context car:context="navigation"/>
-                        <context car:context="voice_command"/>
-                        <context car:context="call_ring"/>
-                        <context car:context="call"/>
-                        <context car:context="alarm"/>
-                        <context car:context="notification"/>
-                        <context car:context="system_sound"/>
-                    </device>
-                </group>
-            </volumeGroups>
-            <displays>
-                <display car:display="rear_seat_display"/>
-            </displays>
-        </zone>
-    </zones>
-</carAudioConfiguration>
diff --git a/service/src/com/android/car/audio/CarAudioService.java b/service/src/com/android/car/audio/CarAudioService.java
index 3c5434d..b19fda5 100644
--- a/service/src/com/android/car/audio/CarAudioService.java
+++ b/service/src/com/android/car/audio/CarAudioService.java
@@ -56,6 +56,7 @@
 import com.android.car.R;
 import com.android.internal.util.Preconditions;
 
+import java.io.File;
 import java.io.PrintWriter;
 import java.util.ArrayList;
 import java.util.Arrays;
@@ -83,6 +84,14 @@
     // allows listening for both GROUP/MEDIA and GROUP/NAVIGATION.
     private static final String VOLUME_SETTINGS_KEY_FOR_GROUP_PREFIX = "android.car.VOLUME_GROUP/";
 
+    // CarAudioService reads configuration from the following paths respectively.
+    // If the first one is found, all others are ignored.
+    // If no one is found, it fallbacks to car_volume_groups.xml resource file.
+    private static final String[] AUDIO_CONFIGURATION_PATHS = new String[] {
+            "/vendor/etc/car_audio_configuration.xml",
+            "/system/etc/car_audio_configuration.xml"
+    };
+
     /**
      * Gets the key to persist volume for a volume group in settings
      *
@@ -101,7 +110,6 @@
     private final TelephonyManager mTelephonyManager;
     private final AudioManager mAudioManager;
     private final boolean mUseDynamicRouting;
-    private final boolean mUseUnifiedConfiguration;
     private final boolean mPersistMasterMuteState;
 
     private final AudioPolicy.AudioPolicyVolumeCallback mAudioPolicyVolumeCallback =
@@ -175,6 +183,7 @@
 
     private AudioPolicy mAudioPolicy;
     private CarAudioFocus mFocusHandler;
+    private String mCarAudioConfigurationPath;
     private CarAudioZone[] mCarAudioZones;
 
     public CarAudioService(Context context) {
@@ -182,8 +191,6 @@
         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);
     }
@@ -254,9 +261,11 @@
     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());
+        if (mCarAudioConfigurationPath != null) {
+            writer.println("\tCar audio configuration path: " + mCarAudioConfigurationPath);
+        }
         // Empty line for comfortable reading
         writer.println();
         if (mUseDynamicRouting) {
@@ -407,8 +416,9 @@
         builder.setLooper(Looper.getMainLooper());
 
         final CarAudioZonesLoader zonesLoader;
-        if (mUseUnifiedConfiguration) {
-            zonesLoader = new CarAudioZonesHelper(mContext, R.xml.car_audio_configuration,
+        mCarAudioConfigurationPath = getAudioConfigurationPath();
+        if (mCarAudioConfigurationPath != null) {
+            zonesLoader = new CarAudioZonesHelper(mContext, mCarAudioConfigurationPath,
                     busToCarAudioDeviceInfo);
         } else {
             // In legacy mode, context -> bus mapping is done by querying IAudioControl HAL.
@@ -459,6 +469,21 @@
     }
 
     /**
+     * Read from {@link #AUDIO_CONFIGURATION_PATHS} respectively.
+     * @return File path of the first hit in {@link #AUDIO_CONFIGURATION_PATHS}
+     */
+    @Nullable
+    private String getAudioConfigurationPath() {
+        for (String path : AUDIO_CONFIGURATION_PATHS) {
+            File configuration = new File(path);
+            if (configuration.exists()) {
+                return path;
+            }
+        }
+        return null;
+    }
+
+    /**
      * @return Context number for a given audio usage, 0 if the given usage is unrecognized.
      */
     int getContextForUsage(int audioUsage) {
diff --git a/service/src/com/android/car/audio/CarAudioZonesHelper.java b/service/src/com/android/car/audio/CarAudioZonesHelper.java
index ba7b93c..0acae73 100644
--- a/service/src/com/android/car/audio/CarAudioZonesHelper.java
+++ b/service/src/com/android/car/audio/CarAudioZonesHelper.java
@@ -16,30 +16,32 @@
 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.hardware.automotive.audiocontrol.V1_0.ContextNumber;
 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.XmlPullParser;
 import org.xmlpull.v1.XmlPullParserException;
 
+import java.io.FileInputStream;
 import java.io.IOException;
+import java.io.InputStream;
 import java.util.ArrayList;
+import java.util.HashMap;
 import java.util.List;
+import java.util.Map;
 
 /**
  * A helper class loads all audio zones from the configuration XML file.
  */
 /* package */ class CarAudioZonesHelper implements CarAudioService.CarAudioZonesLoader {
 
+    private static final String NAMESPACE = null;
     private static final String TAG_ROOT = "carAudioConfiguration";
     private static final String TAG_AUDIO_ZONES = "zones";
     private static final String TAG_AUDIO_ZONE = "zone";
@@ -47,18 +49,37 @@
     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 String ATTR_VERSION = "version";
+    private static final String ATTR_IS_PRIMARY = "isPrimary";
+    private static final String ATTR_ZONE_NAME = "name";
+    private static final String ATTR_DEVICE_ADDRESS = "address";
+    private static final String ATTR_CONTEXT_NAME = "context";
     private static final int SUPPORTED_VERSION = 1;
 
+    private static final Map<String, Integer> CONTEXT_NAME_MAP;
+
+    static {
+        CONTEXT_NAME_MAP = new HashMap<>();
+        CONTEXT_NAME_MAP.put("music", ContextNumber.MUSIC);
+        CONTEXT_NAME_MAP.put("navigation", ContextNumber.NAVIGATION);
+        CONTEXT_NAME_MAP.put("voice_command", ContextNumber.VOICE_COMMAND);
+        CONTEXT_NAME_MAP.put("call_ring", ContextNumber.CALL_RING);
+        CONTEXT_NAME_MAP.put("call", ContextNumber.CALL);
+        CONTEXT_NAME_MAP.put("alarm", ContextNumber.ALARM);
+        CONTEXT_NAME_MAP.put("notification", ContextNumber.NOTIFICATION);
+        CONTEXT_NAME_MAP.put("system_sound", ContextNumber.SYSTEM_SOUND);
+    }
+
     private final Context mContext;
-    private final int mXmlConfiguration;
+    private final String mXmlConfigurationPath;
     private final SparseArray<CarAudioDeviceInfo> mBusToCarAudioDeviceInfo;
 
     private int mNextSecondaryZoneId;
 
-    CarAudioZonesHelper(Context context, @XmlRes int xmlConfiguration,
+    CarAudioZonesHelper(Context context, @NonNull String xmlConfigurationPath,
             @NonNull SparseArray<CarAudioDeviceInfo> busToCarAudioDeviceInfo) {
         mContext = context;
-        mXmlConfiguration = xmlConfiguration;
+        mXmlConfigurationPath = xmlConfigurationPath;
         mBusToCarAudioDeviceInfo = busToCarAudioDeviceInfo;
 
         mNextSecondaryZoneId = CarAudioManager.PRIMARY_AUDIO_ZONE + 1;
@@ -67,133 +88,137 @@
     @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);
-            }
+        try (InputStream stream = new FileInputStream(mXmlConfigurationPath)) {
+            final XmlPullParser parser = Xml.newPullParser();
+            parser.setFeature(XmlPullParser.FEATURE_PROCESS_NAMESPACES, NAMESPACE != null);
+            parser.setInput(stream, null);
+
+            // Ensure <carAudioConfiguration> is the root
+            parser.nextTag();
+            parser.require(XmlPullParser.START_TAG, NAMESPACE, TAG_ROOT);
 
             // Version check
-            TypedArray c = mContext.getResources().obtainAttributes(
-                    attrs, R.styleable.carAudioConfiguration);
-            final int versionNumber = c.getInt(R.styleable.carAudioConfiguration_version, -1);
+            final int versionNumber = Integer.parseInt(
+                    parser.getAttributeValue(NAMESPACE, ATTR_VERSION));
             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));
+            // Get all zones configured under <zones> tag
+            while (parser.next() != XmlPullParser.END_TAG) {
+                if (parser.getEventType() != XmlPullParser.START_TAG) continue;
+                if (TAG_AUDIO_ZONES.equals(parser.getName())) {
+                    parseAudioZones(parser, carAudioZones);
+                } else {
+                    skip(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)
+    private void parseAudioZones(XmlPullParser parser, List<CarAudioZone> carAudioZones)
             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();
+        while (parser.next() != XmlPullParser.END_TAG) {
+            if (parser.getEventType() != XmlPullParser.START_TAG) continue;
+            if (TAG_AUDIO_ZONE.equals(parser.getName())) {
+                carAudioZones.add(parseAudioZone(parser));
+            } else {
+                skip(parser);
+            }
+        }
+    }
+
+    private CarAudioZone parseAudioZone(XmlPullParser parser)
+            throws XmlPullParserException, IOException {
+        final boolean isPrimary = Boolean.parseBoolean(
+                parser.getAttributeValue(NAMESPACE, ATTR_IS_PRIMARY));
+        final String zoneName = parser.getAttributeValue(NAMESPACE, ATTR_ZONE_NAME);
 
         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;
+        while (parser.next() != XmlPullParser.END_TAG) {
+            if (parser.getEventType() != XmlPullParser.START_TAG) continue;
+            // Expect one <volumeGroups> in one audio zone
+            if (TAG_VOLUME_GROUPS.equals(parser.getName())) {
+                parseVolumeGroups(parser, zone);
+            } else {
+                skip(parser);
             }
         }
         return zone;
     }
 
-    private CarVolumeGroup parseVolumeGroup(
-            int zoneId, int groupId, AttributeSet attrs, XmlResourceParser parser)
+    private void parseVolumeGroups(XmlPullParser parser, CarAudioZone zone)
+            throws XmlPullParserException, IOException {
+        int groupId = 0;
+        while (parser.next() != XmlPullParser.END_TAG) {
+            if (parser.getEventType() != XmlPullParser.START_TAG) continue;
+            if (TAG_VOLUME_GROUP.equals(parser.getName())) {
+                zone.addVolumeGroup(parseVolumeGroup(parser, zone.getId(), groupId));
+                groupId++;
+            } else {
+                skip(parser);
+            }
+        }
+    }
+
+    private CarVolumeGroup parseVolumeGroup(XmlPullParser parser, int zoneId, int groupId)
             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;
-            }
+        while (parser.next() != XmlPullParser.END_TAG) {
+            if (parser.getEventType() != XmlPullParser.START_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();
+                String address = parser.getAttributeValue(NAMESPACE, ATTR_DEVICE_ADDRESS);
+                parseVolumeGroupContexts(parser, group,
+                        CarAudioDeviceInfo.parseDeviceAddress(address));
+            } else {
+                skip(parser);
             }
         }
         return group;
     }
 
     private void parseVolumeGroupContexts(
-            CarVolumeGroup group, int busNumber, AttributeSet attrs, XmlResourceParser parser)
+            XmlPullParser parser, CarVolumeGroup group, int busNumber)
             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;
-            }
+        while (parser.next() != XmlPullParser.END_TAG) {
+            if (parser.getEventType() != XmlPullParser.START_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));
+                group.bind(
+                        parseContextNumber(parser.getAttributeValue(NAMESPACE, ATTR_CONTEXT_NAME)),
+                        busNumber, mBusToCarAudioDeviceInfo.get(busNumber));
+            }
+            // Always skip to upper level since we're at the lowest.
+            skip(parser);
+        }
+    }
+
+    private void skip(XmlPullParser parser) throws XmlPullParserException, IOException {
+        if (parser.getEventType() != XmlPullParser.START_TAG) {
+            throw new IllegalStateException();
+        }
+        int depth = 1;
+        while (depth != 0) {
+            switch (parser.next()) {
+                case XmlPullParser.END_TAG:
+                    depth--;
+                    break;
+                case XmlPullParser.START_TAG:
+                    depth++;
+                    break;
             }
         }
     }
 
+    private int parseContextNumber(String context) {
+        return CONTEXT_NAME_MAP.getOrDefault(context.toLowerCase(), ContextNumber.INVALID);
+    }
+
     private int getNextSecondaryZoneId() {
         int zoneId = mNextSecondaryZoneId;
         mNextSecondaryZoneId += 1;
diff --git a/tests/DirectRenderingClusterSample/AndroidManifest.xml b/tests/DirectRenderingClusterSample/AndroidManifest.xml
index 660f460..2dcdc58 100644
--- a/tests/DirectRenderingClusterSample/AndroidManifest.xml
+++ b/tests/DirectRenderingClusterSample/AndroidManifest.xml
@@ -46,6 +46,7 @@
     <uses-permission android:name="android.car.permission.CAR_POWERTRAIN"/>
     <uses-permission android:name="android.car.permission.CAR_INFO"/>
     <uses-permission android:name="android.car.permission.CAR_SPEED"/>
+    <uses-permission android:name="android.car.permission.CAR_ENGINE_DETAILED"/>
 
     <application android:label="@string/app_name"
                  android:icon="@mipmap/ic_launcher"
diff --git a/tests/DirectRenderingClusterSample/src/android/car/cluster/sample/ClusterViewModel.java b/tests/DirectRenderingClusterSample/src/android/car/cluster/sample/ClusterViewModel.java
index 8a67856..2efe765 100644
--- a/tests/DirectRenderingClusterSample/src/android/car/cluster/sample/ClusterViewModel.java
+++ b/tests/DirectRenderingClusterSample/src/android/car/cluster/sample/ClusterViewModel.java
@@ -74,23 +74,8 @@
             try {
                 Log.i(TAG, "onServiceConnected, name: " + name + ", service: " + service);
 
-                // Listen navigation focus state
-                mCarAppFocusManager = (CarAppFocusManager) mCar.getCarManager(
-                        Car.APP_FOCUS_SERVICE);
-                if (mCarAppFocusManager == null) {
-                    Log.e(TAG, "onServiceConnected: unable to obtain CarAppFocusManager");
-                    return;
-                }
-                mCarAppFocusManager.addFocusListener(
-                        (appType, active) -> setNavigationFocus(active),
-                        CarAppFocusManager.APP_FOCUS_TYPE_NAVIGATION);
-
-                // Listen property value changes
-                mCarPropertyManager = (CarPropertyManager) mCar.getCarManager(Car.PROPERTY_SERVICE);
-                for (Integer propertyId : Sensors.getInstance().getPropertyIds()) {
-                    mCarPropertyManager.registerListener(mCarPropertyEventListener,
-                            propertyId, PROPERTIES_REFRESH_RATE_UI);
-                }
+                registerAppFocusListener();
+                registerCarPropertiesListener();
             } catch (CarNotConnectedException e) {
                 Log.e(TAG, "onServiceConnected: error obtaining manager", e);
             }
@@ -104,6 +89,33 @@
         }
     };
 
+    private void registerAppFocusListener() throws CarNotConnectedException {
+        mCarAppFocusManager = (CarAppFocusManager) mCar.getCarManager(
+                Car.APP_FOCUS_SERVICE);
+        if (mCarAppFocusManager != null) {
+            mCarAppFocusManager.addFocusListener(
+                    (appType, active) -> setNavigationFocus(active),
+                    CarAppFocusManager.APP_FOCUS_TYPE_NAVIGATION);
+        } else {
+            Log.e(TAG, "onServiceConnected: unable to obtain CarAppFocusManager");
+        }
+    }
+
+    private void registerCarPropertiesListener() throws CarNotConnectedException {
+        Sensors sensors = Sensors.getInstance();
+        mCarPropertyManager = (CarPropertyManager) mCar.getCarManager(Car.PROPERTY_SERVICE);
+        for (Integer propertyId : sensors.getPropertyIds()) {
+            try {
+                mCarPropertyManager.registerListener(mCarPropertyEventListener,
+                        propertyId, PROPERTIES_REFRESH_RATE_UI);
+            } catch (SecurityException ex) {
+                Log.e(TAG, "onServiceConnected: Unable to listen to car property: " + propertyId
+                        + " sensors: " + sensors.getSensorsForPropertyId(propertyId), ex);
+            }
+        }
+    }
+
+
     private CarPropertyManager.CarPropertyEventListener mCarPropertyEventListener =
             new CarPropertyManager.CarPropertyEventListener() {
         @Override
diff --git a/tests/DirectRenderingClusterSample/src/android/car/cluster/sample/MainClusterActivity.java b/tests/DirectRenderingClusterSample/src/android/car/cluster/sample/MainClusterActivity.java
index e138a6b..cf38e1f 100644
--- a/tests/DirectRenderingClusterSample/src/android/car/cluster/sample/MainClusterActivity.java
+++ b/tests/DirectRenderingClusterSample/src/android/car/cluster/sample/MainClusterActivity.java
@@ -429,11 +429,9 @@
      * have a default navigation activity selected yet.
      */
     private void tryLaunchNavigationActivity() {
-        int userHandle = ActivityManager.getCurrentUser();
-        if (userHandle == UserHandle.USER_SYSTEM || mNavigationDisplayId == NO_DISPLAY) {
+        if (mNavigationDisplayId == NO_DISPLAY) {
             if (Log.isLoggable(TAG, Log.DEBUG)) {
-                Log.d(TAG, String.format("Launch activity ignored (user: %d, display: %d)",
-                        userHandle, mNavigationDisplayId));
+                Log.d(TAG, String.format("Launch activity ignored (no display yet)"));
             }
             // Not ready to launch yet.
             return;
diff --git a/tests/DirectRenderingClusterSample/src/android/car/cluster/sample/sensors/Sensor.java b/tests/DirectRenderingClusterSample/src/android/car/cluster/sample/sensors/Sensor.java
index 54cc7f8..8f01cd7 100644
--- a/tests/DirectRenderingClusterSample/src/android/car/cluster/sample/sensors/Sensor.java
+++ b/tests/DirectRenderingClusterSample/src/android/car/cluster/sample/sensors/Sensor.java
@@ -57,4 +57,9 @@
         mExpectedPropertyType = expectedPropertyType;
         mAdapter = adapter;
     }
+
+    @Override
+    public String toString() {
+        return mName;
+    }
 }