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