Merge "EVS Manager, App, and Test initial submission"
diff --git a/car-lib/src/android/car/Car.java b/car-lib/src/android/car/Car.java
index aa3c799..8db1852 100644
--- a/car-lib/src/android/car/Car.java
+++ b/car-lib/src/android/car/Car.java
@@ -139,6 +139,13 @@
             "android.car.permission.CAR_CONTROL_AUDIO_VOLUME";
 
     /**
+     * Permission necessary to change car audio settings through {@link CarAudioManager}.
+     * @hide
+     */
+    public static final String PERMISSION_CAR_CONTROL_AUDIO_SETTINGS =
+            "android.car.permission.CAR_CONTROL_AUDIO_SETTINGS";
+
+    /**
      * Permission necessary to use {@link CarNavigationStatusManager}.
      * @hide
      */
@@ -515,7 +522,7 @@
         CarManagerBase manager = null;
         switch (serviceName) {
             case AUDIO_SERVICE:
-                manager = new CarAudioManager(binder, mContext);
+                manager = new CarAudioManager(binder, mContext, mEventHandler);
                 break;
             case SENSOR_SERVICE:
                 manager = new CarSensorManager(binder, mContext, mEventHandler);
diff --git a/car-lib/src/android/car/media/CarAudioManager.java b/car-lib/src/android/car/media/CarAudioManager.java
index 9e1a6b3..cf7ce1f 100644
--- a/car-lib/src/android/car/media/CarAudioManager.java
+++ b/car-lib/src/android/car/media/CarAudioManager.java
@@ -16,6 +16,7 @@
 package android.car.media;
 
 import android.annotation.IntDef;
+import android.annotation.Nullable;
 import android.annotation.SystemApi;
 import android.car.CarLibLog;
 import android.car.CarNotConnectedException;
@@ -24,6 +25,7 @@
 import android.media.AudioManager;
 import android.media.AudioManager.OnAudioFocusChangeListener;
 import android.media.IVolumeController;
+import android.os.Handler;
 import android.os.IBinder;
 import android.os.RemoteException;
 import android.car.CarManagerBase;
@@ -31,6 +33,7 @@
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
+import java.lang.ref.WeakReference;
 
 /**
  * APIs for handling car specific audio stuffs.
@@ -121,6 +124,10 @@
 
     private final ICarAudio mService;
     private final AudioManager mAudioManager;
+    private final Handler mHandler;
+
+    private ParameterChangeCallback mParameterChangeCallback;
+    private OnParameterChangeListener mOnParameterChangeListener;
 
     /**
      * Get {@link AudioAttributes} relevant for the given usage in car.
@@ -367,19 +374,149 @@
         }
     }
 
+    /**
+     * Listener to monitor audio parameter changes.
+     * @hide
+     */
+    public interface OnParameterChangeListener {
+        /**
+         * Parameter changed.
+         * @param parameters Have format of key1=value1;key2=value2;...
+         */
+        void onParameterChange(String parameters);
+    }
+
+    /**
+     * Return array of keys supported in this system.
+     * Requires {@link android.car.Car.PERMISSION_CAR_CONTROL_AUDIO_SETTINGS} permission.
+     * The list is static and will not change.
+     * @return null if there is no audio parameters supported.
+     * @throws CarNotConnectedException
+     *
+     * @hide
+     */
+    public @Nullable String[] getParameterKeys() throws CarNotConnectedException {
+        try {
+            return mService.getParameterKeys();
+        } catch (RemoteException e) {
+            Log.e(CarLibLog.TAG_CAR, "getParameterKeys failed", e);
+            throw new CarNotConnectedException(e);
+        }
+    }
+
+    /**
+     * Set car specific audio parameters.
+     * Requires {@link android.car.Car.PERMISSION_CAR_CONTROL_AUDIO_SETTINGS} permission.
+     * Only keys listed from {@link #getParameterKeys()} should be used.
+     * @param parameters has format of key1=value1;key2=value2;...
+     * @throws CarNotConnectedException
+     *
+     * @hide
+     */
+    public void setParameters(String parameters) throws CarNotConnectedException {
+        try {
+            mService.setParameters(parameters);
+        } catch (RemoteException e) {
+            Log.e(CarLibLog.TAG_CAR, "setParameters failed", e);
+            throw new CarNotConnectedException(e);
+        }
+    }
+
+    /**
+     * Get parameters for the key passed.
+     * Requires {@link android.car.Car.PERMISSION_CAR_CONTROL_AUDIO_SETTINGS} permission.
+     * Only keys listed from {@link #getParameterKeys()} should be used.
+     * @param keys Keys to get value. Format is key1;key2;...
+     * @return Parameters in format of key1=value1;key2=value2;...
+     * @throws CarNotConnectedException
+     *
+     * @hide
+     */
+    public String getParameters(String keys) throws CarNotConnectedException {
+        try {
+            return mService.getParameters(keys);
+        } catch (RemoteException e) {
+            Log.e(CarLibLog.TAG_CAR, "getParameters failed", e);
+            throw new CarNotConnectedException(e);
+        }
+    }
+
+    /**
+     * Set listener to monitor audio parameter changes.
+     * Requires {@link android.car.Car.PERMISSION_CAR_CONTROL_AUDIO_SETTINGS} permission.
+     * @param listener Non-null listener will start monitoring. null listener will stop listening.
+     * @throws CarNotConnectedException
+     *
+     * @hide
+     */
+    public void setOnParameterChangeListener(OnParameterChangeListener listener)
+            throws CarNotConnectedException {
+        ParameterChangeCallback oldCb = null;
+        ParameterChangeCallback newCb = null;
+        synchronized (this) {
+            if (listener != null) {
+                if (mParameterChangeCallback != null) {
+                    oldCb = mParameterChangeCallback;
+                }
+                newCb = new ParameterChangeCallback(this);
+            }
+            mParameterChangeCallback = newCb;
+            mOnParameterChangeListener = listener;
+        }
+        try {
+            if (oldCb != null) {
+                mService.unregisterOnParameterChangeListener(oldCb);
+            }
+            if (newCb != null) {
+                mService.registerOnParameterChangeListener(newCb);
+            }
+        } catch (RemoteException e) {
+            Log.e(CarLibLog.TAG_CAR, "setOnParameterChangeListener failed", e);
+            throw new CarNotConnectedException(e);
+        }
+    }
+
     /** @hide */
     @Override
     public void onCarDisconnected() {
     }
 
     /** @hide */
-    public CarAudioManager(IBinder service, Context context) {
+    public CarAudioManager(IBinder service, Context context, Handler handler) {
         mService = ICarAudio.Stub.asInterface(service);
         mAudioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
+        mHandler = handler;
     }
 
     private AudioAttributes createAudioAttributes(int contentType, int usage) {
         AudioAttributes.Builder builder = new AudioAttributes.Builder();
         return builder.setContentType(contentType).setUsage(usage).build();
     }
+
+    private static class ParameterChangeCallback extends ICarAudioCallback.Stub {
+
+        private final WeakReference<CarAudioManager> mManager;
+
+        private ParameterChangeCallback(CarAudioManager manager) {
+            mManager = new WeakReference<>(manager);
+        }
+
+        @Override
+        public void onParameterChange(final String params) {
+            CarAudioManager manager = mManager.get();
+            if (manager == null) {
+                return;
+            }
+            final OnParameterChangeListener listener = manager.mOnParameterChangeListener;
+            if (listener == null) {
+                return;
+            }
+            manager.mHandler.post(new Runnable() {
+                @Override
+                public void run() {
+                    listener.onParameterChange(params);
+                }
+            });
+        }
+    }
 }
diff --git a/car-lib/src/android/car/media/ICarAudio.aidl b/car-lib/src/android/car/media/ICarAudio.aidl
index 1c07796..f43a378 100644
--- a/car-lib/src/android/car/media/ICarAudio.aidl
+++ b/car-lib/src/android/car/media/ICarAudio.aidl
@@ -16,6 +16,7 @@
 
 package android.car.media;
 
+import android.car.media.ICarAudioCallback;
 import android.media.AudioAttributes;
 import android.media.IVolumeController;
 
@@ -38,4 +39,9 @@
     AudioAttributes getAudioAttributesForExternalSource(in String externalSourceType) = 9;
     String[] getSupportedExternalSourceTypes() = 10;
     String[] getSupportedRadioTypes() = 11;
+    String[] getParameterKeys() = 12;
+    void setParameters(in String parameters) = 13;
+    String getParameters(in String keys) = 14;
+    void registerOnParameterChangeListener(in ICarAudioCallback callback) = 15;
+    void unregisterOnParameterChangeListener(in ICarAudioCallback callback) = 16;
 }
diff --git a/car-lib/src/android/car/media/ICarAudioCallback.aidl b/car-lib/src/android/car/media/ICarAudioCallback.aidl
new file mode 100644
index 0000000..7e01c71
--- /dev/null
+++ b/car-lib/src/android/car/media/ICarAudioCallback.aidl
@@ -0,0 +1,22 @@
+/*
+ * 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.
+ */
+
+package android.car.media;
+
+/** @hide */
+oneway interface ICarAudioCallback {
+    void onParameterChange(in String params) = 0;
+}
diff --git a/service/AndroidManifest.xml b/service/AndroidManifest.xml
index c23958e..4f372a3 100644
--- a/service/AndroidManifest.xml
+++ b/service/AndroidManifest.xml
@@ -100,6 +100,12 @@
         android:description="@string/car_permission_desc_audio_volume" />
 
     <permission
+        android:name="android.car.permission.CAR_CONTROL_AUDIO_SETTINGS"
+        android:protectionLevel="system|signature"
+        android:label="@string/car_permission_label_audio_settings"
+        android:description="@string/car_permission_desc_audio_settings" />
+
+    <permission
             android:name="android.car.permission.BIND_INSTRUMENT_CLUSTER_RENDERER_SERVICE"
             android:protectionLevel="signature"
             android:label="@string/car_permission_label_bind_instrument_cluster_rendering"
diff --git a/service/res/values/strings.xml b/service/res/values/strings.xml
index 7af6140..cf9b32a 100644
--- a/service/res/values/strings.xml
+++ b/service/res/values/strings.xml
@@ -57,6 +57,8 @@
     <string name="car_permission_label_projection">Car Projection</string>
     <!-- Permission text: apps can control car-audio-volume [CHAR LIMIT=NONE] -->
     <string name="car_permission_label_audio_volume">Car Audio Volume</string>
+    <!-- Permission text: apps can control car-audio-settings [CHAR LIMIT=NONE] -->
+    <string name="car_permission_label_audio_settings">Car Audio Settings</string>
     <!-- Permission text: apps can control car-projection [CHAR LIMIT=NONE] -->
     <string name="car_permission_desc_projection">Project phone interface on car display.</string>
     <string name="car_permission_label_mock_vehicle_hal">Emulate vehicle HAL</string>
@@ -65,6 +67,7 @@
         testing purpose.</string>
     <!-- Permission text: can adjust the audio volume on your car [CHAR LIMIT=NONE] -->
     <string name="car_permission_desc_audio_volume">Control your car\'s audio volume.</string>
+    <string name="car_permission_desc_audio_settings">Control your car\'s audio settings.</string>
     <string name="car_permission_label_control_app_blocking">Application blocking</string>
     <!-- Permission text: can emulate information from your car [CHAR LIMIT=NONE] -->
     <string name="car_permission_desc_control_app_blocking">Control application blocking while
diff --git a/service/src/com/android/car/BinderInterfaceContainer.java b/service/src/com/android/car/BinderInterfaceContainer.java
index 5c83565..a03b633 100644
--- a/service/src/com/android/car/BinderInterfaceContainer.java
+++ b/service/src/com/android/car/BinderInterfaceContainer.java
@@ -57,6 +57,10 @@
         mEventHandler = eventHandler;
     }
 
+    public BinderInterfaceContainer() {
+        mEventHandler = null;
+    }
+
     public void addBinder(T binderInterface) {
         IBinder binder = binderInterface.asBinder();
         synchronized (this) {
@@ -111,6 +115,10 @@
         }
     }
 
+    public synchronized int size() {
+        return mBinders.size();
+    }
+
     public synchronized void clear() {
         Collection<BinderInterface<T>> interfaces = getInterfaces();
         for (BinderInterface<T> bInterface : interfaces) {
diff --git a/service/src/com/android/car/CarAudioService.java b/service/src/com/android/car/CarAudioService.java
index 26004be..5404a6f 100644
--- a/service/src/com/android/car/CarAudioService.java
+++ b/service/src/com/android/car/CarAudioService.java
@@ -18,7 +18,9 @@
 import android.car.Car;
 import android.car.VehicleZoneUtil;
 import android.car.media.CarAudioManager;
+import android.car.media.CarAudioManager.OnParameterChangeListener;
 import android.car.media.ICarAudio;
+import android.car.media.ICarAudioCallback;
 import android.content.Context;
 import android.content.pm.PackageManager;
 import android.content.res.Resources;
@@ -36,6 +38,7 @@
 import android.os.HandlerThread;
 import android.os.Looper;
 import android.os.Message;
+import android.os.RemoteException;
 import android.util.Log;
 
 import com.android.car.hal.AudioHalService;
@@ -52,7 +55,7 @@
 import java.util.Set;
 
 public class CarAudioService extends ICarAudio.Stub implements CarServiceBase,
-        AudioHalFocusListener {
+        AudioHalFocusListener, OnParameterChangeListener {
 
     public interface AudioContextChangeListener {
         /**
@@ -166,6 +169,12 @@
             CarAudioAttributesUtil.getAudioAttributesForCarUsage(
                     CarAudioAttributesUtil.CAR_AUDIO_USAGE_CARSERVICE_CAR_PROXY);
 
+    @GuardedBy("mLock")
+    private final BinderInterfaceContainer<ICarAudioCallback> mAudioParamListeners =
+        new BinderInterfaceContainer<>();
+    @GuardedBy("mLock")
+    private HashSet<String> mAudioParamKeys;
+
     public CarAudioService(Context context, AudioHalService audioHal,
             CarInputService inputService) {
         mAudioHal = audioHal;
@@ -220,6 +229,7 @@
         }
         mAudioHal.setFocusListener(this);
         mAudioHal.setAudioRoutingPolicy(audioRoutingPolicy);
+        mAudioHal.setOnParameterChangeListener(this);
         // get call outside lock as it can take time
         HashSet<String> externalRadioRoutingTypes = new HashSet<>();
         HashSet<String> externalNonRadioRoutingTypes = new HashSet<>();
@@ -277,6 +287,7 @@
             mExternalNonRadioRoutingTypes = externalNonRadioRoutingTypes;
             mDefaultRadioRoutingType = defaultRadioRouting;
             Arrays.fill(mExternalRoutings, 0);
+            populateParameterKeysLocked();
         }
         mVolumeService.init();
 
@@ -404,6 +415,7 @@
         mAudioManager.abandonAudioFocus(mCarProxyAudioFocusListener);
         AudioPolicy audioPolicy;
         synchronized (mLock) {
+            mAudioParamKeys = null;
             mCurrentFocusState = FocusState.STATE_LOSS;
             mLastFocusRequestToCar = null;
             mTopFocusInfo = null;
@@ -464,6 +476,12 @@
                     writer.println("  type:" + entry.getKey() + " info:" + entry.getValue());
                 }
             }
+            if (mAudioParamKeys != null) {
+                writer.println("** Audio parameter keys**");
+                for (String key : mAudioParamKeys) {
+                    writer.println("  " + key);
+                }
+            }
         }
         writer.println("** Dump CarVolumeService**");
         mVolumeService.dump(writer);
@@ -575,6 +593,99 @@
         }
     }
 
+    @Override
+    public void onParameterChange(String parameters) {
+        for (BinderInterfaceContainer.BinderInterface<ICarAudioCallback> client :
+            mAudioParamListeners.getInterfaces()) {
+            try {
+                client.binderInterface.onParameterChange(parameters);
+            } catch (RemoteException e) {
+                // ignore. death handler will handle it.
+            }
+        }
+    }
+
+    @Override
+    public String[] getParameterKeys() {
+        enforceAudioSettingsPermission();
+        return mAudioHal.getAudioParameterKeys();
+    }
+
+    @Override
+    public void setParameters(String parameters) {
+        enforceAudioSettingsPermission();
+        if (parameters == null) {
+            throw new IllegalArgumentException("null parameters");
+        }
+        String[] keyValues = parameters.split(";");
+        synchronized (mLock) {
+            for (String keyValue : keyValues) {
+                String[] keyValuePair = keyValue.split("=");
+                if (keyValuePair.length != 2) {
+                    throw new IllegalArgumentException("Wrong audio parameter:" + parameters);
+                }
+                assertPamameterKeysLocked(keyValuePair[0]);
+            }
+        }
+        mAudioHal.setAudioParameters(parameters);
+    }
+
+    @Override
+    public String getParameters(String keys) {
+        enforceAudioSettingsPermission();
+        if (keys == null) {
+            throw new IllegalArgumentException("null keys");
+        }
+        synchronized (mLock) {
+            for (String key : keys.split(";")) {
+                assertPamameterKeysLocked(key);
+            }
+        }
+        return mAudioHal.getAudioParameters(keys);
+    }
+
+    @Override
+    public void registerOnParameterChangeListener(ICarAudioCallback callback) {
+        enforceAudioSettingsPermission();
+        if (callback == null) {
+            throw new IllegalArgumentException("callback null");
+        }
+        mAudioParamListeners.addBinder(callback);
+    }
+
+    @Override
+    public void unregisterOnParameterChangeListener(ICarAudioCallback callback) {
+        if (callback == null) {
+            return;
+        }
+        mAudioParamListeners.removeBinder(callback);
+    }
+
+    private void populateParameterKeysLocked() {
+        String[] keys = mAudioHal.getAudioParameterKeys();
+        mAudioParamKeys = new HashSet<>();
+        if (keys == null) { // not supported
+            return;
+        }
+        for (String key : keys) {
+            mAudioParamKeys.add(key);
+        }
+    }
+
+    private void assertPamameterKeysLocked(String key) {
+        if (!mAudioParamKeys.contains(key)) {
+            throw new IllegalArgumentException("Audio parameter not available:" + key);
+        }
+    }
+
+    private void enforceAudioSettingsPermission() {
+        if (mContext.checkCallingOrSelfPermission(Car.PERMISSION_CAR_CONTROL_AUDIO_SETTINGS)
+                != PackageManager.PERMISSION_GRANTED) {
+            throw new SecurityException(
+                    "requires permission " + Car.PERMISSION_CAR_CONTROL_AUDIO_SETTINGS);
+        }
+    }
+
     /**
      * API for system to control mute with lock.
      * @param mute
diff --git a/service/src/com/android/car/hal/AudioHalService.java b/service/src/com/android/car/hal/AudioHalService.java
index b3f8880..7096fa3 100644
--- a/service/src/com/android/car/hal/AudioHalService.java
+++ b/service/src/com/android/car/hal/AudioHalService.java
@@ -18,6 +18,7 @@
 import static android.hardware.vehicle.V2_0.VehicleProperty.AUDIO_EXT_ROUTING_HINT;
 import static android.hardware.vehicle.V2_0.VehicleProperty.AUDIO_FOCUS;
 import static android.hardware.vehicle.V2_0.VehicleProperty.AUDIO_HW_VARIANT;
+import static android.hardware.vehicle.V2_0.VehicleProperty.AUDIO_PARAMETERS;
 import static android.hardware.vehicle.V2_0.VehicleProperty.AUDIO_ROUTING_POLICY;
 import static android.hardware.vehicle.V2_0.VehicleProperty.AUDIO_VOLUME;
 import static android.hardware.vehicle.V2_0.VehicleProperty.AUDIO_VOLUME_LIMIT;
@@ -27,6 +28,7 @@
 import android.annotation.Nullable;
 import android.car.VehicleZoneUtil;
 import android.car.media.CarAudioManager;
+import android.car.media.CarAudioManager.OnParameterChangeListener;
 import android.hardware.vehicle.V2_0.VehicleAudioContextFlag;
 import android.hardware.vehicle.V2_0.VehicleAudioExtFocusFlag;
 import android.hardware.vehicle.V2_0.VehicleAudioFocusIndex;
@@ -174,6 +176,8 @@
 
     private final HashMap<Integer, VehiclePropConfig> mProperties = new HashMap<>();
 
+    private OnParameterChangeListener mOnParameterChangeListener;
+
     public AudioHalService(VehicleHal vehicleHal) {
         mVehicleHal = vehicleHal;
     }
@@ -603,6 +607,7 @@
                 case VehicleProperty.AUDIO_VOLUME_LIMIT:
                 case VehicleProperty.AUDIO_HW_VARIANT:
                 case VehicleProperty.AUDIO_EXT_ROUTING_HINT:
+                case VehicleProperty.AUDIO_PARAMETERS:
                     // TODO(pavelm): we don't have internal properties anymore.
 //                case VehicleProperty.INTERNAL_AUDIO_STREAM_STATE:
                     mProperties.put(p.prop, p);
@@ -616,20 +621,75 @@
     public void handleHalEvents(List<VehiclePropValue> values) {
         AudioHalFocusListener focusListener;
         AudioHalVolumeListener volumeListener;
+        OnParameterChangeListener parameterListener;
         synchronized (this) {
             focusListener = mFocusListener;
             volumeListener = mVolumeListener;
+            parameterListener = mOnParameterChangeListener;
         }
-        dispatchEventToListener(focusListener, volumeListener, values);
+        dispatchEventToListener(focusListener, volumeListener, parameterListener, values);
+    }
+
+    public String[] getAudioParameterKeys() {
+        VehiclePropConfig config;
+        synchronized (this) {
+            if (!isPropertySupportedLocked(AUDIO_PARAMETERS)) {
+                if (DBG) {
+                    Log.i(CarLog.TAG_AUDIO, "AUDIO_PARAMETERS is not supported");
+                }
+                return null;
+            }
+            config = mProperties.get(AUDIO_PARAMETERS);
+        }
+        return config.configString.split(";");
+    }
+
+    public void setAudioParameters(String parameters) {
+        synchronized (this) {
+            if (!isPropertySupportedLocked(AUDIO_PARAMETERS)) {
+                throw new IllegalStateException("VehicleProperty.AUDIO_PARAMETERS not supported");
+            }
+        }
+        VehiclePropValue value = new VehiclePropValue();
+        value.prop = AUDIO_PARAMETERS;
+        value.value.stringValue = parameters;
+        try {
+            mVehicleHal.set(value);
+        } catch (PropertyTimeoutException e) {
+            Log.e(CarLog.TAG_AUDIO, "Cannot write to VehicleProperty.AUDIO_EXT_ROUTING_HINT", e);
+        }
+    }
+
+    public String getAudioParameters(String keys) {
+        synchronized (this) {
+            if (!isPropertySupportedLocked(AUDIO_PARAMETERS)) {
+                throw new IllegalStateException("VehicleProperty.AUDIO_PARAMETERS not supported");
+            }
+        }
+        try {
+            VehiclePropValue requested = new VehiclePropValue();
+            requested.prop = AUDIO_PARAMETERS;
+            requested.value.stringValue = keys;
+            VehiclePropValue propValue = mVehicleHal.get(requested);
+            return propValue.value.stringValue;
+        } catch (PropertyTimeoutException e) {
+            Log.e(CarLog.TAG_AUDIO, "VehicleProperty.AUDIO_PARAMETERS not ready", e);
+            return new String("");
+        }
+    }
+
+    public synchronized void setOnParameterChangeListener(OnParameterChangeListener listener) {
+        mOnParameterChangeListener = listener;
     }
 
     private void dispatchEventToListener(AudioHalFocusListener focusListener,
             AudioHalVolumeListener volumeListener,
+            OnParameterChangeListener parameterListener,
             List<VehiclePropValue> values) {
         for (VehiclePropValue v : values) {
-            ArrayList<Integer> vec = v.value.int32Values;
             switch (v.prop) {
                 case VehicleProperty.AUDIO_FOCUS: {
+                    ArrayList<Integer> vec = v.value.int32Values;
                     int focusState = vec.get(VehicleAudioFocusIndex.FOCUS);
                     int streams = vec.get(VehicleAudioFocusIndex.STREAMS);
                     int externalFocus = vec.get(VehicleAudioFocusIndex.EXTERNAL_FOCUS_STATE);
@@ -649,6 +709,7 @@
 //                    }
 //                } break;
                 case AUDIO_VOLUME: {
+                    ArrayList<Integer> vec = v.value.int32Values;
                     int volume = vec.get(VehicleAudioVolumeIndex.INDEX_VOLUME);
                     int streamNum = vec.get(VehicleAudioVolumeIndex.INDEX_STREAM);
                     int volumeState = vec.get(VehicleAudioVolumeIndex.INDEX_STATE);
@@ -657,12 +718,19 @@
                     }
                 } break;
                 case AUDIO_VOLUME_LIMIT: {
+                    ArrayList<Integer> vec = v.value.int32Values;
                     int stream = vec.get(VehicleAudioVolumeLimitIndex.STREAM);
                     int maxVolume = vec.get(VehicleAudioVolumeLimitIndex.MAX_VOLUME);
                     if (volumeListener != null) {
                         volumeListener.onVolumeLimitChange(stream, maxVolume);
                     }
                 } break;
+                case AUDIO_PARAMETERS: {
+                    String params = v.value.stringValue;
+                    if (parameterListener != null) {
+                        parameterListener.onParameterChange(params);
+                    }
+                }
             }
         }
         values.clear();
diff --git a/tests/carservice_test/AndroidManifest.xml b/tests/carservice_test/AndroidManifest.xml
index bf48e88..6a1e2bf 100644
--- a/tests/carservice_test/AndroidManifest.xml
+++ b/tests/carservice_test/AndroidManifest.xml
@@ -23,6 +23,7 @@
     <uses-permission android:name="android.car.permission.CAR_RADIO" />
     <uses-permission android:name="android.car.permission.CONTROL_APP_BLOCKING" />
     <uses-permission android:name="android.car.permission.CAR_CONTROL_AUDIO_VOLUME" />
+    <uses-permission android:name="android.car.permission.CAR_CONTROL_AUDIO_SETTINGS" />
     <instrumentation android:name="android.test.InstrumentationTestRunner"
             android:targetPackage="com.android.car.test"
             android:label="Tests for Car APIs"/>
diff --git a/tests/carservice_test/src/com/android/car/test/CarAudioManagerTest.java b/tests/carservice_test/src/com/android/car/test/CarAudioManagerTest.java
new file mode 100644
index 0000000..20c43c6
--- /dev/null
+++ b/tests/carservice_test/src/com/android/car/test/CarAudioManagerTest.java
@@ -0,0 +1,225 @@
+/*
+ * 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.
+ */
+package com.android.car.test;
+
+import android.car.Car;
+import android.car.media.CarAudioManager;
+import android.hardware.vehicle.V2_0.VehiclePropValue;
+import android.hardware.vehicle.V2_0.VehicleProperty;
+import android.hardware.vehicle.V2_0.VehiclePropertyAccess;
+import android.os.SystemClock;
+import android.test.suitebuilder.annotation.MediumTest;
+
+import com.android.car.vehiclehal.VehiclePropValueBuilder;
+import com.android.car.vehiclehal.test.MockedVehicleHal.VehicleHalPropertyHandler;
+
+import java.util.concurrent.Semaphore;
+import java.util.concurrent.TimeUnit;
+
+@MediumTest
+public class CarAudioManagerTest extends MockedCarTestBase {
+    private final AudioParametersPropertyHandler mAudioParametersPropertyHandler =
+            new AudioParametersPropertyHandler();
+    CarAudioManager mCarAudioManager;
+
+    @Override
+    protected synchronized void configureMockedHal() {
+        addProperty(VehicleProperty.AUDIO_PARAMETERS, mAudioParametersPropertyHandler)
+	    .setAccess(VehiclePropertyAccess.READ_WRITE)
+	    .setConfigString("com.android.test.param1;com.android.test.param2");
+    }
+
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+        mCarAudioManager = (CarAudioManager) getCar().getCarManager(
+                Car.AUDIO_SERVICE);
+        assertNotNull(mCarAudioManager);
+    }
+
+    public void testAudioParamConfig() throws Exception {
+        String[] keys = mCarAudioManager.getParameterKeys();
+        assertNotNull(keys);
+        assertEquals(2, keys.length);
+        assertEquals("com.android.test.param1", keys[0]);
+        assertEquals("com.android.test.param2", keys[1]);
+    }
+
+    public void testAudioParamSet() throws Exception {
+        try {
+            mCarAudioManager.setParameters(null);
+            fail();
+        } catch (IllegalArgumentException e) {
+            // expected
+        }
+
+        try {
+            mCarAudioManager.setParameters("com.android.test.param3=3");
+            fail();
+        } catch (IllegalArgumentException e) {
+            // expected
+        }
+
+        final String SET_OK1 = "com.android.test.param1=1";
+        mCarAudioManager.setParameters(SET_OK1);
+        mAudioParametersPropertyHandler.waitForSet(DEFAULT_WAIT_TIMEOUT_MS, SET_OK1);
+
+        final String SET_OK2 = "com.android.test.param1=1;com.android.test.param2=2";
+        mCarAudioManager.setParameters(SET_OK2);
+        mAudioParametersPropertyHandler.waitForSet(DEFAULT_WAIT_TIMEOUT_MS, SET_OK2);
+    }
+
+    public void testAudioParamGet() throws Exception {
+        try {
+            mCarAudioManager.getParameters(null);
+            fail();
+        } catch (IllegalArgumentException e) {
+            // expected
+        }
+
+        try {
+            mCarAudioManager.getParameters("com.android.test.param3");
+            fail();
+        } catch (IllegalArgumentException e) {
+            // expected
+        }
+
+        try {
+            mCarAudioManager.getParameters("com.android.test.param1;com.android.test.param3");
+            fail();
+        } catch (IllegalArgumentException e) {
+            // expected
+        }
+
+        final String GET_RESP1 = "com.android.test.param1=1";
+        mAudioParametersPropertyHandler.setValueForGet(GET_RESP1);
+        String get1 = mCarAudioManager.getParameters("com.android.test.param1");
+        assertEquals(GET_RESP1, get1);
+
+        final String GET_RESP2 = "com.android.test.param1=1;com.android.test.param2=2";
+        mAudioParametersPropertyHandler.setValueForGet(GET_RESP2);
+        String get2 = mCarAudioManager.getParameters(
+                "com.android.test.param1;com.android.test.param2");
+        assertEquals(GET_RESP2, get2);
+    }
+
+    public void testAudioParamChangeListener() throws Exception {
+        AudioParamListener listener1 = new AudioParamListener();
+        AudioParamListener listener2 = new AudioParamListener();
+
+        mCarAudioManager.setOnParameterChangeListener(listener1);
+        final String EVENT1 = "com.android.test.param1=10";
+        sendAudioParamChange(EVENT1);
+        listener1.waitForChange(DEFAULT_WAIT_TIMEOUT_MS, EVENT1);
+
+        mCarAudioManager.setOnParameterChangeListener(listener2);
+        listener1.clearParameter();
+        final String EVENT2 = "com.android.test.param1=20;com.android.test.param2=10";
+        sendAudioParamChange(EVENT2);
+        listener2.waitForChange(DEFAULT_WAIT_TIMEOUT_MS, EVENT2);
+        listener1.assertParameter(null);
+
+        mCarAudioManager.setOnParameterChangeListener(null);
+        listener2.clearParameter();
+        sendAudioParamChange(EVENT1);
+        Thread.sleep(200);
+        listener1.assertParameter(null);
+        listener2.assertParameter(null);
+    }
+
+    private void sendAudioParamChange(String params) {
+        getMockedVehicleHal().injectEvent(
+                VehiclePropValueBuilder.newBuilder(VehicleProperty.AUDIO_PARAMETERS)
+                .setTimestamp(SystemClock.elapsedRealtimeNanos())
+                .setStringValue(params)
+                .build());
+    }
+
+    static class AudioParametersPropertyHandler implements VehicleHalPropertyHandler {
+        private final Semaphore mSetWaitSemaphore = new Semaphore(0);
+
+        private String mValueSet;
+        private String mGetResponse;
+
+        public void waitForSet(long waitTimeMs, String expected) throws Exception {
+            mSetWaitSemaphore.tryAcquire(waitTimeMs, TimeUnit.MILLISECONDS);
+            synchronized (this) {
+                assertEquals(expected, mValueSet);
+            }
+        }
+
+        public synchronized void setValueForGet(String value) {
+            mGetResponse = value;
+        }
+
+        @Override
+        public void onPropertySet(VehiclePropValue value) {
+            assertEquals(VehicleProperty.AUDIO_PARAMETERS, value.prop);
+            String setValue = value.value.stringValue;
+            synchronized (this) {
+                mValueSet = setValue;
+            }
+            mSetWaitSemaphore.release();
+        }
+
+        @Override
+        public VehiclePropValue onPropertyGet(VehiclePropValue value) {
+            assertEquals(VehicleProperty.AUDIO_PARAMETERS, value.prop);
+            String response;
+            synchronized (this) {
+                response = mGetResponse;
+            }
+            return VehiclePropValueBuilder.newBuilder(VehicleProperty.AUDIO_PARAMETERS)
+                    .setTimestamp(SystemClock.elapsedRealtimeNanos())
+                    .setStringValue(mGetResponse)
+                    .build();
+        }
+
+        @Override
+        public void onPropertySubscribe(int property, int zones, float sampleRate) {
+            assertEquals(VehicleProperty.AUDIO_PARAMETERS, property);
+        }
+
+        @Override
+        public void onPropertyUnsubscribe(int property) {
+            assertEquals(VehicleProperty.AUDIO_PARAMETERS, property);
+        }
+    }
+
+    static class AudioParamListener implements CarAudioManager.OnParameterChangeListener {
+        private String mParameter;
+        private final Semaphore mChangeWaitSemaphore = new Semaphore(0);
+
+        @Override
+        public void onParameterChange(String parameters) {
+            mParameter = parameters;
+            mChangeWaitSemaphore.release();
+        }
+
+        public void waitForChange(long waitTimeMs, String expected) throws Exception {
+            mChangeWaitSemaphore.tryAcquire(waitTimeMs, TimeUnit.MILLISECONDS);
+            assertEquals(expected, mParameter);
+        }
+
+        public void clearParameter() {
+            mParameter = null;
+        }
+
+        public void assertParameter(String expected) {
+            assertEquals(expected, mParameter);
+        }
+    }
+}