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