blob: 14b83197e6b12cfe935addddd2d686ebc94bfe52 [file] [log] [blame]
/*
* Copyright (C) 2015 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.hal;
import static android.hardware.automotive.vehicle.V2_0.VehicleProperty.AUDIO_EXT_ROUTING_HINT;
import static android.hardware.automotive.vehicle.V2_0.VehicleProperty.AUDIO_FOCUS;
import static android.hardware.automotive.vehicle.V2_0.VehicleProperty.AUDIO_HW_VARIANT;
import static android.hardware.automotive.vehicle.V2_0.VehicleProperty.AUDIO_PARAMETERS;
import static android.hardware.automotive.vehicle.V2_0.VehicleProperty.AUDIO_ROUTING_POLICY;
import static android.hardware.automotive.vehicle.V2_0.VehicleProperty.AUDIO_STREAM_STATE;
import static android.hardware.automotive.vehicle.V2_0.VehicleProperty.AUDIO_VOLUME;
import static android.hardware.automotive.vehicle.V2_0.VehicleProperty.AUDIO_VOLUME_LIMIT;
import static com.android.car.CarServiceUtils.toIntArray;
import android.annotation.Nullable;
import android.car.VehicleZoneUtil;
import android.car.media.CarAudioManager;
import android.car.media.CarAudioManager.OnParameterChangeListener;
import android.hardware.automotive.vehicle.V2_0.SubscribeFlags;
import android.hardware.automotive.vehicle.V2_0.VehicleAudioContextFlag;
import android.hardware.automotive.vehicle.V2_0.VehicleAudioExtFocusFlag;
import android.hardware.automotive.vehicle.V2_0.VehicleAudioFocusIndex;
import android.hardware.automotive.vehicle.V2_0.VehicleAudioFocusRequest;
import android.hardware.automotive.vehicle.V2_0.VehicleAudioFocusState;
import android.hardware.automotive.vehicle.V2_0.VehicleAudioHwVariantConfigFlag;
import android.hardware.automotive.vehicle.V2_0.VehicleAudioRoutingPolicyIndex;
import android.hardware.automotive.vehicle.V2_0.VehicleAudioVolumeCapabilityFlag;
import android.hardware.automotive.vehicle.V2_0.VehicleAudioVolumeIndex;
import android.hardware.automotive.vehicle.V2_0.VehicleAudioVolumeLimitIndex;
import android.hardware.automotive.vehicle.V2_0.VehiclePropConfig;
import android.hardware.automotive.vehicle.V2_0.VehiclePropValue;
import android.hardware.automotive.vehicle.V2_0.VehicleProperty;
import android.text.TextUtils;
import android.util.Log;
import com.android.car.AudioRoutingPolicy;
import com.android.car.CarAudioAttributesUtil;
import com.android.car.CarLog;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class AudioHalService extends HalServiceBase {
public static final int VEHICLE_AUDIO_FOCUS_REQUEST_INVALID = -1;
public static final int VEHICLE_AUDIO_FOCUS_REQUEST_GAIN =
VehicleAudioFocusRequest.REQUEST_GAIN;
public static final int VEHICLE_AUDIO_FOCUS_REQUEST_GAIN_TRANSIENT =
VehicleAudioFocusRequest.REQUEST_GAIN_TRANSIENT;
public static final int VEHICLE_AUDIO_FOCUS_REQUEST_GAIN_TRANSIENT_MAY_DUCK =
VehicleAudioFocusRequest.REQUEST_GAIN_TRANSIENT_MAY_DUCK;
public static final int VEHICLE_AUDIO_FOCUS_REQUEST_GAIN_TRANSIENT_NO_DUCK =
VehicleAudioFocusRequest.REQUEST_GAIN_TRANSIENT_NO_DUCK;
public static final int VEHICLE_AUDIO_FOCUS_REQUEST_RELEASE =
VehicleAudioFocusRequest.REQUEST_RELEASE;
public static final int VEHICLE_AUDIO_FOCUS_STATE_INVALID = -1;
public static final int VEHICLE_AUDIO_FOCUS_STATE_GAIN =
VehicleAudioFocusState.STATE_GAIN;
public static final int VEHICLE_AUDIO_FOCUS_STATE_GAIN_TRANSIENT =
VehicleAudioFocusState.STATE_GAIN_TRANSIENT;
public static final int VEHICLE_AUDIO_FOCUS_STATE_LOSS_TRANSIENT_CAN_DUCK =
VehicleAudioFocusState.STATE_LOSS_TRANSIENT_CAN_DUCK;
public static final int VEHICLE_AUDIO_FOCUS_STATE_LOSS_TRANSIENT =
VehicleAudioFocusState.STATE_LOSS_TRANSIENT;
public static final int VEHICLE_AUDIO_FOCUS_STATE_LOSS =
VehicleAudioFocusState.STATE_LOSS;
public static final int VEHICLE_AUDIO_FOCUS_STATE_LOSS_TRANSIENT_EXLCUSIVE =
VehicleAudioFocusState.STATE_LOSS_TRANSIENT_EXLCUSIVE;
public static final int VEHICLE_AUDIO_STREAM_STATE_STOPPED = 0;
public static final int VEHICLE_AUDIO_STREAM_STATE_STARTED = 1;
public static final int VEHICLE_AUDIO_EXT_FOCUS_NONE_FLAG =
VehicleAudioExtFocusFlag.NONE_FLAG;
public static final int VEHICLE_AUDIO_EXT_FOCUS_CAR_PERMANENT_FLAG =
VehicleAudioExtFocusFlag.PERMANENT_FLAG;
public static final int VEHICLE_AUDIO_EXT_FOCUS_CAR_TRANSIENT_FLAG =
VehicleAudioExtFocusFlag.TRANSIENT_FLAG;
public static final int VEHICLE_AUDIO_EXT_FOCUS_CAR_PLAY_ONLY_FLAG =
VehicleAudioExtFocusFlag.PLAY_ONLY_FLAG;
public static final int VEHICLE_AUDIO_EXT_FOCUS_CAR_MUTE_MEDIA_FLAG =
VehicleAudioExtFocusFlag.MUTE_MEDIA_FLAG;
public static final int STREAM_NUM_DEFAULT = 0;
public static final int FOCUS_STATE_ARRAY_INDEX_STATE = 0;
public static final int FOCUS_STATE_ARRAY_INDEX_STREAMS = 1;
public static final int FOCUS_STATE_ARRAY_INDEX_EXTERNAL_FOCUS = 2;
public static final int AUDIO_CONTEXT_MUSIC_FLAG =
VehicleAudioContextFlag.MUSIC_FLAG;
public static final int AUDIO_CONTEXT_NAVIGATION_FLAG =
VehicleAudioContextFlag.NAVIGATION_FLAG;
public static final int AUDIO_CONTEXT_VOICE_COMMAND_FLAG =
VehicleAudioContextFlag.VOICE_COMMAND_FLAG;
public static final int AUDIO_CONTEXT_CALL_FLAG =
VehicleAudioContextFlag.CALL_FLAG;
public static final int AUDIO_CONTEXT_ALARM_FLAG =
VehicleAudioContextFlag.ALARM_FLAG;
public static final int AUDIO_CONTEXT_NOTIFICATION_FLAG =
VehicleAudioContextFlag.NOTIFICATION_FLAG;
public static final int AUDIO_CONTEXT_UNKNOWN_FLAG =
VehicleAudioContextFlag.UNKNOWN_FLAG;
public static final int AUDIO_CONTEXT_SAFETY_ALERT_FLAG =
VehicleAudioContextFlag.SAFETY_ALERT_FLAG;
public static final int AUDIO_CONTEXT_RADIO_FLAG =
VehicleAudioContextFlag.RADIO_FLAG;
public static final int AUDIO_CONTEXT_CD_ROM_FLAG =
VehicleAudioContextFlag.CD_ROM_FLAG;
public static final int AUDIO_CONTEXT_AUX_AUDIO_FLAG =
VehicleAudioContextFlag.AUX_AUDIO_FLAG;
public static final int AUDIO_CONTEXT_SYSTEM_SOUND_FLAG =
VehicleAudioContextFlag.SYSTEM_SOUND_FLAG;
public static final int AUDIO_CONTEXT_EXT_SOURCE_FLAG =
VehicleAudioContextFlag.EXT_SOURCE_FLAG;
public interface AudioHalFocusListener {
/**
* Audio focus change from car.
* @param focusState
* @param streams
* @param externalFocus Flags of active external audio focus.
* 0 means no external audio focus.
*/
void onFocusChange(int focusState, int streams, int externalFocus);
/**
* Stream state change (start / stop) from android
* @param streamNumber stream number like 0, 1, ...
* @param streamActive Whether the stream is active or not.
*/
void onStreamStatusChange(int streamNumber, boolean streamActive);
}
public interface AudioHalVolumeListener {
/**
* Audio volume change from car.
* @param streamNumber
* @param volume
* @param volumeState
*/
void onVolumeChange(int streamNumber, int volume, int volumeState);
/**
* Volume limit change from car.
* @param streamNumber
* @param volume
*/
void onVolumeLimitChange(int streamNumber, int volume);
}
private static final boolean DBG = false;
private final VehicleHal mVehicleHal;
private AudioHalFocusListener mFocusListener;
private AudioHalVolumeListener mVolumeListener;
private int mVariant;
private final HashMap<Integer, VehiclePropConfig> mProperties = new HashMap<>();
private OnParameterChangeListener mOnParameterChangeListener;
public AudioHalService(VehicleHal vehicleHal) {
mVehicleHal = vehicleHal;
}
public synchronized void setFocusListener(AudioHalFocusListener focusListener) {
mFocusListener = focusListener;
}
public synchronized void setVolumeListener(AudioHalVolumeListener volumeListener) {
mVolumeListener = volumeListener;
}
public void setAudioRoutingPolicy(AudioRoutingPolicy policy) {
if (!mVehicleHal.isPropertySupported(VehicleProperty.AUDIO_ROUTING_POLICY)) {
Log.w(CarLog.TAG_AUDIO,
"Vehicle HAL did not implement VehicleProperty.AUDIO_ROUTING_POLICY");
return;
}
int[] policyToSet = new int[2];
for (int i = 0; i < policy.getPhysicalStreamsCount(); i++) {
policyToSet[VehicleAudioRoutingPolicyIndex.STREAM] = i;
int contexts = 0;
for (int logicalStream : policy.getLogicalStreamsForPhysicalStream(i)) {
contexts |= logicalStreamToHalContextType(logicalStream);
}
policyToSet[VehicleAudioRoutingPolicyIndex.CONTEXTS] = contexts;
try {
mVehicleHal.set(AUDIO_ROUTING_POLICY).to(policyToSet);
} catch (PropertyTimeoutException e) {
Log.e(CarLog.TAG_AUDIO, "Cannot write to VehicleProperty.AUDIO_ROUTING_POLICY", e);
}
}
}
/**
* Returns the volume limits of a stream. Returns null if max value wasn't defined for
* AUDIO_VOLUME property.
*/
@Nullable
public synchronized Integer getStreamMaxVolume(int stream) {
VehiclePropConfig config = mProperties.get(VehicleProperty.AUDIO_VOLUME);
if (config == null) {
throw new IllegalStateException("VehicleProperty.AUDIO_VOLUME not supported");
}
int supportedContext = getSupportedAudioVolumeContexts();
int MAX_VALUES_FIRST_ELEMENT_INDEX = 4;
ArrayList<Integer> maxValues = new ArrayList<>();
for (int i = MAX_VALUES_FIRST_ELEMENT_INDEX; i < config.configArray.size(); i++) {
maxValues.add(config.configArray.get(i));
}
Integer result = null;
if (supportedContext != 0) {
int index = VehicleZoneUtil.zoneToIndex(supportedContext, stream);
if (index < maxValues.size()) {
result = maxValues.get(index);
}
} else {
if (stream < maxValues.size()) {
result = maxValues.get(stream);
}
}
if (result == null) {
Log.e(CarLog.TAG_AUDIO, "No min/max volume found in vehicle" +
" prop config for stream: " + stream);
}
return result;
}
/**
* Convert car audio manager stream type (usage) into audio context type.
*/
public static int logicalStreamToHalContextType(int logicalStream) {
return logicalStreamWithExtTypeToHalContextType(logicalStream, null);
}
public static int logicalStreamWithExtTypeToHalContextType(int logicalStream, String extType) {
switch (logicalStream) {
case CarAudioManager.CAR_AUDIO_USAGE_RADIO:
return VehicleAudioContextFlag.RADIO_FLAG;
case CarAudioManager.CAR_AUDIO_USAGE_VOICE_CALL:
return VehicleAudioContextFlag.CALL_FLAG;
case CarAudioManager.CAR_AUDIO_USAGE_MUSIC:
return VehicleAudioContextFlag.MUSIC_FLAG;
case CarAudioManager.CAR_AUDIO_USAGE_NAVIGATION_GUIDANCE:
return VehicleAudioContextFlag.NAVIGATION_FLAG;
case CarAudioManager.CAR_AUDIO_USAGE_VOICE_COMMAND:
return VehicleAudioContextFlag.VOICE_COMMAND_FLAG;
case CarAudioManager.CAR_AUDIO_USAGE_ALARM:
return VehicleAudioContextFlag.ALARM_FLAG;
case CarAudioManager.CAR_AUDIO_USAGE_NOTIFICATION:
return VehicleAudioContextFlag.NOTIFICATION_FLAG;
case CarAudioManager.CAR_AUDIO_USAGE_SYSTEM_SAFETY_ALERT:
return VehicleAudioContextFlag.SAFETY_ALERT_FLAG;
case CarAudioManager.CAR_AUDIO_USAGE_SYSTEM_SOUND:
return VehicleAudioContextFlag.SYSTEM_SOUND_FLAG;
case CarAudioManager.CAR_AUDIO_USAGE_DEFAULT:
return VehicleAudioContextFlag.UNKNOWN_FLAG;
case CarAudioManager.CAR_AUDIO_USAGE_EXTERNAL_AUDIO_SOURCE:
if (extType != null) {
switch (extType) {
case CarAudioManager.CAR_EXTERNAL_SOURCE_TYPE_CD_DVD:
return AudioHalService.AUDIO_CONTEXT_CD_ROM_FLAG;
case CarAudioManager.CAR_EXTERNAL_SOURCE_TYPE_AUX_IN0:
case CarAudioManager.CAR_EXTERNAL_SOURCE_TYPE_AUX_IN1:
return AudioHalService.AUDIO_CONTEXT_AUX_AUDIO_FLAG;
default:
if (extType.startsWith("RADIO_")) {
return VehicleAudioContextFlag.RADIO_FLAG;
} else {
return AudioHalService.AUDIO_CONTEXT_EXT_SOURCE_FLAG;
}
}
} else { // no external source specified. fall back to radio
return VehicleAudioContextFlag.RADIO_FLAG;
}
case CarAudioAttributesUtil.CAR_AUDIO_USAGE_CARSERVICE_BOTTOM:
case CarAudioAttributesUtil.CAR_AUDIO_USAGE_CARSERVICE_CAR_PROXY:
case CarAudioAttributesUtil.CAR_AUDIO_USAGE_CARSERVICE_MEDIA_MUTE:
// internal tag not associated with any stream
return 0;
default:
Log.w(CarLog.TAG_AUDIO, "Unknown logical stream:" + logicalStream);
return 0;
}
}
/**
* Converts car audio context type to car stream usage.
*/
public static int carContextToCarUsage(int carContext) {
switch (carContext) {
case VehicleAudioContextFlag.MUSIC_FLAG:
return CarAudioManager.CAR_AUDIO_USAGE_MUSIC;
case VehicleAudioContextFlag.NAVIGATION_FLAG:
return CarAudioManager.CAR_AUDIO_USAGE_NAVIGATION_GUIDANCE;
case VehicleAudioContextFlag.ALARM_FLAG:
return CarAudioManager.CAR_AUDIO_USAGE_ALARM;
case VehicleAudioContextFlag.VOICE_COMMAND_FLAG:
return CarAudioManager.CAR_AUDIO_USAGE_VOICE_COMMAND;
case VehicleAudioContextFlag.AUX_AUDIO_FLAG:
return CarAudioManager.CAR_AUDIO_USAGE_EXTERNAL_AUDIO_SOURCE;
case VehicleAudioContextFlag.CALL_FLAG:
return CarAudioManager.CAR_AUDIO_USAGE_VOICE_CALL;
case VehicleAudioContextFlag.CD_ROM_FLAG:
return CarAudioManager.CAR_AUDIO_USAGE_EXTERNAL_AUDIO_SOURCE;
case VehicleAudioContextFlag.NOTIFICATION_FLAG:
return CarAudioManager.CAR_AUDIO_USAGE_NOTIFICATION;
case VehicleAudioContextFlag.RADIO_FLAG:
return CarAudioManager.CAR_AUDIO_USAGE_RADIO;
case VehicleAudioContextFlag.SAFETY_ALERT_FLAG:
return CarAudioManager.CAR_AUDIO_USAGE_SYSTEM_SAFETY_ALERT;
case VehicleAudioContextFlag.SYSTEM_SOUND_FLAG:
return CarAudioManager.CAR_AUDIO_USAGE_SYSTEM_SOUND;
case VehicleAudioContextFlag.UNKNOWN_FLAG:
return CarAudioManager.CAR_AUDIO_USAGE_DEFAULT;
case VehicleAudioContextFlag.EXT_SOURCE_FLAG:
return CarAudioManager.CAR_AUDIO_USAGE_EXTERNAL_AUDIO_SOURCE;
default:
Log.w(CarLog.TAG_AUDIO, "Unknown car context:" + carContext);
return 0;
}
}
public void requestAudioFocusChange(int request, int streams, int audioContexts) {
requestAudioFocusChange(request, streams, VEHICLE_AUDIO_EXT_FOCUS_NONE_FLAG, audioContexts);
}
public void requestAudioFocusChange(int request, int streams, int extFocus, int audioContexts) {
int[] payload = { request, streams, extFocus, audioContexts };
try {
mVehicleHal.set(AUDIO_FOCUS).to(payload);
} catch (PropertyTimeoutException e) {
Log.e(CarLog.TAG_AUDIO, "Cannot write to VehicleProperty.AUDIO_FOCUS", e);
// focus timeout will reset it anyway
}
}
public void setStreamVolume(int streamType, int index) {
int[] payload = {streamType, index, 0};
try {
mVehicleHal.set(VehicleProperty.AUDIO_VOLUME).to(payload);
} catch (PropertyTimeoutException e) {
Log.e(CarLog.TAG_AUDIO, "Cannot write to VehicleProperty.AUDIO_VOLUME", e);
//TODO should reset volume, bug: 32096870
}
}
public int getStreamVolume(int stream) {
int[] volume = {stream, 0, 0};
VehiclePropValue requestedStreamVolume = new VehiclePropValue();
requestedStreamVolume.prop = VehicleProperty.AUDIO_VOLUME;
requestedStreamVolume.value.int32Values.addAll(Arrays.asList(stream, 0 , 0));
VehiclePropValue propValue;
try {
propValue = mVehicleHal.get(requestedStreamVolume);
} catch (PropertyTimeoutException e) {
Log.e(CarLog.TAG_AUDIO, "VehicleProperty.AUDIO_VOLUME not ready", e);
return 0;
}
if (propValue.value.int32Values.size() != 3) {
Log.e(CarLog.TAG_AUDIO, "returned value not valid");
throw new IllegalStateException("Invalid preset returned from service: "
+ Arrays.toString(propValue.value.int32Values.toArray()));
}
int retStreamNum = propValue.value.int32Values.get(0);
int retVolume = propValue.value.int32Values.get(1);
int retVolumeState = propValue.value.int32Values.get(2);
if (retStreamNum != stream) {
Log.e(CarLog.TAG_AUDIO, "Stream number is not the same: "
+ stream + " vs " + retStreamNum);
throw new IllegalStateException("Stream number is not the same");
}
return retVolume;
}
public synchronized int getHwVariant() {
return mVariant;
}
public synchronized boolean isRadioExternal() {
VehiclePropConfig config = mProperties.get(VehicleProperty.AUDIO_HW_VARIANT);
if (config == null) {
return true;
}
return (config.configArray.get(0)
& VehicleAudioHwVariantConfigFlag.INTERNAL_RADIO_FLAG) == 0;
}
public synchronized boolean isFocusSupported() {
return isPropertySupportedLocked(AUDIO_FOCUS);
}
public synchronized boolean isAudioVolumeSupported() {
return isPropertySupportedLocked(VehicleProperty.AUDIO_VOLUME);
}
public synchronized int getSupportedAudioVolumeContexts() {
if (!isPropertySupportedLocked(VehicleProperty.AUDIO_VOLUME)) {
throw new IllegalStateException("VehicleProperty.AUDIO_VOLUME not supported");
}
VehiclePropConfig config = mProperties.get(VehicleProperty.AUDIO_VOLUME);
return config.configArray.get(0);
}
/**
* Whether external audio module can memorize logical audio volumes or not.
* @return
*/
public synchronized boolean isExternalAudioVolumePersistent() {
if (!isPropertySupportedLocked(VehicleProperty.AUDIO_VOLUME)) {
throw new IllegalStateException("VehicleProperty.AUDIO_VOLUME not supported");
}
VehiclePropConfig config = mProperties.get(VehicleProperty.AUDIO_VOLUME);
if (config.configArray.get(0) == 0) { // physical streams only
return false;
}
if ((config.configArray.get(1)
& VehicleAudioVolumeCapabilityFlag.PERSISTENT_STORAGE) != 0) {
return true;
}
return false;
}
public synchronized boolean isAudioVolumeLimitSupported() {
return isPropertySupportedLocked(AUDIO_VOLUME_LIMIT);
}
public synchronized boolean isAudioVolumeMasterOnly() {
if (!isPropertySupportedLocked(VehicleProperty.AUDIO_VOLUME)) {
throw new IllegalStateException("VehicleProperty.AUDIO_VOLUME not supported");
}
VehiclePropConfig config = mProperties.get(
AUDIO_VOLUME);
if ((config.configArray.get(1) &
VehicleAudioVolumeCapabilityFlag.MASTER_VOLUME_ONLY)
!= 0) {
return true;
}
return false;
}
/**
* Get the current audio focus state.
* @return 0: focusState, 1: streams, 2: externalFocus
*/
public int[] getCurrentFocusState() {
if (!isFocusSupported()) {
return new int[] { VEHICLE_AUDIO_FOCUS_STATE_GAIN, 0xffffffff, 0};
}
try {
VehiclePropValue propValue = mVehicleHal.get(VehicleProperty.AUDIO_FOCUS);
return toIntArray(propValue.value.int32Values);
} catch (PropertyTimeoutException e) {
Log.e(CarLog.TAG_AUDIO, "VehicleProperty.AUDIO_HW_VARIANT not ready", e);
return new int[] { VEHICLE_AUDIO_FOCUS_STATE_LOSS, 0x0, 0};
}
}
public static class ExtRoutingSourceInfo {
/** Represents an external route which will not disable any physical stream in android side.
*/
public static final int NO_DISABLED_PHYSICAL_STREAM = -1;
/** Bit position of this source in vhal */
public final int bitPosition;
/**
* Physical stream replaced by this routing. will be {@link #NO_DISABLED_PHYSICAL_STREAM}
* if no physical stream for android is replaced by this routing.
*/
public final int physicalStreamNumber;
public ExtRoutingSourceInfo(int bitPosition, int physycalStreamNumber) {
this.bitPosition = bitPosition;
this.physicalStreamNumber = physycalStreamNumber;
}
@Override
public String toString() {
return "[bitPosition=" + bitPosition + ", physicalStreamNumber="
+ physicalStreamNumber + "]";
}
}
/**
* Get external audio routing types from AUDIO_EXT_ROUTING_HINT property.
*
* @return null if AUDIO_EXT_ROUTING_HINT is not supported.
*/
public Map<String, ExtRoutingSourceInfo> getExternalAudioRoutingTypes() {
VehiclePropConfig config;
synchronized (this) {
if (!isPropertySupportedLocked(AUDIO_EXT_ROUTING_HINT)) {
if (DBG) {
Log.i(CarLog.TAG_AUDIO, "AUDIO_EXT_ROUTING_HINT is not supported");
}
return null;
}
config = mProperties.get(AUDIO_EXT_ROUTING_HINT);
}
if (TextUtils.isEmpty(config.configString)) {
Log.w(CarLog.TAG_AUDIO, "AUDIO_EXT_ROUTING_HINT with empty config string");
return null;
}
Map<String, ExtRoutingSourceInfo> routingTypes = new HashMap<>();
String configString = config.configString;
if (DBG) {
Log.i(CarLog.TAG_AUDIO, "AUDIO_EXT_ROUTING_HINT config string:" + configString);
}
String[] routes = configString.split(",");
for (String routeString : routes) {
String[] tokens = routeString.split(":");
int bitPosition = 0;
String name = null;
int physicalStreamNumber = ExtRoutingSourceInfo.NO_DISABLED_PHYSICAL_STREAM;
if (tokens.length == 2) {
bitPosition = Integer.parseInt(tokens[0]);
name = tokens[1];
} else if (tokens.length == 3) {
bitPosition = Integer.parseInt(tokens[0]);
name = tokens[1];
physicalStreamNumber = Integer.parseInt(tokens[2]);
} else {
Log.w(CarLog.TAG_AUDIO, "AUDIO_EXT_ROUTING_HINT has wrong entry:" +
routeString);
continue;
}
routingTypes.put(name, new ExtRoutingSourceInfo(bitPosition, physicalStreamNumber));
}
return routingTypes;
}
public void setExternalRoutingSource(int[] externalRoutings) {
try {
mVehicleHal.set(AUDIO_EXT_ROUTING_HINT).to(externalRoutings);
} catch (PropertyTimeoutException e) {
Log.e(CarLog.TAG_AUDIO, "Cannot write to VehicleProperty.AUDIO_EXT_ROUTING_HINT", e);
}
}
private boolean isPropertySupportedLocked(int property) {
VehiclePropConfig config = mProperties.get(property);
return config != null;
}
@Override
public synchronized void init() {
for (VehiclePropConfig config : mProperties.values()) {
if (VehicleHal.isPropertySubscribable(config)) {
int subsribeFlag = SubscribeFlags.HAL_EVENT;
if (AUDIO_STREAM_STATE == config.prop) {
subsribeFlag |= SubscribeFlags.SET_CALL;
}
mVehicleHal.subscribeProperty(this, config.prop, 0, subsribeFlag);
}
}
try {
mVariant = mVehicleHal.get(int.class, AUDIO_HW_VARIANT);
} catch (IllegalArgumentException e) {
// no variant. Set to default, 0.
mVariant = 0;
} catch (PropertyTimeoutException e) {
Log.e(CarLog.TAG_AUDIO, "VehicleProperty.AUDIO_HW_VARIANT not ready", e);
mVariant = 0;
}
}
@Override
public synchronized void release() {
for (VehiclePropConfig config : mProperties.values()) {
if (VehicleHal.isPropertySubscribable(config)) {
mVehicleHal.unsubscribeProperty(this, config.prop);
}
}
mProperties.clear();
}
@Override
public synchronized Collection<VehiclePropConfig> takeSupportedProperties(
Collection<VehiclePropConfig> allProperties) {
for (VehiclePropConfig p : allProperties) {
switch (p.prop) {
case VehicleProperty.AUDIO_FOCUS:
case VehicleProperty.AUDIO_VOLUME:
case VehicleProperty.AUDIO_VOLUME_LIMIT:
case VehicleProperty.AUDIO_HW_VARIANT:
case VehicleProperty.AUDIO_EXT_ROUTING_HINT:
case VehicleProperty.AUDIO_PARAMETERS:
case VehicleProperty.AUDIO_STREAM_STATE:
mProperties.put(p.prop, p);
break;
}
}
return new ArrayList<>(mProperties.values());
}
@Override
public void handleHalEvents(List<VehiclePropValue> values) {
AudioHalFocusListener focusListener;
AudioHalVolumeListener volumeListener;
OnParameterChangeListener parameterListener;
synchronized (this) {
focusListener = mFocusListener;
volumeListener = mVolumeListener;
parameterListener = mOnParameterChangeListener;
}
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) {
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);
if (focusListener != null) {
focusListener.onFocusChange(focusState, streams, externalFocus);
}
} break;
case VehicleProperty.AUDIO_STREAM_STATE: {
ArrayList<Integer> vec = v.value.int32Values;
boolean streamStarted = vec.get(0) == VEHICLE_AUDIO_STREAM_STATE_STARTED;
int streamNum = vec.get(1);
if (focusListener != null) {
focusListener.onStreamStatusChange(streamNum, streamStarted);
}
} 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);
if (volumeListener != null) {
volumeListener.onVolumeChange(streamNum, volume, volumeState);
}
} 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();
}
@Override
public void dump(PrintWriter writer) {
writer.println("*Audio HAL*");
writer.println(" audio H/W variant:" + mVariant);
writer.println(" Supported properties");
VehicleHal.dumpProperties(writer, mProperties.values());
}
}