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 20 import static com.android.car.audio.CarAudioUtils.isMicrophoneInputDevice; 21 22 import android.annotation.NonNull; 23 import android.media.AudioDeviceAttributes; 24 import android.media.AudioDeviceInfo; 25 import android.text.TextUtils; 26 import android.util.ArrayMap; 27 import android.util.ArraySet; 28 import android.util.SparseArray; 29 import android.util.SparseIntArray; 30 import android.util.Xml; 31 32 import com.android.car.audio.CarAudioContext.AudioContext; 33 import com.android.internal.util.Preconditions; 34 35 import org.xmlpull.v1.XmlPullParser; 36 import org.xmlpull.v1.XmlPullParserException; 37 38 import java.io.IOException; 39 import java.io.InputStream; 40 import java.util.ArrayList; 41 import java.util.Arrays; 42 import java.util.List; 43 import java.util.Map; 44 import java.util.Objects; 45 import java.util.Set; 46 import java.util.stream.Collectors; 47 48 /** 49 * A helper class loads all audio zones from the configuration XML file. 50 */ 51 /* package */ class CarAudioZonesHelper { 52 private static final String NAMESPACE = null; 53 private static final String TAG_ROOT = "carAudioConfiguration"; 54 private static final String TAG_AUDIO_ZONES = "zones"; 55 private static final String TAG_AUDIO_ZONE = "zone"; 56 private static final String TAG_VOLUME_GROUPS = "volumeGroups"; 57 private static final String TAG_VOLUME_GROUP = "group"; 58 private static final String TAG_AUDIO_DEVICE = "device"; 59 private static final String TAG_CONTEXT = "context"; 60 private static final String ATTR_VERSION = "version"; 61 private static final String ATTR_IS_PRIMARY = "isPrimary"; 62 private static final String ATTR_ZONE_NAME = "name"; 63 private static final String ATTR_DEVICE_ADDRESS = "address"; 64 private static final String ATTR_CONTEXT_NAME = "context"; 65 private static final String ATTR_ZONE_ID = "audioZoneId"; 66 private static final String ATTR_OCCUPANT_ZONE_ID = "occupantZoneId"; 67 private static final String TAG_INPUT_DEVICES = "inputDevices"; 68 private static final String TAG_INPUT_DEVICE = "inputDevice"; 69 private static final int INVALID_VERSION = -1; 70 private static final int SUPPORTED_VERSION_1 = 1; 71 private static final int SUPPORTED_VERSION_2 = 2; 72 private static final SparseIntArray SUPPORTED_VERSIONS; 73 74 75 private static final Map<String, Integer> CONTEXT_NAME_MAP; 76 77 static { 78 CONTEXT_NAME_MAP = new ArrayMap<>(CarAudioContext.CONTEXTS.length); 79 CONTEXT_NAME_MAP.put("music", CarAudioContext.MUSIC); 80 CONTEXT_NAME_MAP.put("navigation", CarAudioContext.NAVIGATION); 81 CONTEXT_NAME_MAP.put("voice_command", CarAudioContext.VOICE_COMMAND); 82 CONTEXT_NAME_MAP.put("call_ring", CarAudioContext.CALL_RING); 83 CONTEXT_NAME_MAP.put("call", CarAudioContext.CALL); 84 CONTEXT_NAME_MAP.put("alarm", CarAudioContext.ALARM); 85 CONTEXT_NAME_MAP.put("notification", CarAudioContext.NOTIFICATION); 86 CONTEXT_NAME_MAP.put("system_sound", CarAudioContext.SYSTEM_SOUND); 87 CONTEXT_NAME_MAP.put("emergency", CarAudioContext.EMERGENCY); 88 CONTEXT_NAME_MAP.put("safety", CarAudioContext.SAFETY); 89 CONTEXT_NAME_MAP.put("vehicle_status", CarAudioContext.VEHICLE_STATUS); 90 CONTEXT_NAME_MAP.put("announcement", CarAudioContext.ANNOUNCEMENT); 91 92 SUPPORTED_VERSIONS = new SparseIntArray(2); SUPPORTED_VERSIONS.put(SUPPORTED_VERSION_1, SUPPORTED_VERSION_1)93 SUPPORTED_VERSIONS.put(SUPPORTED_VERSION_1, SUPPORTED_VERSION_1); SUPPORTED_VERSIONS.put(SUPPORTED_VERSION_2, SUPPORTED_VERSION_2)94 SUPPORTED_VERSIONS.put(SUPPORTED_VERSION_2, SUPPORTED_VERSION_2); 95 } 96 97 // Same contexts as defined in android.hardware.automotive.audiocontrol.V1_0.ContextNumber 98 static final int[] LEGACY_CONTEXTS = new int[]{ 99 CarAudioContext.MUSIC, 100 CarAudioContext.NAVIGATION, 101 CarAudioContext.VOICE_COMMAND, 102 CarAudioContext.CALL_RING, 103 CarAudioContext.CALL, 104 CarAudioContext.ALARM, 105 CarAudioContext.NOTIFICATION, 106 CarAudioContext.SYSTEM_SOUND 107 }; 108 isLegacyContext(@udioContext int audioContext)109 private static boolean isLegacyContext(@AudioContext int audioContext) { 110 return Arrays.binarySearch(LEGACY_CONTEXTS, audioContext) >= 0; 111 } 112 113 private static final List<Integer> NON_LEGACY_CONTEXTS = new ArrayList<>( 114 CarAudioContext.CONTEXTS.length - LEGACY_CONTEXTS.length); 115 116 static { 117 for (@AudioContext int audioContext : CarAudioContext.CONTEXTS) { 118 if (!isLegacyContext(audioContext)) { 119 NON_LEGACY_CONTEXTS.add(audioContext); 120 } 121 } 122 } 123 setNonLegacyContexts(CarVolumeGroup.Builder groupBuilder, CarAudioDeviceInfo info)124 static void setNonLegacyContexts(CarVolumeGroup.Builder groupBuilder, 125 CarAudioDeviceInfo info) { 126 for (@AudioContext int audioContext : NON_LEGACY_CONTEXTS) { 127 groupBuilder.setDeviceInfoForContext(audioContext, info); 128 } 129 } 130 131 private final CarAudioSettings mCarAudioSettings; 132 private final Map<String, CarAudioDeviceInfo> mAddressToCarAudioDeviceInfo; 133 private final Map<String, AudioDeviceInfo> mAddressToInputAudioDeviceInfoForAllInputDevices; 134 private final InputStream mInputStream; 135 private final SparseIntArray mZoneIdToOccupantZoneIdMapping; 136 private final Set<Integer> mAudioZoneIds; 137 private final Set<String> mAssignedInputAudioDevices; 138 private final boolean mUseCarVolumeGroupMute; 139 140 private int mNextSecondaryZoneId; 141 private int mCurrentVersion; 142 143 /** 144 * <p><b>Note: <b/> CarAudioZonesHelper is expected to be used from a single thread. This 145 * should be the same thread that originally called new CarAudioZonesHelper. 146 */ CarAudioZonesHelper(@onNull CarAudioSettings carAudioSettings, @NonNull InputStream inputStream, @NonNull List<CarAudioDeviceInfo> carAudioDeviceInfos, @NonNull AudioDeviceInfo[] inputDeviceInfo, boolean useCarVolumeGroupMute)147 CarAudioZonesHelper(@NonNull CarAudioSettings carAudioSettings, 148 @NonNull InputStream inputStream, 149 @NonNull List<CarAudioDeviceInfo> carAudioDeviceInfos, 150 @NonNull AudioDeviceInfo[] inputDeviceInfo, boolean useCarVolumeGroupMute) { 151 mCarAudioSettings = Objects.requireNonNull(carAudioSettings); 152 mInputStream = Objects.requireNonNull(inputStream); 153 Objects.requireNonNull(carAudioDeviceInfos); 154 Objects.requireNonNull(inputDeviceInfo); 155 mAddressToCarAudioDeviceInfo = CarAudioZonesHelper.generateAddressToInfoMap( 156 carAudioDeviceInfos); 157 mAddressToInputAudioDeviceInfoForAllInputDevices = 158 CarAudioZonesHelper.generateAddressToInputAudioDeviceInfoMap(inputDeviceInfo); 159 mNextSecondaryZoneId = PRIMARY_AUDIO_ZONE + 1; 160 mZoneIdToOccupantZoneIdMapping = new SparseIntArray(); 161 mAudioZoneIds = new ArraySet<>(); 162 mAssignedInputAudioDevices = new ArraySet<>(); 163 mUseCarVolumeGroupMute = useCarVolumeGroupMute; 164 } 165 getCarAudioZoneIdToOccupantZoneIdMapping()166 SparseIntArray getCarAudioZoneIdToOccupantZoneIdMapping() { 167 return mZoneIdToOccupantZoneIdMapping; 168 } 169 loadAudioZones()170 SparseArray<CarAudioZone> loadAudioZones() throws IOException, XmlPullParserException { 171 return parseCarAudioZones(mInputStream); 172 } 173 generateAddressToInfoMap( List<CarAudioDeviceInfo> carAudioDeviceInfos)174 private static Map<String, CarAudioDeviceInfo> generateAddressToInfoMap( 175 List<CarAudioDeviceInfo> carAudioDeviceInfos) { 176 return carAudioDeviceInfos.stream() 177 .filter(info -> !TextUtils.isEmpty(info.getAddress())) 178 .collect(Collectors.toMap(CarAudioDeviceInfo::getAddress, info -> info)); 179 } 180 generateAddressToInputAudioDeviceInfoMap( @onNull AudioDeviceInfo[] inputAudioDeviceInfos)181 private static Map<String, AudioDeviceInfo> generateAddressToInputAudioDeviceInfoMap( 182 @NonNull AudioDeviceInfo[] inputAudioDeviceInfos) { 183 Map<String, AudioDeviceInfo> deviceAddressToInputDeviceMap = 184 new ArrayMap<>(inputAudioDeviceInfos.length); 185 for (int i = 0; i < inputAudioDeviceInfos.length; ++i) { 186 AudioDeviceInfo device = inputAudioDeviceInfos[i]; 187 if (device.isSource()) { 188 deviceAddressToInputDeviceMap.put(device.getAddress(), device); 189 } 190 } 191 return deviceAddressToInputDeviceMap; 192 } 193 parseCarAudioZones(InputStream stream)194 private SparseArray<CarAudioZone> parseCarAudioZones(InputStream stream) 195 throws XmlPullParserException, IOException { 196 XmlPullParser parser = Xml.newPullParser(); 197 parser.setFeature(XmlPullParser.FEATURE_PROCESS_NAMESPACES, NAMESPACE != null); 198 parser.setInput(stream, null); 199 200 // Ensure <carAudioConfiguration> is the root 201 parser.nextTag(); 202 parser.require(XmlPullParser.START_TAG, NAMESPACE, TAG_ROOT); 203 204 // Version check 205 final int versionNumber = Integer.parseInt( 206 parser.getAttributeValue(NAMESPACE, ATTR_VERSION)); 207 208 if (SUPPORTED_VERSIONS.get(versionNumber, INVALID_VERSION) == INVALID_VERSION) { 209 throw new IllegalArgumentException("Latest Supported version:" 210 + SUPPORTED_VERSION_2 + " , got version:" + versionNumber); 211 } 212 213 mCurrentVersion = versionNumber; 214 215 // Get all zones configured under <zones> tag 216 while (parser.next() != XmlPullParser.END_TAG) { 217 if (parser.getEventType() != XmlPullParser.START_TAG) continue; 218 if (TAG_AUDIO_ZONES.equals(parser.getName())) { 219 return parseAudioZones(parser); 220 } else { 221 skip(parser); 222 } 223 } 224 throw new RuntimeException(TAG_AUDIO_ZONES + " is missing from configuration"); 225 } 226 parseAudioZones(XmlPullParser parser)227 private SparseArray<CarAudioZone> parseAudioZones(XmlPullParser parser) 228 throws XmlPullParserException, IOException { 229 SparseArray<CarAudioZone> carAudioZones = new SparseArray<>(); 230 231 while (parser.next() != XmlPullParser.END_TAG) { 232 if (parser.getEventType() != XmlPullParser.START_TAG) continue; 233 if (TAG_AUDIO_ZONE.equals(parser.getName())) { 234 CarAudioZone zone = parseAudioZone(parser); 235 verifyOnlyOnePrimaryZone(zone, carAudioZones); 236 carAudioZones.put(zone.getId(), zone); 237 } else { 238 skip(parser); 239 } 240 } 241 242 verifyPrimaryZonePresent(carAudioZones); 243 addRemainingMicrophonesToPrimaryZone(carAudioZones); 244 return carAudioZones; 245 } 246 addRemainingMicrophonesToPrimaryZone(SparseArray<CarAudioZone> carAudioZones)247 private void addRemainingMicrophonesToPrimaryZone(SparseArray<CarAudioZone> carAudioZones) { 248 CarAudioZone primaryAudioZone = carAudioZones.get(PRIMARY_AUDIO_ZONE); 249 for (AudioDeviceInfo info : mAddressToInputAudioDeviceInfoForAllInputDevices.values()) { 250 if (!mAssignedInputAudioDevices.contains(info.getAddress()) 251 && isMicrophoneInputDevice(info)) { 252 primaryAudioZone.addInputAudioDevice(new AudioDeviceAttributes(info)); 253 } 254 } 255 } 256 verifyOnlyOnePrimaryZone(CarAudioZone newZone, SparseArray<CarAudioZone> zones)257 private void verifyOnlyOnePrimaryZone(CarAudioZone newZone, SparseArray<CarAudioZone> zones) { 258 if (newZone.getId() == PRIMARY_AUDIO_ZONE && zones.contains(PRIMARY_AUDIO_ZONE)) { 259 throw new RuntimeException("More than one zone parsed with primary audio zone ID: " 260 + PRIMARY_AUDIO_ZONE); 261 } 262 } 263 verifyPrimaryZonePresent(SparseArray<CarAudioZone> zones)264 private void verifyPrimaryZonePresent(SparseArray<CarAudioZone> zones) { 265 if (!zones.contains(PRIMARY_AUDIO_ZONE)) { 266 throw new RuntimeException("Primary audio zone is required"); 267 } 268 } 269 parseAudioZone(XmlPullParser parser)270 private CarAudioZone parseAudioZone(XmlPullParser parser) 271 throws XmlPullParserException, IOException { 272 final boolean isPrimary = Boolean.parseBoolean( 273 parser.getAttributeValue(NAMESPACE, ATTR_IS_PRIMARY)); 274 final String zoneName = parser.getAttributeValue(NAMESPACE, ATTR_ZONE_NAME); 275 final int audioZoneId = getZoneId(isPrimary, parser); 276 parseOccupantZoneId(audioZoneId, parser); 277 final CarAudioZone zone = new CarAudioZone(audioZoneId, zoneName); 278 while (parser.next() != XmlPullParser.END_TAG) { 279 if (parser.getEventType() != XmlPullParser.START_TAG) continue; 280 // Expect one <volumeGroups> in one audio zone 281 if (TAG_VOLUME_GROUPS.equals(parser.getName())) { 282 parseVolumeGroups(parser, zone); 283 } else if (TAG_INPUT_DEVICES.equals(parser.getName())) { 284 parseInputAudioDevices(parser, zone); 285 } else { 286 skip(parser); 287 } 288 } 289 return zone; 290 } 291 getZoneId(boolean isPrimary, XmlPullParser parser)292 private int getZoneId(boolean isPrimary, XmlPullParser parser) { 293 String audioZoneIdString = parser.getAttributeValue(NAMESPACE, ATTR_ZONE_ID); 294 if (isVersionOne()) { 295 Preconditions.checkArgument(audioZoneIdString == null, 296 "Invalid audio attribute %s" 297 + ", Please update car audio configurations file " 298 + "to version to 2 to use it.", ATTR_ZONE_ID); 299 return isPrimary ? PRIMARY_AUDIO_ZONE 300 : getNextSecondaryZoneId(); 301 } 302 // Primary zone does not need to define it 303 if (isPrimary && audioZoneIdString == null) { 304 return PRIMARY_AUDIO_ZONE; 305 } 306 Objects.requireNonNull(audioZoneIdString, () -> 307 "Requires " + ATTR_ZONE_ID + " for all audio zones."); 308 int zoneId = parsePositiveIntAttribute(ATTR_ZONE_ID, audioZoneIdString); 309 //Verify that primary zone id is PRIMARY_AUDIO_ZONE 310 if (isPrimary) { 311 Preconditions.checkArgument(zoneId == PRIMARY_AUDIO_ZONE, 312 "Primary zone %s must be %d or it can be left empty.", 313 ATTR_ZONE_ID, PRIMARY_AUDIO_ZONE); 314 } else { 315 Preconditions.checkArgument(zoneId != PRIMARY_AUDIO_ZONE, 316 "%s can only be %d for primary zone.", 317 ATTR_ZONE_ID, PRIMARY_AUDIO_ZONE); 318 } 319 validateAudioZoneIdIsUnique(zoneId); 320 return zoneId; 321 } 322 parseOccupantZoneId(int audioZoneId, XmlPullParser parser)323 private void parseOccupantZoneId(int audioZoneId, XmlPullParser parser) { 324 String occupantZoneIdString = parser.getAttributeValue(NAMESPACE, ATTR_OCCUPANT_ZONE_ID); 325 if (isVersionOne()) { 326 Preconditions.checkArgument(occupantZoneIdString == null, 327 "Invalid audio attribute %s" 328 + ", Please update car audio configurations file " 329 + "to version to 2 to use it.", ATTR_OCCUPANT_ZONE_ID); 330 return; 331 } 332 //Occupant id not required for all zones 333 if (occupantZoneIdString == null) { 334 return; 335 } 336 int occupantZoneId = parsePositiveIntAttribute(ATTR_OCCUPANT_ZONE_ID, occupantZoneIdString); 337 validateOccupantZoneIdIsUnique(occupantZoneId); 338 mZoneIdToOccupantZoneIdMapping.put(audioZoneId, occupantZoneId); 339 } 340 parsePositiveIntAttribute(String attribute, String integerString)341 private int parsePositiveIntAttribute(String attribute, String integerString) { 342 try { 343 return Integer.parseUnsignedInt(integerString); 344 } catch (NumberFormatException | IndexOutOfBoundsException e) { 345 throw new IllegalArgumentException(attribute + " must be a positive integer, but was \"" 346 + integerString + "\" instead.", e); 347 } 348 } 349 parseInputAudioDevices(XmlPullParser parser, CarAudioZone zone)350 private void parseInputAudioDevices(XmlPullParser parser, CarAudioZone zone) 351 throws IOException, XmlPullParserException { 352 if (isVersionOne()) { 353 throw new IllegalStateException( 354 TAG_INPUT_DEVICES + " are not supported in car_audio_configuration.xml version " 355 + SUPPORTED_VERSION_1); 356 } 357 while (parser.next() != XmlPullParser.END_TAG) { 358 if (parser.getEventType() != XmlPullParser.START_TAG) continue; 359 if (TAG_INPUT_DEVICE.equals(parser.getName())) { 360 String audioDeviceAddress = 361 parser.getAttributeValue(NAMESPACE, ATTR_DEVICE_ADDRESS); 362 validateInputAudioDeviceAddress(audioDeviceAddress); 363 AudioDeviceInfo info = 364 mAddressToInputAudioDeviceInfoForAllInputDevices.get(audioDeviceAddress); 365 Preconditions.checkArgument(info != null, 366 "%s %s of %s does not exist, add input device to" 367 + " audio_policy_configuration.xml.", 368 ATTR_DEVICE_ADDRESS, audioDeviceAddress, TAG_INPUT_DEVICE); 369 zone.addInputAudioDevice(new AudioDeviceAttributes(info)); 370 } 371 skip(parser); 372 } 373 } 374 validateInputAudioDeviceAddress(String audioDeviceAddress)375 private void validateInputAudioDeviceAddress(String audioDeviceAddress) { 376 Objects.requireNonNull(audioDeviceAddress, () -> 377 TAG_INPUT_DEVICE + " " + ATTR_DEVICE_ADDRESS + " attribute must be present."); 378 Preconditions.checkArgument(!audioDeviceAddress.isEmpty(), 379 "%s %s attribute can not be empty.", 380 TAG_INPUT_DEVICE, ATTR_DEVICE_ADDRESS); 381 if (mAssignedInputAudioDevices.contains(audioDeviceAddress)) { 382 throw new IllegalArgumentException(TAG_INPUT_DEVICE + " " + audioDeviceAddress 383 + " repeats, " + TAG_INPUT_DEVICES + " can not repeat."); 384 } 385 mAssignedInputAudioDevices.add(audioDeviceAddress); 386 } 387 validateOccupantZoneIdIsUnique(int occupantZoneId)388 private void validateOccupantZoneIdIsUnique(int occupantZoneId) { 389 if (mZoneIdToOccupantZoneIdMapping.indexOfValue(occupantZoneId) > -1) { 390 throw new IllegalArgumentException(ATTR_OCCUPANT_ZONE_ID + " " + occupantZoneId 391 + " is already associated with a zone"); 392 } 393 } 394 validateAudioZoneIdIsUnique(int audioZoneId)395 private void validateAudioZoneIdIsUnique(int audioZoneId) { 396 if (mAudioZoneIds.contains(audioZoneId)) { 397 throw new IllegalArgumentException(ATTR_ZONE_ID + " " + audioZoneId 398 + " is already associated with a zone"); 399 } 400 mAudioZoneIds.add(audioZoneId); 401 } 402 parseVolumeGroups(XmlPullParser parser, CarAudioZone zone)403 private void parseVolumeGroups(XmlPullParser parser, CarAudioZone zone) 404 throws XmlPullParserException, IOException { 405 int groupId = 0; 406 while (parser.next() != XmlPullParser.END_TAG) { 407 if (parser.getEventType() != XmlPullParser.START_TAG) continue; 408 if (TAG_VOLUME_GROUP.equals(parser.getName())) { 409 zone.addVolumeGroup(parseVolumeGroup(parser, zone.getId(), groupId)); 410 groupId++; 411 } else { 412 skip(parser); 413 } 414 } 415 } 416 parseVolumeGroup(XmlPullParser parser, int zoneId, int groupId)417 private CarVolumeGroup parseVolumeGroup(XmlPullParser parser, int zoneId, int groupId) 418 throws XmlPullParserException, IOException { 419 CarVolumeGroup.Builder groupBuilder = 420 new CarVolumeGroup.Builder(zoneId, groupId, mCarAudioSettings, 421 mUseCarVolumeGroupMute); 422 while (parser.next() != XmlPullParser.END_TAG) { 423 if (parser.getEventType() != XmlPullParser.START_TAG) continue; 424 if (TAG_AUDIO_DEVICE.equals(parser.getName())) { 425 String address = parser.getAttributeValue(NAMESPACE, ATTR_DEVICE_ADDRESS); 426 validateOutputDeviceExist(address); 427 parseVolumeGroupContexts(parser, groupBuilder, address); 428 } else { 429 skip(parser); 430 } 431 } 432 return groupBuilder.build(); 433 } 434 validateOutputDeviceExist(String address)435 private void validateOutputDeviceExist(String address) { 436 if (!mAddressToCarAudioDeviceInfo.containsKey(address)) { 437 throw new IllegalStateException(String.format( 438 "Output device address %s does not belong to any configured output device.", 439 address)); 440 } 441 } 442 parseVolumeGroupContexts( XmlPullParser parser, CarVolumeGroup.Builder groupBuilder, String address)443 private void parseVolumeGroupContexts( 444 XmlPullParser parser, CarVolumeGroup.Builder groupBuilder, String address) 445 throws XmlPullParserException, IOException { 446 while (parser.next() != XmlPullParser.END_TAG) { 447 if (parser.getEventType() != XmlPullParser.START_TAG) continue; 448 if (TAG_CONTEXT.equals(parser.getName())) { 449 @AudioContext int carAudioContext = parseCarAudioContext( 450 parser.getAttributeValue(NAMESPACE, ATTR_CONTEXT_NAME)); 451 validateCarAudioContextSupport(carAudioContext); 452 CarAudioDeviceInfo info = mAddressToCarAudioDeviceInfo.get(address); 453 groupBuilder.setDeviceInfoForContext(carAudioContext, info); 454 455 // If V1, default new contexts to same device as DEFAULT_AUDIO_USAGE 456 if (isVersionOne() && carAudioContext == CarAudioService.DEFAULT_AUDIO_CONTEXT) { 457 setNonLegacyContexts(groupBuilder, info); 458 } 459 } 460 // Always skip to upper level since we're at the lowest. 461 skip(parser); 462 } 463 } 464 isVersionOne()465 private boolean isVersionOne() { 466 return mCurrentVersion == SUPPORTED_VERSION_1; 467 } 468 skip(XmlPullParser parser)469 private void skip(XmlPullParser parser) throws XmlPullParserException, IOException { 470 if (parser.getEventType() != XmlPullParser.START_TAG) { 471 throw new IllegalStateException(); 472 } 473 int depth = 1; 474 while (depth != 0) { 475 switch (parser.next()) { 476 case XmlPullParser.END_TAG: 477 depth--; 478 break; 479 case XmlPullParser.START_TAG: 480 depth++; 481 break; 482 } 483 } 484 } 485 parseCarAudioContext(String context)486 private static @AudioContext int parseCarAudioContext(String context) { 487 return CONTEXT_NAME_MAP.getOrDefault(context.toLowerCase(), CarAudioContext.INVALID); 488 } 489 validateCarAudioContextSupport(@udioContext int audioContext)490 private void validateCarAudioContextSupport(@AudioContext int audioContext) { 491 if (isVersionOne() && NON_LEGACY_CONTEXTS.contains(audioContext)) { 492 throw new IllegalArgumentException(String.format( 493 "Non-legacy audio contexts such as %s are not supported in " 494 + "car_audio_configuration.xml version %d", 495 CarAudioContext.toString(audioContext), SUPPORTED_VERSION_1)); 496 } 497 } 498 getNextSecondaryZoneId()499 private int getNextSecondaryZoneId() { 500 int zoneId = mNextSecondaryZoneId; 501 mNextSecondaryZoneId += 1; 502 return zoneId; 503 } 504 } 505