1 /* 2 * Copyright (C) 2024 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.media.AudioAttributes.USAGE_NOTIFICATION_EVENT; 19 20 import static com.android.car.internal.ExcludeFromCodeCoverageGeneratedReport.PRIVATE_CONSTRUCTOR; 21 22 import android.car.builtin.media.AudioManagerHelper; 23 import android.media.AudioAttributes; 24 25 import com.android.car.internal.ExcludeFromCodeCoverageGeneratedReport; 26 27 import org.xmlpull.v1.XmlPullParser; 28 import org.xmlpull.v1.XmlPullParserException; 29 30 import java.io.IOException; 31 import java.util.ArrayList; 32 import java.util.List; 33 import java.util.Objects; 34 35 /** 36 * Utils for common parser functionalities used across multiple config files 37 */ 38 /* package */ final class CarAudioParserUtils { 39 40 private static final String NAMESPACE = null; 41 private static final int INVALID = -1; 42 43 public static final String TAG_AUDIO_ATTRIBUTES = "audioAttributes"; 44 public static final String TAG_AUDIO_ATTRIBUTE = "audioAttribute"; 45 public static final String TAG_USAGE = "usage"; 46 public static final String ATTR_USAGE_VALUE = "value"; 47 public static final String ATTR_NAME = "name"; 48 public static final String ATTR_CONTENT_TYPE = "contentType"; 49 public static final String ATTR_USAGE = "usage"; 50 public static final String ATTR_TAGS = "tags"; 51 52 @ExcludeFromCodeCoverageGeneratedReport(reason = PRIVATE_CONSTRUCTOR) CarAudioParserUtils()53 private CarAudioParserUtils() { 54 throw new UnsupportedOperationException( 55 "CarAudioParserUtils class is non-instantiable, contains static members only"); 56 } 57 parseAudioAttributes(XmlPullParser parser, String sectionName)58 /* package */ static List<AudioAttributes> parseAudioAttributes(XmlPullParser parser, 59 String sectionName) throws XmlPullParserException, IOException { 60 List<AudioAttributes> attrs = new ArrayList<>(); 61 while (parser.next() != XmlPullParser.END_TAG) { 62 if (parser.getEventType() != XmlPullParser.START_TAG) { 63 continue; 64 } 65 66 if (Objects.equals(parser.getName(), TAG_USAGE)) { 67 AudioAttributes.Builder attributesBuilder = new AudioAttributes.Builder(); 68 parseUsage(parser, attributesBuilder, ATTR_USAGE_VALUE); 69 AudioAttributes attributes = attributesBuilder.build(); 70 attrs.add(attributes); 71 } else if (Objects.equals(parser.getName(), TAG_AUDIO_ATTRIBUTE)) { 72 attrs.add(parseAudioAttribute(parser, sectionName)); 73 } 74 // Always skip to upper level since we're at the lowest. 75 skip(parser); 76 } 77 if (attrs.isEmpty()) { 78 throw new IllegalArgumentException("No attributes for config: " + sectionName); 79 } 80 return attrs; 81 } 82 parseAudioAttribute(XmlPullParser parser, String sectionName)83 static AudioAttributes parseAudioAttribute(XmlPullParser parser, 84 String sectionName) throws XmlPullParserException, IOException { 85 AudioAttributes.Builder attributesBuilder = new AudioAttributes.Builder(); 86 // Usage, ContentType and tags are optional but at least one value must be 87 // provided to build a valid audio attributes 88 boolean hasValidUsage = parseUsage(parser, attributesBuilder, ATTR_USAGE); 89 boolean hasValidContentType = parseContentType(parser, attributesBuilder); 90 boolean hasValidTags = parseTags(parser, attributesBuilder); 91 if (!(hasValidUsage || hasValidContentType || hasValidTags)) { 92 throw new RuntimeException("Empty attributes for context: " + sectionName); 93 } 94 return attributesBuilder.build(); 95 } 96 parseUsage(XmlPullParser parser, AudioAttributes.Builder builder, String attrValue)97 private static boolean parseUsage(XmlPullParser parser, AudioAttributes.Builder builder, 98 String attrValue) throws XmlPullParserException, IOException { 99 int usage = parseUsageValue(parser, attrValue); 100 if (usage == INVALID) { 101 return false; 102 } 103 104 if (AudioAttributes.isSystemUsage(usage)) { 105 builder.setSystemUsage(usage); 106 } else { 107 builder.setUsage(usage); 108 } 109 return true; 110 } 111 parseUsageValue(XmlPullParser parser, String attrValue)112 static int parseUsageValue(XmlPullParser parser, String attrValue) 113 throws XmlPullParserException, IOException { 114 String usageLiteral = parser.getAttributeValue(NAMESPACE, attrValue); 115 if (usageLiteral == null) { 116 return INVALID; 117 } 118 119 int usage = AudioManagerHelper.xsdStringToUsage(usageLiteral); 120 // TODO (b/248106031): Remove once AUDIO_USAGE_NOTIFICATION_EVENT is fixed in core 121 if (Objects.equals(usageLiteral, "AUDIO_USAGE_NOTIFICATION_EVENT")) { 122 usage = USAGE_NOTIFICATION_EVENT; 123 } 124 return usage; 125 } 126 parseContentType(XmlPullParser parser, AudioAttributes.Builder builder)127 private static boolean parseContentType(XmlPullParser parser, AudioAttributes.Builder builder) 128 throws XmlPullParserException, IOException { 129 int contentType = parseContentTypeValue(parser); 130 if (contentType == INVALID) { 131 return false; 132 } 133 134 builder.setContentType(contentType); 135 return true; 136 } 137 parseContentTypeValue(XmlPullParser parser)138 static int parseContentTypeValue(XmlPullParser parser) 139 throws XmlPullParserException, IOException { 140 String contentTypeLiteral = parser.getAttributeValue(NAMESPACE, ATTR_CONTENT_TYPE); 141 if (contentTypeLiteral == null) { 142 return INVALID; 143 } 144 return AudioManagerHelper.xsdStringToContentType(contentTypeLiteral); 145 } 146 parseTags(XmlPullParser parser, AudioAttributes.Builder builder)147 private static boolean parseTags(XmlPullParser parser, AudioAttributes.Builder builder) 148 throws XmlPullParserException, IOException { 149 String tagsLiteral = parser.getAttributeValue(NAMESPACE, ATTR_TAGS); 150 if (tagsLiteral == null) { 151 return false; 152 } 153 AudioManagerHelper.addTagToAudioAttributes(builder, tagsLiteral); 154 return true; 155 } 156 parsePositiveIntAttribute(String attribute, String integerString)157 /* package */ static int parsePositiveIntAttribute(String attribute, String integerString) { 158 try { 159 return Integer.parseUnsignedInt(integerString); 160 } catch (NumberFormatException | IndexOutOfBoundsException e) { 161 throw new IllegalArgumentException(attribute + " must be a positive integer, but was \"" 162 + integerString + "\" instead.", e); 163 } 164 } 165 parsePositiveLongAttribute(String attribute, String longString)166 static long parsePositiveLongAttribute(String attribute, String longString) { 167 try { 168 return Long.parseUnsignedLong(longString); 169 } catch (NumberFormatException | IndexOutOfBoundsException e) { 170 throw new IllegalArgumentException(attribute + " must be a positive long, but was \"" 171 + longString + "\" instead.", e); 172 } 173 } 174 skip(XmlPullParser parser)175 /* package */ static void skip(XmlPullParser parser) 176 throws XmlPullParserException, IOException { 177 if (parser.getEventType() != XmlPullParser.START_TAG) { 178 throw new IllegalStateException(); 179 } 180 int depth = 1; 181 while (depth != 0) { 182 switch (parser.next()) { 183 case XmlPullParser.END_TAG: 184 depth--; 185 break; 186 case XmlPullParser.START_TAG: 187 depth++; 188 break; 189 default: 190 break; 191 } 192 } 193 } 194 } 195