• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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