1 /* 2 * Copyright (C) 2018 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 package com.android.car.audio; 17 18 import static android.car.media.CarAudioManager.PRIMARY_AUDIO_ZONE; 19 import static android.media.AudioDeviceInfo.TYPE_AUX_LINE; 20 import static android.media.AudioDeviceInfo.TYPE_BLE_BROADCAST; 21 import static android.media.AudioDeviceInfo.TYPE_BLE_HEADSET; 22 import static android.media.AudioDeviceInfo.TYPE_BLE_SPEAKER; 23 import static android.media.AudioDeviceInfo.TYPE_BLUETOOTH_A2DP; 24 import static android.media.AudioDeviceInfo.TYPE_BUILTIN_SPEAKER; 25 import static android.media.AudioDeviceInfo.TYPE_BUS; 26 import static android.media.AudioDeviceInfo.TYPE_HDMI; 27 import static android.media.AudioDeviceInfo.TYPE_USB_ACCESSORY; 28 import static android.media.AudioDeviceInfo.TYPE_USB_DEVICE; 29 import static android.media.AudioDeviceInfo.TYPE_USB_HEADSET; 30 import static android.media.AudioDeviceInfo.TYPE_WIRED_HEADPHONES; 31 import static android.media.AudioDeviceInfo.TYPE_WIRED_HEADSET; 32 33 import static com.android.car.audio.CarAudioService.CAR_DEFAULT_AUDIO_ATTRIBUTE; 34 import static com.android.car.audio.CarAudioService.TAG; 35 import static com.android.car.audio.CarAudioUtils.ACTIVATION_VOLUME_INVOCATION_TYPE; 36 import static com.android.car.audio.CarAudioUtils.ACTIVATION_VOLUME_PERCENTAGE_MAX; 37 import static com.android.car.audio.CarAudioUtils.ACTIVATION_VOLUME_PERCENTAGE_MIN; 38 import static com.android.car.audio.CarAudioUtils.DEFAULT_ACTIVATION_VOLUME; 39 import static com.android.car.audio.CarAudioUtils.generateAddressToCarAudioDeviceInfoMap; 40 import static com.android.car.audio.CarAudioUtils.generateAddressToInputAudioDeviceInfoMap; 41 import static com.android.car.audio.CarAudioUtils.isMicrophoneInputDevice; 42 43 import static java.util.Locale.ROOT; 44 45 import android.car.builtin.util.Slogf; 46 import android.car.feature.Flags; 47 import android.car.oem.CarAudioFadeConfiguration; 48 import android.media.AudioAttributes; 49 import android.media.AudioDeviceAttributes; 50 import android.media.AudioDeviceInfo; 51 import android.media.audiopolicy.AudioProductStrategy; 52 import android.text.TextUtils; 53 import android.util.ArrayMap; 54 import android.util.ArraySet; 55 import android.util.SparseArray; 56 import android.util.SparseIntArray; 57 import android.util.Xml; 58 59 import com.android.car.audio.CarAudioContext.AudioContext; 60 import com.android.car.internal.util.ConstantDebugUtils; 61 import com.android.car.internal.util.DebugUtils; 62 import com.android.car.internal.util.LocalLog; 63 import com.android.internal.util.Preconditions; 64 65 import org.xmlpull.v1.XmlPullParser; 66 import org.xmlpull.v1.XmlPullParserException; 67 68 import java.io.IOException; 69 import java.io.InputStream; 70 import java.util.ArrayList; 71 import java.util.List; 72 import java.util.Map; 73 import java.util.MissingResourceException; 74 import java.util.Objects; 75 import java.util.Optional; 76 import java.util.Set; 77 78 /** 79 * A helper class loads all audio zones from the configuration XML file. 80 */ 81 /* package */ final class CarAudioZonesHelperImpl implements CarAudioZonesHelper { 82 private static final String NAMESPACE = null; 83 private static final String TAG_ROOT = "carAudioConfiguration"; 84 85 private static final String TAG_OEM_CONTEXTS = "oemContexts"; 86 private static final String TAG_OEM_CONTEXT = "oemContext"; 87 private static final String OEM_CONTEXT_NAME = "name"; 88 89 private static final String TAG_DEVICE_CONFIGURATIONS = "deviceConfigurations"; 90 private static final String TAG_DEVICE_CONFIG = "deviceConfiguration"; 91 private static final String TAG_DEVICE_CONFIG_NAME = "name"; 92 private static final String TAG_DEVICE_CONFIG_VALUE = "value"; 93 private static final String DEVICE_CONFIG_CORE_VOLUME = "useCoreAudioVolume"; 94 private static final String DEVICE_CONFIG_CORE_ROUTING = "useCoreAudioRouting"; 95 private static final String DEVICE_CONFIG_DUCKING_SIGNALS = "useHalDuckingSignals"; 96 private static final String DEVICE_CONFIG_GROUP_MUTING = "useCarVolumeGroupMuting"; 97 98 private static final String TAG_AUDIO_ZONES = "zones"; 99 private static final String TAG_AUDIO_ZONE = "zone"; 100 private static final String TAG_AUDIO_ZONE_CONFIGS = "zoneConfigs"; 101 private static final String TAG_AUDIO_ZONE_CONFIG = "zoneConfig"; 102 private static final String TAG_VOLUME_GROUPS = "volumeGroups"; 103 private static final String VOLUME_GROUP_NAME = "name"; 104 private static final String TAG_VOLUME_GROUP = "group"; 105 private static final String TAG_AUDIO_DEVICE = "device"; 106 private static final String TAG_CONTEXT = "context"; 107 private static final String ATTR_ACTIVATION_VOLUME_CONFIG = "activationConfig"; 108 private static final String ATTR_VERSION = "version"; 109 private static final String ATTR_IS_PRIMARY = "isPrimary"; 110 private static final String ATTR_IS_CONFIG_DEFAULT = "isDefault"; 111 private static final String ATTR_ZONE_NAME = "name"; 112 private static final String ATTR_CONFIG_NAME = "name"; 113 private static final String ATTR_DEVICE_ADDRESS = "address"; 114 private static final String ATTR_DEVICE_TYPE = "type"; 115 private static final String ATTR_CONTEXT_NAME = "context"; 116 private static final String ATTR_ZONE_ID = "audioZoneId"; 117 private static final String ATTR_OCCUPANT_ZONE_ID = "occupantZoneId"; 118 private static final String TAG_INPUT_DEVICES = "inputDevices"; 119 private static final String TAG_INPUT_DEVICE = "inputDevice"; 120 private static final String TAG_MIRRORING_DEVICES = "mirroringDevices"; 121 private static final String TAG_MIRRORING_DEVICE = "mirroringDevice"; 122 private static final String TAG_ACTIVATION_VOLUME_CONFIGS = "activationVolumeConfigs"; 123 private static final String TAG_ACTIVATION_VOLUME_CONFIG = "activationVolumeConfig"; 124 private static final String TAG_ACTIVATION_VOLUME_CONFIG_ENTRY = "activationVolumeConfigEntry"; 125 private static final String ATTR_ACTIVATION_VOLUME_CONFIG_NAME = "name"; 126 private static final String ATTR_ACTIVATION_VOLUME_INVOCATION_TYPE = "invocationType"; 127 private static final String ATTR_MAX_ACTIVATION_VOLUME_PERCENTAGE = 128 "maxActivationVolumePercentage"; 129 private static final String ATTR_MIN_ACTIVATION_VOLUME_PERCENTAGE = 130 "minActivationVolumePercentage"; 131 private static final String ACTIVATION_VOLUME_INVOCATION_TYPE_ON_BOOT = "onBoot"; 132 private static final String ACTIVATION_VOLUME_INVOCATION_TYPE_ON_SOURCE_CHANGED = 133 "onSourceChanged"; 134 private static final String ACTIVATION_VOLUME_INVOCATION_TYPE_ON_PLAYBACK_CHANGED = 135 "onPlaybackChanged"; 136 private static final String TAG_APPLY_FADE_CONFIGS = "applyFadeConfigs"; 137 private static final String FADE_CONFIG = "fadeConfig"; 138 private static final String FADE_CONFIG_NAME = "name"; 139 private static final String FADE_CONFIG_IS_DEFAULT = "isDefault"; 140 private static final int INVALID_VERSION = -1; 141 private static final int SUPPORTED_VERSION_1 = 1; 142 private static final int SUPPORTED_VERSION_2 = 2; 143 private static final int SUPPORTED_VERSION_3 = 3; 144 private static final int SUPPORTED_VERSION_4 = 4; 145 private static final SparseIntArray SUPPORTED_VERSIONS; 146 147 static { 148 SUPPORTED_VERSIONS = new SparseIntArray(4); SUPPORTED_VERSIONS.put(SUPPORTED_VERSION_1, SUPPORTED_VERSION_1)149 SUPPORTED_VERSIONS.put(SUPPORTED_VERSION_1, SUPPORTED_VERSION_1); SUPPORTED_VERSIONS.put(SUPPORTED_VERSION_2, SUPPORTED_VERSION_2)150 SUPPORTED_VERSIONS.put(SUPPORTED_VERSION_2, SUPPORTED_VERSION_2); SUPPORTED_VERSIONS.put(SUPPORTED_VERSION_3, SUPPORTED_VERSION_3)151 SUPPORTED_VERSIONS.put(SUPPORTED_VERSION_3, SUPPORTED_VERSION_3); SUPPORTED_VERSIONS.put(SUPPORTED_VERSION_4, SUPPORTED_VERSION_4)152 SUPPORTED_VERSIONS.put(SUPPORTED_VERSION_4, SUPPORTED_VERSION_4); 153 } 154 155 private final AudioManagerWrapper mAudioManager; 156 private final CarAudioSettings mCarAudioSettings; 157 private final List<CarAudioContextInfo> mCarAudioContextInfos = new ArrayList<>(); 158 private final Map<String, CarAudioDeviceInfo> mAddressToCarAudioDeviceInfo; 159 private final Map<String, AudioDeviceInfo> mAddressToInputAudioDeviceInfoForAllInputDevices; 160 private final InputStream mInputStream; 161 private final SparseIntArray mZoneIdToOccupantZoneIdMapping; 162 private final Set<Integer> mAudioZoneIds; 163 private final Set<String> mAssignedInputAudioDevices; 164 private final Set<String> mAudioZoneConfigNames; 165 private final boolean mUseFadeManagerConfiguration; 166 private final CarAudioFadeConfigurationHelper mCarAudioFadeConfigurationHelper; 167 private final List<CarAudioDeviceInfo> mMirroringDevices = new ArrayList<>(); 168 private final Map<String, CarActivationVolumeConfig> mConfigNameToActivationVolumeConfig = 169 new ArrayMap<>(); 170 171 private final ArrayMap<String, String> mDeviceConfigNameToValue = new ArrayMap<>(); 172 private boolean mUseCoreAudioVolume; 173 private boolean mUseCoreAudioRouting; 174 private boolean mUseCarVolumeGroupMute; 175 private Optional<Boolean> mUseHalDuckingSignals = Optional.empty(); 176 private final ArrayMap<String, Integer> mContextNameToId = new ArrayMap<>(); 177 private final LocalLog mCarServiceLocalLog; 178 private CarAudioContext mCarAudioContext; 179 private int mNextSecondaryZoneId; 180 private int mCurrentVersion; 181 182 /** 183 * <p><b>Note: </b> CarAudioZonesHelper is expected to be used from a single thread. This 184 * should be the same thread that originally called new CarAudioZonesHelper. 185 */ CarAudioZonesHelperImpl(AudioManagerWrapper audioManager, CarAudioSettings carAudioSettings, InputStream inputStream, List<CarAudioDeviceInfo> carAudioDeviceInfos, AudioDeviceInfo[] inputDeviceInfo, LocalLog serviceLog, boolean useCarVolumeGroupMute, boolean useCoreAudioVolume, boolean useCoreAudioRouting, boolean useFadeManagerConfiguration, CarAudioFadeConfigurationHelper carAudioFadeConfigurationHelper)186 CarAudioZonesHelperImpl(AudioManagerWrapper audioManager, CarAudioSettings carAudioSettings, 187 InputStream inputStream, List<CarAudioDeviceInfo> carAudioDeviceInfos, 188 AudioDeviceInfo[] inputDeviceInfo, LocalLog serviceLog, 189 boolean useCarVolumeGroupMute, boolean useCoreAudioVolume, 190 boolean useCoreAudioRouting, boolean useFadeManagerConfiguration, 191 CarAudioFadeConfigurationHelper carAudioFadeConfigurationHelper) { 192 mAudioManager = Objects.requireNonNull(audioManager, 193 "Audio manager cannot be null"); 194 mCarAudioSettings = Objects.requireNonNull(carAudioSettings); 195 mInputStream = Objects.requireNonNull(inputStream); 196 Objects.requireNonNull(carAudioDeviceInfos); 197 Objects.requireNonNull(inputDeviceInfo); 198 mAddressToCarAudioDeviceInfo = generateAddressToCarAudioDeviceInfoMap(carAudioDeviceInfos); 199 mCarServiceLocalLog = Objects.requireNonNull(serviceLog, 200 "Car audio service local log cannot be null"); 201 mAddressToInputAudioDeviceInfoForAllInputDevices = 202 generateAddressToInputAudioDeviceInfoMap(inputDeviceInfo); 203 mNextSecondaryZoneId = PRIMARY_AUDIO_ZONE + 1; 204 mZoneIdToOccupantZoneIdMapping = new SparseIntArray(); 205 mAudioZoneIds = new ArraySet<>(); 206 mAssignedInputAudioDevices = new ArraySet<>(); 207 mAudioZoneConfigNames = new ArraySet<>(); 208 mUseCarVolumeGroupMute = useCarVolumeGroupMute; 209 mUseCoreAudioVolume = useCoreAudioVolume; 210 mUseCoreAudioRouting = useCoreAudioRouting; 211 mUseFadeManagerConfiguration = useFadeManagerConfiguration; 212 mCarAudioFadeConfigurationHelper = carAudioFadeConfigurationHelper; 213 } 214 215 /** 216 * see {@code CarAudioZonesHelperI#getCarAudioZoneIdToOccupantZoneIdMapping} for details 217 */ 218 @Override getCarAudioZoneIdToOccupantZoneIdMapping()219 public SparseIntArray getCarAudioZoneIdToOccupantZoneIdMapping() { 220 return mZoneIdToOccupantZoneIdMapping; 221 } 222 223 /** 224 * see {@code CarAudioZonesHelperI#loadAudioZones} for details 225 */ 226 @Override loadAudioZones()227 public SparseArray<CarAudioZone> loadAudioZones() throws IOException, XmlPullParserException { 228 return parseCarAudioZones(mInputStream); 229 } 230 231 /** 232 * Returns the updated use core volume device configuration 233 * 234 * <p><b>Note</b> The value will depend on the device configuration obtained from the car audio 235 * configuration file. With value obtained from the configuration file having higher priority 236 * over the passed in value (which was obtained from the legacy RRO). If the value is not 237 * defined in the audio configuration file, the original passed in value will be returned. 238 * 239 * @return {@code true} if core volume management should be used, {@code false} otherwise. 240 */ 241 @Override useCoreAudioVolume()242 public boolean useCoreAudioVolume() { 243 return mUseCoreAudioVolume; 244 } 245 246 /** 247 * Returns the updated use core routing device configuration 248 * 249 * <p><b>Note</b> The value will depend on the device configuration obtained from the car audio 250 * configuration file. With value obtained from the configuration file having higher priority 251 * over the passed in value (which was obtained from the legacy RRO). If the value is not 252 * defined in the audio configuration file, the original passed in value will be returned. 253 * 254 * @return {@code true} if core routing management should be used, {@code false} otherwise. 255 */ 256 @Override useCoreAudioRouting()257 public boolean useCoreAudioRouting() { 258 return mUseCoreAudioRouting; 259 } 260 261 /** 262 * Returns the updated use volume group muting device configuration 263 * 264 * <p><b>Note</b> The value will depend on the device configuration obtained from the car audio 265 * configuration file. With value obtained from the configuration file having higher priority 266 * over the passed in value (which was obtained from the legacy RRO). If the value is not 267 * defined in the audio configuration file, the original passed in value will be returned. 268 * 269 * @return {@code true} if volume group muting should be used, {@code false} otherwise. 270 */ 271 @Override useVolumeGroupMuting()272 public boolean useVolumeGroupMuting() { 273 return mUseCarVolumeGroupMute; 274 } 275 276 /** 277 * Returns the updated use HAL ducking device configuration 278 * 279 * <p><b>Note</b> The value will depend on the device configuration obtained from the car audio 280 * configuration file. With value obtained from the configuration file having higher priority 281 * over the passed in default value (which was obtained from the legacy RRO). If the value is 282 * not defined in the audio configuration file, the passed in value will be returned. 283 * 284 * @param defaultUseHalDuckingSignal default value that should be returned if the config was 285 * not present in the audio configuration file. 286 * 287 * @return {@code true} if volume group muting should be used, {@code false} otherwise. 288 */ 289 @Override useHalDuckingSignalOrDefault(boolean defaultUseHalDuckingSignal)290 public boolean useHalDuckingSignalOrDefault(boolean defaultUseHalDuckingSignal) { 291 return mUseHalDuckingSignals.orElse(defaultUseHalDuckingSignal); 292 } 293 parseCarAudioZones(InputStream stream)294 private SparseArray<CarAudioZone> parseCarAudioZones(InputStream stream) 295 throws XmlPullParserException, IOException { 296 XmlPullParser parser = Xml.newPullParser(); 297 parser.setFeature(XmlPullParser.FEATURE_PROCESS_NAMESPACES, NAMESPACE != null); 298 parser.setInput(stream, null); 299 300 // Ensure <carAudioConfiguration> is the root 301 parser.nextTag(); 302 parser.require(XmlPullParser.START_TAG, NAMESPACE, TAG_ROOT); 303 304 // Version check 305 final int versionNumber = Integer.parseInt( 306 parser.getAttributeValue(NAMESPACE, ATTR_VERSION)); 307 308 if (SUPPORTED_VERSIONS.get(versionNumber, INVALID_VERSION) == INVALID_VERSION) { 309 throw new IllegalArgumentException("Latest Supported version:" 310 + SUPPORTED_VERSION_4 + " , got version:" + versionNumber); 311 } 312 313 mCurrentVersion = versionNumber; 314 // Get all zones configured under <zones> tag 315 while (parser.next() != XmlPullParser.END_TAG) { 316 if (parser.getEventType() != XmlPullParser.START_TAG) continue; 317 if (Flags.audioVendorFreezeImprovements() 318 && Objects.equals(parser.getName(), TAG_DEVICE_CONFIGURATIONS)) { 319 parseDeviceConfigurations(parser); 320 } else if (Objects.equals(parser.getName(), TAG_OEM_CONTEXTS)) { 321 parseCarAudioContexts(parser); 322 } else if (Objects.equals(parser.getName(), TAG_MIRRORING_DEVICES)) { 323 parseMirroringDevices(parser); 324 } else if (Objects.equals(parser.getName(), TAG_ACTIVATION_VOLUME_CONFIGS)) { 325 parseActivationVolumeConfigs(parser); 326 } else if (Objects.equals(parser.getName(), TAG_AUDIO_ZONES)) { 327 loadCarAudioContexts(); 328 return parseAudioZones(parser); 329 } else { 330 CarAudioParserUtils.skip(parser); 331 } 332 } 333 throw new MissingResourceException(TAG_AUDIO_ZONES + " is missing from configuration", 334 "", TAG_AUDIO_ZONES); 335 } 336 parseMirroringDevices(XmlPullParser parser)337 private void parseMirroringDevices(XmlPullParser parser) 338 throws XmlPullParserException, IOException { 339 if (isVersionLessThanThree()) { 340 throw new IllegalStateException( 341 TAG_MIRRORING_DEVICES + " are not supported in car_audio_configuration.xml" 342 + " version " + mCurrentVersion + ". Must be at least version " 343 + SUPPORTED_VERSION_3); 344 } 345 346 while (parser.next() != XmlPullParser.END_TAG) { 347 if (parser.getEventType() != XmlPullParser.START_TAG) { 348 continue; 349 } 350 if (Objects.equals(parser.getName(), TAG_MIRRORING_DEVICE)) { 351 parseMirroringDevice(parser); 352 } 353 CarAudioParserUtils.skip(parser); 354 } 355 } 356 parseMirroringDevice(XmlPullParser parser)357 private void parseMirroringDevice(XmlPullParser parser) { 358 String address = parser.getAttributeValue(NAMESPACE, ATTR_DEVICE_ADDRESS); 359 validateOutputAudioDevice(address, TYPE_BUS); 360 CarAudioDeviceInfo info = getCarAudioDeviceInfo(address, TYPE_BUS); 361 if (mMirroringDevices.contains(info)) { 362 throw new IllegalArgumentException(TAG_MIRRORING_DEVICE + " " + address 363 + " repeats, " + TAG_MIRRORING_DEVICES + " can not repeat."); 364 } 365 mMirroringDevices.add(info); 366 } 367 loadCarAudioContexts()368 private void loadCarAudioContexts() { 369 if (isVersionLessThanThree() || mCarAudioContextInfos.isEmpty()) { 370 mCarAudioContextInfos.addAll(CarAudioContext.getAllContextsInfo()); 371 } 372 for (int index = 0; index < mCarAudioContextInfos.size(); index++) { 373 CarAudioContextInfo info = mCarAudioContextInfos.get(index); 374 mContextNameToId.put(info.getName().toLowerCase(ROOT), info.getId()); 375 } 376 mCarAudioContext = new CarAudioContext(mCarAudioContextInfos, mUseCoreAudioRouting); 377 } 378 parseCarAudioContexts(XmlPullParser parser)379 private void parseCarAudioContexts(XmlPullParser parser) 380 throws XmlPullParserException, IOException { 381 int contextId = CarAudioContext.getInvalidContext() + 1; 382 383 while (parser.next() != XmlPullParser.END_TAG) { 384 if (parser.getEventType() != XmlPullParser.START_TAG) { 385 continue; 386 } 387 if (Objects.equals(parser.getName(), TAG_OEM_CONTEXT)) { 388 parseCarAudioContext(parser, contextId); 389 contextId++; 390 } else { 391 CarAudioParserUtils.skip(parser); 392 } 393 } 394 } 395 parseCarAudioContext(XmlPullParser parser, int contextId)396 private void parseCarAudioContext(XmlPullParser parser, int contextId) 397 throws XmlPullParserException, IOException { 398 String contextName = parser.getAttributeValue(NAMESPACE, OEM_CONTEXT_NAME); 399 CarAudioContextInfo context = null; 400 while (parser.next() != XmlPullParser.END_TAG) { 401 if (parser.getEventType() != XmlPullParser.START_TAG) { 402 continue; 403 } 404 if (Objects.equals(parser.getName(), CarAudioParserUtils.TAG_AUDIO_ATTRIBUTES)) { 405 List<AudioAttributes> attributes = 406 CarAudioParserUtils.parseAudioAttributes(parser, contextName); 407 if (mUseCoreAudioRouting) { 408 contextId = CoreAudioHelper.getStrategyForContextName(contextName); 409 if (contextId == CoreAudioHelper.INVALID_STRATEGY) { 410 throw new IllegalArgumentException(CarAudioParserUtils.TAG_AUDIO_ATTRIBUTES 411 + ": Cannot find strategy id for context: " 412 + contextName + " and attributes \"" + attributes.get(0) + "\" ."); 413 } 414 } 415 validateCarAudioContextAttributes(contextId, attributes, contextName); 416 context = new CarAudioContextInfo(attributes.toArray(new AudioAttributes[0]), 417 contextName, contextId); 418 mCarAudioContextInfos.add(context); 419 } else { 420 CarAudioParserUtils.skip(parser); 421 } 422 } 423 } 424 validateCarAudioContextAttributes(int contextId, List<AudioAttributes> attributes, String contextName)425 private void validateCarAudioContextAttributes(int contextId, List<AudioAttributes> attributes, 426 String contextName) { 427 if (!mUseCoreAudioRouting) { 428 return; 429 } 430 AudioProductStrategy strategy = CoreAudioHelper.getStrategy(contextId); 431 Preconditions.checkNotNull(strategy, "No strategy for context id = %d", contextId); 432 for (int index = 0; index < attributes.size(); index++) { 433 AudioAttributes aa = attributes.get(index); 434 if (!strategy.supportsAudioAttributes(aa) 435 && !CoreAudioHelper.isDefaultStrategy(strategy.getId())) { 436 throw new IllegalArgumentException("Invalid attributes " + aa + " for context: " 437 + contextName); 438 } 439 } 440 } 441 parseDeviceConfigurations(XmlPullParser parser)442 private void parseDeviceConfigurations(XmlPullParser parser) 443 throws XmlPullParserException, IOException { 444 while (parser.next() != XmlPullParser.END_TAG) { 445 if (parser.getEventType() != XmlPullParser.START_TAG) continue; 446 if (Objects.equals(parser.getName(), TAG_DEVICE_CONFIG)) { 447 parseAudioConfig(parser); 448 } else { 449 CarAudioParserUtils.skip(parser); 450 } 451 } 452 mUseCoreAudioVolume = parseUseCoreAudioVolume(); 453 mUseCoreAudioRouting = parseUseCoreAudioRouting(); 454 mUseCarVolumeGroupMute = parseUseCarVolumeGroupMuting(); 455 mUseHalDuckingSignals = parseUseHalDuckingSignals(); 456 } 457 parseAudioConfig(XmlPullParser parser)458 private void parseAudioConfig(XmlPullParser parser) 459 throws XmlPullParserException, IOException { 460 String name = parser.getAttributeValue(NAMESPACE, TAG_DEVICE_CONFIG_NAME); 461 String value = parser.getAttributeValue(NAMESPACE, TAG_DEVICE_CONFIG_VALUE); 462 if (!TextUtils.isEmpty(name) && !TextUtils.isEmpty(value) 463 && !mDeviceConfigNameToValue.containsKey(name)) { 464 mDeviceConfigNameToValue.put(name, value); 465 } else if (mDeviceConfigNameToValue.containsKey(name)) { 466 // If the name already exist consistently keep the previous value 467 mCarServiceLocalLog.log("Device configuration " + name 468 + " has a duplicate definition with a new value of " + value 469 + ", keeping the previously assigned value of " 470 + mDeviceConfigNameToValue.get(name)); 471 } else { 472 Slogf.w(TAG, "Device configuration with empty name or value was encountered: [name=" 473 + name + ", value= " + value + "]"); 474 } 475 while (parser.next() != XmlPullParser.END_TAG) { 476 if (parser.getEventType() != XmlPullParser.START_TAG) continue; 477 CarAudioParserUtils.skip(parser); 478 } 479 } 480 parseUseHalDuckingSignals()481 private Optional<Boolean> parseUseHalDuckingSignals() { 482 String useHalDuckingSignals = mDeviceConfigNameToValue.get(DEVICE_CONFIG_DUCKING_SIGNALS); 483 return parseOptionalBoolean(useHalDuckingSignals, DEVICE_CONFIG_DUCKING_SIGNALS); 484 } 485 parseUseCarVolumeGroupMuting()486 private boolean parseUseCarVolumeGroupMuting() { 487 String useHalGroupMuting = mDeviceConfigNameToValue.get(DEVICE_CONFIG_GROUP_MUTING); 488 return parseBoolean(useHalGroupMuting, DEVICE_CONFIG_GROUP_MUTING, mUseCarVolumeGroupMute); 489 } 490 parseUseCoreAudioVolume()491 private boolean parseUseCoreAudioVolume() { 492 String useCoreVolumeString = mDeviceConfigNameToValue.get(DEVICE_CONFIG_CORE_VOLUME); 493 return parseBoolean(useCoreVolumeString, DEVICE_CONFIG_CORE_VOLUME, mUseCoreAudioVolume); 494 } 495 parseUseCoreAudioRouting()496 private boolean parseUseCoreAudioRouting() { 497 String useCoreVolumeString = mDeviceConfigNameToValue.get(DEVICE_CONFIG_CORE_ROUTING); 498 return parseBoolean(useCoreVolumeString, DEVICE_CONFIG_CORE_ROUTING, 499 mUseCoreAudioRouting); 500 } 501 parseBoolean(String booleanString, String configName, boolean defaultValue)502 private boolean parseBoolean(String booleanString, String configName, boolean defaultValue) { 503 Optional<Boolean> parsedValue = parseOptionalBoolean(booleanString, configName); 504 505 return parsedValue.orElse(defaultValue); 506 } 507 parseOptionalBoolean(String booleanString, String configName)508 private Optional<Boolean> parseOptionalBoolean(String booleanString, String configName) { 509 if (TextUtils.isEmpty(booleanString)) { 510 return Optional.empty(); 511 } 512 513 // Need to check for "true" or "false", since Boolean parse will return false for anything 514 // not "true" and we are specifically checking for "true" or "false". 515 if (!booleanString.equalsIgnoreCase("true") 516 && !booleanString.equalsIgnoreCase("false")) { 517 mCarServiceLocalLog.log(configName + " was declared with the value of " 518 + booleanString + " but should be either true or false"); 519 return Optional.empty(); 520 } 521 522 mCarServiceLocalLog.log("Found " + configName + " = " + booleanString 523 + " in car_audio_configuration.xml file"); 524 return Optional.of(Boolean.parseBoolean(booleanString)); 525 } 526 parseAudioZones(XmlPullParser parser)527 private SparseArray<CarAudioZone> parseAudioZones(XmlPullParser parser) 528 throws XmlPullParserException, IOException { 529 SparseArray<CarAudioZone> carAudioZones = new SparseArray<>(); 530 531 while (parser.next() != XmlPullParser.END_TAG) { 532 if (parser.getEventType() != XmlPullParser.START_TAG) continue; 533 if (Objects.equals(parser.getName(), TAG_AUDIO_ZONE)) { 534 CarAudioZone zone = parseAudioZone(parser); 535 carAudioZones.put(zone.getId(), zone); 536 } else { 537 CarAudioParserUtils.skip(parser); 538 } 539 } 540 541 verifyPrimaryZonePresent(carAudioZones); 542 addRemainingMicrophonesToPrimaryZone(carAudioZones); 543 return carAudioZones; 544 } 545 addRemainingMicrophonesToPrimaryZone(SparseArray<CarAudioZone> carAudioZones)546 private void addRemainingMicrophonesToPrimaryZone(SparseArray<CarAudioZone> carAudioZones) { 547 CarAudioZone primaryAudioZone = carAudioZones.get(PRIMARY_AUDIO_ZONE); 548 for (AudioDeviceInfo info : mAddressToInputAudioDeviceInfoForAllInputDevices.values()) { 549 if (!mAssignedInputAudioDevices.contains(info.getAddress()) 550 && isMicrophoneInputDevice(info)) { 551 primaryAudioZone.addInputAudioDevice(new AudioDeviceAttributes(info)); 552 } 553 } 554 } 555 verifyPrimaryZonePresent(SparseArray<CarAudioZone> zones)556 private void verifyPrimaryZonePresent(SparseArray<CarAudioZone> zones) { 557 if (!zones.contains(PRIMARY_AUDIO_ZONE)) { 558 throw new RuntimeException("Primary audio zone is required"); 559 } 560 } 561 parseAudioZone(XmlPullParser parser)562 private CarAudioZone parseAudioZone(XmlPullParser parser) 563 throws XmlPullParserException, IOException { 564 final boolean isPrimary = Boolean.parseBoolean( 565 parser.getAttributeValue(NAMESPACE, ATTR_IS_PRIMARY)); 566 final String zoneName = parser.getAttributeValue(NAMESPACE, ATTR_ZONE_NAME); 567 final int audioZoneId = getZoneId(isPrimary, parser); 568 parseOccupantZoneId(audioZoneId, parser); 569 final CarAudioZone zone = new CarAudioZone(mCarAudioContext, zoneName, audioZoneId); 570 if (isVersionLessThanThree()) { 571 final CarAudioZoneConfig.Builder zoneConfigBuilder = new CarAudioZoneConfig.Builder( 572 zoneName, audioZoneId, /* zoneConfigId= */ 0, /* isDefault= */ true); 573 while (parser.next() != XmlPullParser.END_TAG) { 574 if (parser.getEventType() != XmlPullParser.START_TAG) continue; 575 // Expect at least one <volumeGroups> in one audio zone 576 if (Objects.equals(parser.getName(), TAG_VOLUME_GROUPS)) { 577 parseVolumeGroups(parser, zoneConfigBuilder); 578 } else if (Objects.equals(parser.getName(), TAG_INPUT_DEVICES)) { 579 parseInputAudioDevices(parser, zone); 580 } else { 581 CarAudioParserUtils.skip(parser); 582 } 583 } 584 zone.addZoneConfig(zoneConfigBuilder.build()); 585 return zone; 586 } 587 while (parser.next() != XmlPullParser.END_TAG) { 588 if (parser.getEventType() != XmlPullParser.START_TAG) continue; 589 // Expect at least one <zoneConfigs> in one audio zone 590 if (Objects.equals(parser.getName(), TAG_AUDIO_ZONE_CONFIGS)) { 591 parseZoneConfigs(parser, zone); 592 } else if (Objects.equals(parser.getName(), TAG_INPUT_DEVICES)) { 593 parseInputAudioDevices(parser, zone); 594 } else { 595 CarAudioParserUtils.skip(parser); 596 } 597 } 598 return zone; 599 } 600 getZoneId(boolean isPrimary, XmlPullParser parser)601 private int getZoneId(boolean isPrimary, XmlPullParser parser) { 602 String audioZoneIdString = parser.getAttributeValue(NAMESPACE, ATTR_ZONE_ID); 603 if (isVersionOne()) { 604 Preconditions.checkArgument(audioZoneIdString == null, 605 "Invalid audio attribute %s" 606 + ", Please update car audio configurations file " 607 + "to version to 2 to use it.", ATTR_ZONE_ID); 608 return isPrimary ? PRIMARY_AUDIO_ZONE 609 : getNextSecondaryZoneId(); 610 } 611 // Primary zone does not need to define it 612 if (isPrimary && audioZoneIdString == null) { 613 return PRIMARY_AUDIO_ZONE; 614 } 615 Objects.requireNonNull(audioZoneIdString, 616 "Requires audioZoneId for all audio zones."); 617 int zoneId = CarAudioParserUtils.parsePositiveIntAttribute(ATTR_ZONE_ID, audioZoneIdString); 618 //Verify that primary zone id is PRIMARY_AUDIO_ZONE 619 if (isPrimary) { 620 Preconditions.checkArgument(zoneId == PRIMARY_AUDIO_ZONE, 621 "Primary zone %s must be %d or it can be left empty.", 622 ATTR_ZONE_ID, PRIMARY_AUDIO_ZONE); 623 } else { 624 Preconditions.checkArgument(zoneId != PRIMARY_AUDIO_ZONE, 625 "%s can only be %d for primary zone.", 626 ATTR_ZONE_ID, PRIMARY_AUDIO_ZONE); 627 } 628 validateAudioZoneIdIsUnique(zoneId); 629 return zoneId; 630 } 631 parseOccupantZoneId(int audioZoneId, XmlPullParser parser)632 private void parseOccupantZoneId(int audioZoneId, XmlPullParser parser) { 633 String occupantZoneIdString = parser.getAttributeValue(NAMESPACE, ATTR_OCCUPANT_ZONE_ID); 634 if (isVersionOne()) { 635 Preconditions.checkArgument(occupantZoneIdString == null, 636 "Invalid audio attribute %s" 637 + ", Please update car audio configurations file " 638 + "to version to 2 to use it.", ATTR_OCCUPANT_ZONE_ID); 639 return; 640 } 641 //Occupant id not required for all zones 642 if (occupantZoneIdString == null) { 643 return; 644 } 645 int occupantZoneId = CarAudioParserUtils.parsePositiveIntAttribute(ATTR_OCCUPANT_ZONE_ID, 646 occupantZoneIdString); 647 validateOccupantZoneIdIsUnique(occupantZoneId); 648 mZoneIdToOccupantZoneIdMapping.put(audioZoneId, occupantZoneId); 649 } 650 parseInputAudioDevices(XmlPullParser parser, CarAudioZone zone)651 private void parseInputAudioDevices(XmlPullParser parser, CarAudioZone zone) 652 throws IOException, XmlPullParserException { 653 if (isVersionOne()) { 654 throw new IllegalStateException( 655 TAG_INPUT_DEVICES + " are not supported in car_audio_configuration.xml version " 656 + SUPPORTED_VERSION_1); 657 } 658 while (parser.next() != XmlPullParser.END_TAG) { 659 if (parser.getEventType() != XmlPullParser.START_TAG) continue; 660 if (Objects.equals(parser.getName(), TAG_INPUT_DEVICE)) { 661 String audioDeviceAddress = 662 parser.getAttributeValue(NAMESPACE, ATTR_DEVICE_ADDRESS); 663 validateInputAudioDeviceAddress(audioDeviceAddress); 664 AudioDeviceInfo info = 665 mAddressToInputAudioDeviceInfoForAllInputDevices.get(audioDeviceAddress); 666 Preconditions.checkArgument(info != null, 667 "%s %s of %s does not exist, add input device to" 668 + " audio_policy_configuration.xml.", 669 ATTR_DEVICE_ADDRESS, audioDeviceAddress, TAG_INPUT_DEVICE); 670 zone.addInputAudioDevice(new AudioDeviceAttributes(info)); 671 } 672 CarAudioParserUtils.skip(parser); 673 } 674 } 675 validateInputAudioDeviceAddress(String audioDeviceAddress)676 private void validateInputAudioDeviceAddress(String audioDeviceAddress) { 677 Objects.requireNonNull(audioDeviceAddress, () -> 678 TAG_INPUT_DEVICE + " " + ATTR_DEVICE_ADDRESS + " attribute must be present."); 679 Preconditions.checkArgument(!audioDeviceAddress.isEmpty(), 680 "%s %s attribute can not be empty.", 681 TAG_INPUT_DEVICE, ATTR_DEVICE_ADDRESS); 682 if (mAssignedInputAudioDevices.contains(audioDeviceAddress)) { 683 throw new IllegalArgumentException(TAG_INPUT_DEVICE + " " + audioDeviceAddress 684 + " repeats, " + TAG_INPUT_DEVICES + " can not repeat."); 685 } 686 mAssignedInputAudioDevices.add(audioDeviceAddress); 687 } 688 validateOccupantZoneIdIsUnique(int occupantZoneId)689 private void validateOccupantZoneIdIsUnique(int occupantZoneId) { 690 if (mZoneIdToOccupantZoneIdMapping.indexOfValue(occupantZoneId) > -1) { 691 throw new IllegalArgumentException(ATTR_OCCUPANT_ZONE_ID + " " + occupantZoneId 692 + " is already associated with a zone"); 693 } 694 } 695 validateAudioZoneIdIsUnique(int audioZoneId)696 private void validateAudioZoneIdIsUnique(int audioZoneId) { 697 if (mAudioZoneIds.contains(audioZoneId)) { 698 throw new IllegalArgumentException(ATTR_ZONE_ID + " " + audioZoneId 699 + " is already associated with a zone"); 700 } 701 mAudioZoneIds.add(audioZoneId); 702 } 703 parseZoneConfigs(XmlPullParser parser, CarAudioZone zone)704 private void parseZoneConfigs(XmlPullParser parser, CarAudioZone zone) 705 throws XmlPullParserException, IOException { 706 int zoneConfigId = 0; 707 while (parser.next() != XmlPullParser.END_TAG) { 708 if (parser.getEventType() != XmlPullParser.START_TAG) continue; 709 if (Objects.equals(parser.getName(), TAG_AUDIO_ZONE_CONFIG)) { 710 if (isVersionLessThanFour() && zone.getId() == PRIMARY_AUDIO_ZONE 711 && zoneConfigId > 0) { 712 throw new IllegalArgumentException( 713 "Primary zone cannot have multiple zone configurations"); 714 } 715 parseZoneConfig(parser, zone, zoneConfigId); 716 zoneConfigId++; 717 } else { 718 CarAudioParserUtils.skip(parser); 719 } 720 } 721 } 722 parseZoneConfig(XmlPullParser parser, CarAudioZone zone, int zoneConfigId)723 private void parseZoneConfig(XmlPullParser parser, CarAudioZone zone, int zoneConfigId) 724 throws XmlPullParserException, IOException { 725 final boolean isDefault = Boolean.parseBoolean( 726 parser.getAttributeValue(NAMESPACE, ATTR_IS_CONFIG_DEFAULT)); 727 final String zoneConfigName = parser.getAttributeValue(NAMESPACE, ATTR_CONFIG_NAME); 728 validateAudioZoneConfigName(zoneConfigName); 729 final CarAudioZoneConfig.Builder zoneConfigBuilder = new CarAudioZoneConfig.Builder( 730 zoneConfigName, zone.getId(), zoneConfigId, isDefault); 731 boolean valid = true; 732 zoneConfigBuilder.setFadeManagerConfigurationEnabled(mUseFadeManagerConfiguration); 733 while (parser.next() != XmlPullParser.END_TAG) { 734 if (parser.getEventType() != XmlPullParser.START_TAG) continue; 735 // Expect at least one <volumeGroups> in one audio zone config 736 if (Objects.equals(parser.getName(), TAG_VOLUME_GROUPS)) { 737 if (!parseVolumeGroups(parser, zoneConfigBuilder)) { 738 valid = false; 739 } 740 } else if (Objects.equals(parser.getName(), TAG_APPLY_FADE_CONFIGS)) { 741 parseApplyFadeConfigs(parser, zoneConfigBuilder); 742 } else { 743 CarAudioParserUtils.skip(parser); 744 } 745 } 746 // If the configuration is not valid we can skip the config 747 if (!valid) { 748 String message = "Skipped configuration " + zoneConfigName + " in zone " + zone.getId(); 749 Slogf.e(TAG, message); 750 mCarServiceLocalLog.log(message); 751 return; 752 } 753 zone.addZoneConfig(zoneConfigBuilder.build()); 754 } 755 validateAudioZoneConfigName(String configName)756 private void validateAudioZoneConfigName(String configName) { 757 Objects.requireNonNull(configName, TAG_AUDIO_ZONE_CONFIG + " " + ATTR_CONFIG_NAME 758 + " attribute must be present."); 759 Preconditions.checkArgument(!configName.isEmpty(), 760 "%s %s attribute can not be empty.", 761 TAG_AUDIO_ZONE_CONFIG, ATTR_CONFIG_NAME); 762 if (mAudioZoneConfigNames.contains(configName)) { 763 throw new IllegalArgumentException(ATTR_CONFIG_NAME + " " + configName 764 + " repeats, " + ATTR_CONFIG_NAME + " can not repeat."); 765 } 766 mAudioZoneConfigNames.add(configName); 767 } 768 parseVolumeGroups(XmlPullParser parser, CarAudioZoneConfig.Builder zoneConfigBuilder)769 private boolean parseVolumeGroups(XmlPullParser parser, 770 CarAudioZoneConfig.Builder zoneConfigBuilder) 771 throws XmlPullParserException, IOException { 772 int groupId = 0; 773 boolean valid = true; 774 while (parser.next() != XmlPullParser.END_TAG) { 775 if (parser.getEventType() != XmlPullParser.START_TAG) continue; 776 if (Objects.equals(parser.getName(), TAG_VOLUME_GROUP)) { 777 String groupName = parser.getAttributeValue(NAMESPACE, VOLUME_GROUP_NAME); 778 groupName = verifyGroupNameAndCreateIfNeeded(zoneConfigBuilder, groupName, groupId); 779 780 CarActivationVolumeConfig activationVolumeConfig = 781 getActivationVolumeConfig(parser); 782 783 CarVolumeGroupFactory factory = new CarVolumeGroupFactory(mAudioManager, 784 mCarAudioSettings, mCarAudioContext, zoneConfigBuilder.getZoneId(), 785 zoneConfigBuilder.getZoneConfigId(), groupId, groupName, 786 mUseCarVolumeGroupMute, activationVolumeConfig); 787 788 if (!parseVolumeGroup(parser, factory)) { 789 valid = false; 790 } 791 if (!valid) { 792 continue; 793 } 794 zoneConfigBuilder.addVolumeGroup(factory.getCarVolumeGroup(mUseCoreAudioVolume)); 795 groupId++; 796 } else { 797 CarAudioParserUtils.skip(parser); 798 } 799 } 800 return valid; 801 } 802 verifyGroupNameAndCreateIfNeeded( CarAudioZoneConfig.Builder zoneConfigBuilder, String groupName, int groupId)803 private String verifyGroupNameAndCreateIfNeeded( 804 CarAudioZoneConfig.Builder zoneConfigBuilder, String groupName, int groupId) { 805 verifyGroupName(groupName); 806 if (groupName == null) { 807 groupName = "config " + zoneConfigBuilder.getZoneConfigId() + " group " + groupId; 808 } 809 return groupName; 810 } 811 verifyGroupName(String groupName)812 private void verifyGroupName(String groupName) { 813 if (!Flags.audioVendorFreezeImprovements()) { 814 Preconditions.checkArgument(!mUseCoreAudioVolume || groupName != null, 815 "%s %s attribute can not be empty when relying on core volume groups", 816 TAG_VOLUME_GROUP, VOLUME_GROUP_NAME); 817 return; 818 } 819 if (!mUseCoreAudioVolume || groupName != null) { 820 return; 821 } 822 mCarServiceLocalLog.log("Found " + TAG_VOLUME_GROUP + " " + VOLUME_GROUP_NAME 823 + " empty while relying on core volume groups," 824 + " resetting use core volume to false"); 825 mUseCoreAudioVolume = false; 826 } 827 getActivationVolumeConfig(XmlPullParser parser)828 private CarActivationVolumeConfig getActivationVolumeConfig(XmlPullParser parser) { 829 String activationVolumeConfigName = parser.getAttributeValue(NAMESPACE, 830 ATTR_ACTIVATION_VOLUME_CONFIG); 831 if (activationVolumeConfigName != null) { 832 if (isVersionLessThanFour()) { 833 throw new IllegalArgumentException(TAG_VOLUME_GROUP + " " 834 + ATTR_ACTIVATION_VOLUME_CONFIG 835 + " attribute not supported for versions less than " 836 + SUPPORTED_VERSION_4 + ", but current version is " 837 + mCurrentVersion); 838 } 839 if (!mConfigNameToActivationVolumeConfig 840 .containsKey(activationVolumeConfigName)) { 841 throw new IllegalArgumentException(TAG_ACTIVATION_VOLUME_CONFIG + " with " 842 + ATTR_ACTIVATION_VOLUME_CONFIG_NAME + " attribute of " 843 + activationVolumeConfigName + " does not exist"); 844 } 845 if (Flags.carAudioMinMaxActivationVolume()) { 846 return mConfigNameToActivationVolumeConfig.get(activationVolumeConfigName); 847 } 848 } 849 if (!Flags.carAudioMinMaxActivationVolume()) { 850 mCarServiceLocalLog.log("Found " + TAG_VOLUME_GROUP + " " 851 + ATTR_ACTIVATION_VOLUME_CONFIG 852 + " attribute while min/max activation volume is disabled"); 853 } 854 return DEFAULT_ACTIVATION_VOLUME; 855 } 856 parseActivationVolumeConfigs(XmlPullParser parser)857 private void parseActivationVolumeConfigs(XmlPullParser parser) 858 throws XmlPullParserException, IOException { 859 while (parser.next() != XmlPullParser.END_TAG) { 860 if (parser.getEventType() != XmlPullParser.START_TAG) { 861 continue; 862 } 863 if (Objects.equals(parser.getName(), TAG_ACTIVATION_VOLUME_CONFIG)) { 864 parseActivationVolumeConfig(parser); 865 } else { 866 CarAudioParserUtils.skip(parser); 867 } 868 } 869 } 870 parseActivationVolumeConfig(XmlPullParser parser)871 private void parseActivationVolumeConfig(XmlPullParser parser) 872 throws XmlPullParserException, IOException { 873 String activationConfigName = parser.getAttributeValue(NAMESPACE, 874 ATTR_ACTIVATION_VOLUME_CONFIG_NAME); 875 Objects.requireNonNull(activationConfigName, TAG_ACTIVATION_VOLUME_CONFIG + " " 876 + ATTR_ACTIVATION_VOLUME_CONFIG_NAME + " attribute must be present."); 877 if (mConfigNameToActivationVolumeConfig.containsKey(activationConfigName)) { 878 throw new IllegalArgumentException(ATTR_ACTIVATION_VOLUME_CONFIG_NAME + " " 879 + activationConfigName + " repeats, " + ATTR_ACTIVATION_VOLUME_CONFIG_NAME 880 + " can not repeat."); 881 } 882 int configEntryCount = 0; 883 while (parser.next() != XmlPullParser.END_TAG) { 884 if (parser.getEventType() != XmlPullParser.START_TAG) { 885 continue; 886 } 887 if (Objects.equals(parser.getName(), TAG_ACTIVATION_VOLUME_CONFIG_ENTRY)) { 888 if (configEntryCount == 0) { 889 CarActivationVolumeConfig config = parseActivationVolumeConfigEntry(parser); 890 mConfigNameToActivationVolumeConfig.put(activationConfigName, config); 891 configEntryCount++; 892 } else { 893 throw new IllegalArgumentException("Multiple " 894 + TAG_ACTIVATION_VOLUME_CONFIG_ENTRY + "s in one " 895 + TAG_ACTIVATION_VOLUME_CONFIG + " is not supported"); 896 } 897 } 898 CarAudioParserUtils.skip(parser); 899 } 900 } 901 parseActivationVolumeConfigEntry(XmlPullParser parser)902 private CarActivationVolumeConfig parseActivationVolumeConfigEntry(XmlPullParser parser) { 903 int activationVolumeInvocationTypes = parseVolumeGroupActivationVolumeTypes(parser); 904 int minActivationVolumePercentage = parseVolumeGroupActivationVolumePercentage(parser, 905 /* isMax= */ false); 906 int maxActivationVolumePercentage = parseVolumeGroupActivationVolumePercentage(parser, 907 /* isMax= */ true); 908 validateMinMaxActivationVolume(maxActivationVolumePercentage, 909 minActivationVolumePercentage); 910 return new CarActivationVolumeConfig(activationVolumeInvocationTypes, 911 minActivationVolumePercentage, maxActivationVolumePercentage); 912 } 913 parseVolumeGroupActivationVolumePercentage(XmlPullParser parser, boolean isMax)914 private int parseVolumeGroupActivationVolumePercentage(XmlPullParser parser, boolean isMax) { 915 int defaultValue = isMax ? ACTIVATION_VOLUME_PERCENTAGE_MAX 916 : ACTIVATION_VOLUME_PERCENTAGE_MIN; 917 String attr = isMax ? ATTR_MAX_ACTIVATION_VOLUME_PERCENTAGE 918 : ATTR_MIN_ACTIVATION_VOLUME_PERCENTAGE; 919 String activationPercentageString = parser.getAttributeValue(NAMESPACE, attr); 920 if (activationPercentageString == null) { 921 return defaultValue; 922 } 923 return CarAudioParserUtils.parsePositiveIntAttribute(attr, activationPercentageString); 924 } 925 parseVolumeGroupActivationVolumeTypes(XmlPullParser parser)926 private int parseVolumeGroupActivationVolumeTypes(XmlPullParser parser) { 927 String activationPercentageString = parser.getAttributeValue(NAMESPACE, 928 ATTR_ACTIVATION_VOLUME_INVOCATION_TYPE); 929 if (activationPercentageString == null) { 930 return ACTIVATION_VOLUME_INVOCATION_TYPE; 931 } 932 if (Objects.equals(activationPercentageString, ACTIVATION_VOLUME_INVOCATION_TYPE_ON_BOOT)) { 933 return CarActivationVolumeConfig.ACTIVATION_VOLUME_ON_BOOT; 934 } else if (Objects.equals(activationPercentageString, 935 ACTIVATION_VOLUME_INVOCATION_TYPE_ON_SOURCE_CHANGED)) { 936 return CarActivationVolumeConfig.ACTIVATION_VOLUME_ON_BOOT 937 | CarActivationVolumeConfig.ACTIVATION_VOLUME_ON_SOURCE_CHANGED; 938 } else if (Objects.equals(activationPercentageString, 939 ACTIVATION_VOLUME_INVOCATION_TYPE_ON_PLAYBACK_CHANGED)) { 940 return CarActivationVolumeConfig.ACTIVATION_VOLUME_ON_BOOT 941 | CarActivationVolumeConfig.ACTIVATION_VOLUME_ON_SOURCE_CHANGED 942 | CarActivationVolumeConfig.ACTIVATION_VOLUME_ON_PLAYBACK_CHANGED; 943 } else { 944 throw new IllegalArgumentException(" Value " + activationPercentageString 945 + " is invalid for " + TAG_VOLUME_GROUP + " " 946 + ATTR_ACTIVATION_VOLUME_INVOCATION_TYPE); 947 } 948 } 949 parseVolumeGroup(XmlPullParser parser, CarVolumeGroupFactory groupFactory)950 private boolean parseVolumeGroup(XmlPullParser parser, CarVolumeGroupFactory groupFactory) 951 throws XmlPullParserException, IOException { 952 boolean valid = true; 953 while (parser.next() != XmlPullParser.END_TAG) { 954 if (parser.getEventType() != XmlPullParser.START_TAG) continue; 955 if (Objects.equals(parser.getName(), TAG_AUDIO_DEVICE)) { 956 if (!parseVolumeGroupDeviceToContextMapping(parser, groupFactory)) { 957 valid = false; 958 } 959 } else { 960 CarAudioParserUtils.skip(parser); 961 } 962 } 963 return valid; 964 } 965 parseVolumeGroupDeviceToContextMapping(XmlPullParser parser, CarVolumeGroupFactory groupFactory)966 private boolean parseVolumeGroupDeviceToContextMapping(XmlPullParser parser, 967 CarVolumeGroupFactory groupFactory) throws XmlPullParserException, IOException { 968 String address = parser.getAttributeValue(NAMESPACE, ATTR_DEVICE_ADDRESS); 969 int type = parseAudioDeviceType(parser); 970 boolean valid = validateOutputAudioDevice(address, type); 971 parseVolumeGroupContexts(parser, groupFactory, address, type); 972 return valid; 973 } 974 parseAudioDeviceType(XmlPullParser parser)975 private int parseAudioDeviceType(XmlPullParser parser) { 976 String typeString = parser.getAttributeValue(NAMESPACE, ATTR_DEVICE_TYPE); 977 if (typeString == null) { 978 return TYPE_BUS; 979 } 980 if (isVersionLessThanFour()) { 981 throw new IllegalArgumentException("Audio device type " + typeString 982 + " not supported for versions less than " + SUPPORTED_VERSION_4 983 + ", but current version is " + mCurrentVersion); 984 } 985 return ConstantDebugUtils.toValue(AudioDeviceInfo.class, typeString); 986 } 987 parseApplyFadeConfigs(XmlPullParser parser, CarAudioZoneConfig.Builder zoneConfigBuilder)988 private void parseApplyFadeConfigs(XmlPullParser parser, 989 CarAudioZoneConfig.Builder zoneConfigBuilder) 990 throws XmlPullParserException, IOException { 991 Preconditions.checkArgument(!isVersionLessThanFour(), 992 "Fade configurations not available in versions < 4"); 993 // ignore fade configurations when feature is not enabled 994 if (!mUseFadeManagerConfiguration) { 995 String message = "Skipped applying fade configs since feature flag is disabled"; 996 Slogf.i(TAG, message); 997 mCarServiceLocalLog.log(message); 998 CarAudioParserUtils.skip(parser); 999 return; 1000 } 1001 1002 Objects.requireNonNull(mCarAudioFadeConfigurationHelper, 1003 "Car audio fade configuration helper can not be null when parsing fade configs"); 1004 while (parser.next() != XmlPullParser.END_TAG) { 1005 if (parser.getEventType() != XmlPullParser.START_TAG) { 1006 continue; 1007 } 1008 if (Objects.equals(parser.getName(), FADE_CONFIG)) { 1009 parseFadeConfig(parser, zoneConfigBuilder); 1010 } else { 1011 CarAudioParserUtils.skip(parser); 1012 } 1013 } 1014 } 1015 parseFadeConfig(XmlPullParser parser, CarAudioZoneConfig.Builder zoneConfigBuilder)1016 private void parseFadeConfig(XmlPullParser parser, 1017 CarAudioZoneConfig.Builder zoneConfigBuilder) 1018 throws XmlPullParserException, IOException { 1019 String name = parser.getAttributeValue(NAMESPACE, FADE_CONFIG_NAME); 1020 boolean isDefault = Boolean.parseBoolean( 1021 parser.getAttributeValue(NAMESPACE, FADE_CONFIG_IS_DEFAULT)); 1022 validateFadeConfig(name); 1023 CarAudioFadeConfiguration afc = 1024 mCarAudioFadeConfigurationHelper.getCarAudioFadeConfiguration(name); 1025 if (isDefault) { 1026 zoneConfigBuilder.setDefaultCarAudioFadeConfiguration(afc); 1027 CarAudioParserUtils.skip(parser); 1028 } else { 1029 parseTransientFadeConfigs(parser, zoneConfigBuilder, afc); 1030 } 1031 } 1032 parseTransientFadeConfigs(XmlPullParser parser, CarAudioZoneConfig.Builder zoneConfigBuilder, CarAudioFadeConfiguration carAudioFadeConfiguration)1033 private void parseTransientFadeConfigs(XmlPullParser parser, 1034 CarAudioZoneConfig.Builder zoneConfigBuilder, 1035 CarAudioFadeConfiguration carAudioFadeConfiguration) 1036 throws XmlPullParserException, IOException { 1037 boolean hasAudioAttributes = false; 1038 while (parser.next() != XmlPullParser.END_TAG) { 1039 if (parser.getEventType() != XmlPullParser.START_TAG) { 1040 continue; 1041 } 1042 if (Objects.equals(parser.getName(), CarAudioParserUtils.TAG_AUDIO_ATTRIBUTES)) { 1043 List<AudioAttributes> attributes = 1044 CarAudioParserUtils.parseAudioAttributes(parser, 1045 CarAudioParserUtils.TAG_AUDIO_ATTRIBUTES); 1046 for (int index = 0; index < attributes.size(); index++) { 1047 hasAudioAttributes = true; 1048 zoneConfigBuilder.setCarAudioFadeConfigurationForAudioAttributes( 1049 attributes.get(index), carAudioFadeConfiguration); 1050 } 1051 } else { 1052 CarAudioParserUtils.skip(parser); 1053 } 1054 } 1055 Preconditions.checkArgument(hasAudioAttributes, 1056 "Transient fade configs must have valid audio attributes"); 1057 } 1058 validateFadeConfig(String name)1059 private void validateFadeConfig(String name) { 1060 Objects.requireNonNull(name, "Fade config name cannot be null"); 1061 Preconditions.checkArgument(!name.isEmpty(), "Fade config name cannot be empty"); 1062 Preconditions.checkArgument(mCarAudioFadeConfigurationHelper.isConfigAvailable(name), 1063 "No config available for name:%s", name); 1064 } 1065 isVersionLessThanFour()1066 private boolean isVersionLessThanFour() { 1067 return mCurrentVersion < SUPPORTED_VERSION_4; 1068 } 1069 isVersionFourOrGreater()1070 private boolean isVersionFourOrGreater() { 1071 return mCurrentVersion >= SUPPORTED_VERSION_4; 1072 } 1073 validateMinMaxActivationVolume(int maxActivationVolume, int minActivationVolume)1074 private void validateMinMaxActivationVolume(int maxActivationVolume, 1075 int minActivationVolume) { 1076 if (!Flags.carAudioMinMaxActivationVolume()) { 1077 return; 1078 } 1079 Preconditions.checkArgument(maxActivationVolume >= ACTIVATION_VOLUME_PERCENTAGE_MIN 1080 && maxActivationVolume <= ACTIVATION_VOLUME_PERCENTAGE_MAX, 1081 "%s %s attribute is %s but can not be outside the range (%s,%s)", 1082 TAG_VOLUME_GROUP, ATTR_MAX_ACTIVATION_VOLUME_PERCENTAGE, maxActivationVolume, 1083 ACTIVATION_VOLUME_PERCENTAGE_MIN, ACTIVATION_VOLUME_PERCENTAGE_MAX); 1084 Preconditions.checkArgument(minActivationVolume >= ACTIVATION_VOLUME_PERCENTAGE_MIN 1085 && minActivationVolume <= ACTIVATION_VOLUME_PERCENTAGE_MAX, 1086 "%s %s attribute is %s but can not be outside the range (%s,%s)", 1087 TAG_VOLUME_GROUP, ATTR_MIN_ACTIVATION_VOLUME_PERCENTAGE, minActivationVolume, 1088 ACTIVATION_VOLUME_PERCENTAGE_MIN, ACTIVATION_VOLUME_PERCENTAGE_MAX); 1089 Preconditions.checkArgument(minActivationVolume < maxActivationVolume, 1090 "%s %s is %s but can not be larger than or equal to %s attribute %s", 1091 TAG_VOLUME_GROUP, ATTR_MIN_ACTIVATION_VOLUME_PERCENTAGE, minActivationVolume, 1092 ATTR_MAX_ACTIVATION_VOLUME_PERCENTAGE, maxActivationVolume); 1093 } 1094 1095 private boolean validateOutputAudioDevice(String address, int type) { 1096 if (!Flags.carAudioDynamicDevices() && TextUtils.isEmpty(address)) { 1097 // If the version is four or greater, we can only return that the output device is not 1098 // valid since we can not crash. The configuration will only skip reading configuration. 1099 if (isVersionFourOrGreater()) { 1100 mCarServiceLocalLog.log("Found invalid device while dynamic device is disabled," 1101 + " device address is empty for device type " 1102 + DebugUtils.constantToString(AudioDeviceInfo.class, 1103 /* prefix= */ "TYPE_", type)); 1104 return false; 1105 } 1106 throw new IllegalStateException("Output device address must be specified"); 1107 } 1108 1109 if (!isValidAudioDeviceTypeOut(type)) { 1110 // If the version is four or greater, we can only return that the output device is not 1111 // valid since we can not crash. The configuration will only skip reading configuration. 1112 if (isVersionFourOrGreater() && !Flags.carAudioDynamicDevices()) { 1113 mCarServiceLocalLog.log("Found invalid device type while dynamic device is" 1114 + " disabled, device address " + address + " and device type " 1115 + DebugUtils.constantToString(AudioDeviceInfo.class, 1116 /* prefix= */ "TYPE_", type)); 1117 return false; 1118 } 1119 throw new IllegalStateException("Output device type " + DebugUtils.constantToString( 1120 AudioDeviceInfo.class, /* prefix= */ "TYPE_", type) + " is not valid"); 1121 } 1122 1123 boolean requiresDeviceAddress = type == TYPE_BUS; 1124 if (requiresDeviceAddress && !mAddressToCarAudioDeviceInfo.containsKey(address)) { 1125 throw new IllegalStateException("Output device address " + address 1126 + " does not belong to any configured output device."); 1127 } 1128 if (CarAudioUtils.isDynamicDeviceType(type) && !mUseCoreAudioVolume) { 1129 mCarServiceLocalLog.log("Found invalid device setup," 1130 + " dynamic device " + DebugUtils.constantToString(AudioDeviceInfo.class, 1131 /* prefix= */ "TYPE_", type) + " requires core audio volume management" 1132 + " but audioUseCoreVolume is false."); 1133 return false; 1134 } 1135 return true; 1136 } 1137 1138 private void parseVolumeGroupContexts( 1139 XmlPullParser parser, CarVolumeGroupFactory groupFactory, String address, int type) 1140 throws XmlPullParserException, IOException { 1141 while (parser.next() != XmlPullParser.END_TAG) { 1142 if (parser.getEventType() != XmlPullParser.START_TAG) continue; 1143 if (Objects.equals(parser.getName(), TAG_CONTEXT)) { 1144 @AudioContext int carAudioContextId = parseCarAudioContextId( 1145 parser.getAttributeValue(NAMESPACE, ATTR_CONTEXT_NAME)); 1146 validateCarAudioContextSupport(carAudioContextId); 1147 CarAudioDeviceInfo info = getCarAudioDeviceInfo(address, type); 1148 groupFactory.setDeviceInfoForContext(carAudioContextId, info); 1149 1150 // If V1, default new contexts to same device as DEFAULT_AUDIO_USAGE 1151 if (isVersionOne() && carAudioContextId == mCarAudioContext 1152 .getContextForAudioAttribute(CAR_DEFAULT_AUDIO_ATTRIBUTE)) { 1153 groupFactory.setNonLegacyContexts(info); 1154 } 1155 } 1156 // Always skip to upper level since we're at the lowest. 1157 CarAudioParserUtils.skip(parser); 1158 } 1159 } 1160 1161 private CarAudioDeviceInfo getCarAudioDeviceInfo(String address, int type) { 1162 if (type == TYPE_BUS) { 1163 return mAddressToCarAudioDeviceInfo.get(address); 1164 } 1165 1166 String newAddress = address == null ? "" : address; 1167 return new CarAudioDeviceInfo(mAudioManager, 1168 new AudioDeviceAttributes(AudioDeviceAttributes.ROLE_OUTPUT, type, newAddress)); 1169 } 1170 1171 private boolean isVersionLessThanThree() { 1172 return mCurrentVersion < SUPPORTED_VERSION_3; 1173 } 1174 1175 private boolean isVersionOne() { 1176 return mCurrentVersion == SUPPORTED_VERSION_1; 1177 } 1178 1179 private @AudioContext int parseCarAudioContextId(String context) { 1180 return mContextNameToId.getOrDefault(context.toLowerCase(ROOT), 1181 CarAudioContext.getInvalidContext()); 1182 } 1183 1184 private void validateCarAudioContextSupport(@AudioContext int audioContext) { 1185 if (isVersionOne() && CarAudioContext.getCarSystemContextIds().contains(audioContext)) { 1186 throw new IllegalArgumentException("Non-legacy audio contexts such as " 1187 + mCarAudioContext.toString(audioContext) + " are not supported in " 1188 + "car_audio_configuration.xml version " + SUPPORTED_VERSION_1); 1189 } 1190 } 1191 1192 private int getNextSecondaryZoneId() { 1193 int zoneId = mNextSecondaryZoneId; 1194 mNextSecondaryZoneId += 1; 1195 return zoneId; 1196 } 1197 1198 public CarAudioContext getCarAudioContext() { 1199 return mCarAudioContext; 1200 } 1201 1202 public List<CarAudioDeviceInfo> getMirrorDeviceInfos() { 1203 return mMirroringDevices; 1204 } 1205 1206 /** 1207 * Car audio service supports a subset of the output devices, including most dynamic 1208 * devices, built in speaker, and bus devices. 1209 */ 1210 private static boolean isValidAudioDeviceTypeOut(int type) { 1211 if (!Flags.carAudioDynamicDevices()) { 1212 return type == TYPE_BUS; 1213 } 1214 1215 switch (type) { 1216 case TYPE_BUILTIN_SPEAKER: 1217 case TYPE_WIRED_HEADSET: 1218 case TYPE_WIRED_HEADPHONES: 1219 case TYPE_BLUETOOTH_A2DP: 1220 case TYPE_HDMI: 1221 case TYPE_USB_ACCESSORY: 1222 case TYPE_USB_DEVICE: 1223 case TYPE_USB_HEADSET: 1224 case TYPE_AUX_LINE: 1225 case TYPE_BUS: 1226 case TYPE_BLE_HEADSET: 1227 case TYPE_BLE_SPEAKER: 1228 case TYPE_BLE_BROADCAST: 1229 return true; 1230 default: 1231 return false; 1232 } 1233 } 1234 } 1235