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