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 com.android.car.internal.ExcludeFromCodeCoverageGeneratedReport.DUMP_INFO; 19 20 import android.annotation.NonNull; 21 import android.annotation.Nullable; 22 import android.car.oem.CarAudioFadeConfiguration; 23 import android.media.AudioAttributes; 24 import android.media.FadeManagerConfiguration; 25 import android.util.ArrayMap; 26 import android.util.Xml; 27 import android.util.proto.ProtoOutputStream; 28 29 import com.android.car.CarLog; 30 import com.android.car.internal.ExcludeFromCodeCoverageGeneratedReport; 31 import com.android.car.internal.util.IndentingPrintWriter; 32 import com.android.car.internal.util.IntArray; 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.MissingResourceException; 41 import java.util.Objects; 42 import java.util.Set; 43 44 /** 45 * A class to load {@link android.media.FadeManagerConfiguration} from the configuration XML file. 46 */ 47 final class CarAudioFadeConfigurationHelper { 48 private static final String TAG = CarLog.tagFor(CarAudioFadeConfigurationHelper.class); 49 50 private static final String NAMESPACE = null; 51 private static final String TAG_ROOT = "carAudioFadeConfiguration"; 52 private static final String TAG_CONFIGS = "configs"; 53 private static final String TAG_CONFIG = "config"; 54 private static final String CONFIG_NAME = "name"; 55 private static final String CONFIG_DEFAULT_FADE_OUT_DURATION = "defaultFadeOutDurationInMillis"; 56 private static final String CONFIG_DEFAULT_FADE_IN_DURATION = "defaultFadeInDurationInMillis"; 57 private static final String CONFIG_FADE_STATE = "fadeState"; 58 private static final String CONFIG_FADEABLE_USAGES = "fadeableUsages"; 59 private static final String CONFIG_UNFADEABLE_CONTENT_TYPES = "unfadeableContentTypes"; 60 private static final String CONFIG_UNFADEABLE_AUDIO_ATTRIBUTES = "unfadeableAudioAttributes"; 61 private static final String CONFIG_FADE_OUT_CONFIGURATIONS = "fadeOutConfigurations"; 62 private static final String CONFIG_FADE_IN_CONFIGURATIONS = "fadeInConfigurations"; 63 private static final String CONFIG_FADE_CONFIGURATION = "fadeConfiguration"; 64 private static final String FADE_DURATION = "fadeDurationMillis"; 65 private static final String FADE_STATE_VALUE = "value"; 66 private static final String VERSION = "version"; 67 private static final int INVALID = -1; 68 private static final int SUPPORTED_VERSION_1 = 1; 69 70 private static final IntArray SUPPORTED_VERSIONS = IntArray.wrap(new int[] { 71 SUPPORTED_VERSION_1, 72 }); 73 74 private final ArrayMap<String, CarAudioFadeConfiguration> mNameToCarAudioFadeConfigurationMap = 75 new ArrayMap<>(); 76 CarAudioFadeConfigurationHelper(@onNull InputStream stream)77 CarAudioFadeConfigurationHelper(@NonNull InputStream stream) 78 throws XmlPullParserException, IOException { 79 Objects.requireNonNull(stream, "Car audio fade config input stream can not be null"); 80 parseFadeManagerConfigFile(stream); 81 } 82 83 @Nullable getCarAudioFadeConfiguration(String configName)84 public CarAudioFadeConfiguration getCarAudioFadeConfiguration(String configName) { 85 if (!isConfigAvailable(configName)) { 86 return null; 87 } 88 89 return mNameToCarAudioFadeConfigurationMap.get(configName); 90 } 91 isConfigAvailable(String configName)92 public boolean isConfigAvailable(String configName) { 93 return mNameToCarAudioFadeConfigurationMap.containsKey(configName); 94 } 95 96 @NonNull getAllConfigNames()97 public Set<String> getAllConfigNames() { 98 return mNameToCarAudioFadeConfigurationMap.keySet(); 99 } 100 parseFadeManagerConfigFile(InputStream stream)101 private void parseFadeManagerConfigFile(InputStream stream) 102 throws XmlPullParserException, IOException { 103 XmlPullParser parser = Xml.newPullParser(); 104 parser.setFeature(XmlPullParser.FEATURE_PROCESS_NAMESPACES, NAMESPACE != null); 105 parser.setInput(stream, null); 106 107 // Ensure <carAudioFadeConfiguration> is the root 108 parser.nextTag(); 109 parser.require(XmlPullParser.START_TAG, NAMESPACE, TAG_ROOT); 110 111 // Version check 112 int versionNumber = Integer.parseInt(parser.getAttributeValue(NAMESPACE, VERSION)); 113 if (SUPPORTED_VERSIONS.indexOf(versionNumber) == INVALID) { 114 throw new IllegalArgumentException("Latest Supported version:" 115 + SUPPORTED_VERSION_1 + " , got version:" + versionNumber); 116 } 117 118 // Get all fade configs configured under <configs> tag 119 while (parser.next() != XmlPullParser.END_TAG) { 120 if (parser.getEventType() != XmlPullParser.START_TAG) { 121 continue; 122 } 123 if (Objects.equals(parser.getName(), TAG_CONFIGS)) { 124 parseFadeConfigs(parser); 125 } else { 126 CarAudioParserUtils.skip(parser); 127 } 128 } 129 130 if (mNameToCarAudioFadeConfigurationMap.isEmpty()) { 131 throw new MissingResourceException(TAG_CONFIGS + " is missing from configuration ", 132 TAG_ROOT, TAG_CONFIGS); 133 } 134 } 135 parseFadeConfigs(XmlPullParser parser)136 private void parseFadeConfigs(XmlPullParser parser) throws XmlPullParserException, IOException { 137 while (parser.next() != XmlPullParser.END_TAG) { 138 if (parser.getEventType() != XmlPullParser.START_TAG) { 139 continue; 140 } 141 if (Objects.equals(parser.getName(), TAG_CONFIG)) { 142 CarAudioFadeConfiguration afc = parseFadeConfig(parser); 143 mNameToCarAudioFadeConfigurationMap.put(afc.getName(), afc); 144 } else { 145 CarAudioParserUtils.skip(parser); 146 } 147 } 148 } 149 parseFadeConfig(XmlPullParser parser)150 private CarAudioFadeConfiguration parseFadeConfig(XmlPullParser parser) 151 throws XmlPullParserException, IOException { 152 153 String configName = parser.getAttributeValue(NAMESPACE, CONFIG_NAME); 154 Objects.requireNonNull(configName, "Config name can not be null"); 155 Preconditions.checkArgument(!configName.isEmpty(), "Config name can not be empty"); 156 157 String defaultFadeOutDurationLiteral = parser.getAttributeValue(NAMESPACE, 158 CONFIG_DEFAULT_FADE_OUT_DURATION); 159 String defaultFadeInDurationLiteral = parser.getAttributeValue(NAMESPACE, 160 CONFIG_DEFAULT_FADE_IN_DURATION); 161 162 FadeManagerConfiguration.Builder builder; 163 if (defaultFadeOutDurationLiteral != null && defaultFadeInDurationLiteral != null) { 164 builder = new FadeManagerConfiguration.Builder( 165 CarAudioParserUtils.parsePositiveLongAttribute(CONFIG_DEFAULT_FADE_OUT_DURATION, 166 defaultFadeOutDurationLiteral), 167 CarAudioParserUtils.parsePositiveLongAttribute(CONFIG_DEFAULT_FADE_IN_DURATION, 168 defaultFadeInDurationLiteral)); 169 } else { 170 builder = new FadeManagerConfiguration.Builder(); 171 } 172 173 while (parser.next() != XmlPullParser.END_TAG) { 174 if (parser.getEventType() != XmlPullParser.START_TAG) { 175 continue; 176 } 177 if (Objects.equals(parser.getName(), CONFIG_FADE_STATE)) { 178 parseFadeState(parser, builder); 179 } else if (Objects.equals(parser.getName(), CONFIG_FADEABLE_USAGES)) { 180 parseFadeableUsages(parser, builder); 181 } else if (Objects.equals(parser.getName(), CONFIG_UNFADEABLE_CONTENT_TYPES)) { 182 parseUnfadeableContentTypes(parser, builder); 183 } else if (Objects.equals(parser.getName(), CONFIG_UNFADEABLE_AUDIO_ATTRIBUTES)) { 184 parseUnfadeableAudioAttributes(parser, builder, configName); 185 } else if (Objects.equals(parser.getName(), CONFIG_FADE_OUT_CONFIGURATIONS)) { 186 parseFadeOutConfigurations(parser, builder); 187 } else if (Objects.equals(parser.getName(), CONFIG_FADE_IN_CONFIGURATIONS)) { 188 parseFadeInConfigurations(parser, builder); 189 } else { 190 CarAudioParserUtils.skip(parser); 191 } 192 } 193 return new CarAudioFadeConfiguration.Builder(builder.build()).setName(configName).build(); 194 } 195 parseFadeState(XmlPullParser parser, FadeManagerConfiguration.Builder builder)196 private void parseFadeState(XmlPullParser parser, FadeManagerConfiguration.Builder builder) 197 throws XmlPullParserException, IOException { 198 String fadeStateLiteral = parser.getAttributeValue(NAMESPACE, FADE_STATE_VALUE); 199 int fadeState = CarAudioParserUtils.parsePositiveIntAttribute(FADE_STATE_VALUE, 200 fadeStateLiteral); 201 builder.setFadeState(fadeState); 202 CarAudioParserUtils.skip(parser); 203 } 204 parseFadeableUsages(XmlPullParser parser, FadeManagerConfiguration.Builder builder)205 private void parseFadeableUsages(XmlPullParser parser, FadeManagerConfiguration.Builder builder) 206 throws XmlPullParserException, IOException { 207 while (parser.next() != XmlPullParser.END_TAG) { 208 if (parser.getEventType() != XmlPullParser.START_TAG) { 209 continue; 210 } 211 if (Objects.equals(parser.getName(), CarAudioParserUtils.TAG_USAGE)) { 212 int usage = CarAudioParserUtils.parseUsageValue(parser, 213 CarAudioParserUtils.ATTR_USAGE_VALUE); 214 builder.addFadeableUsage(usage); 215 } 216 CarAudioParserUtils.skip(parser); 217 } 218 } 219 parseUnfadeableContentTypes(XmlPullParser parser, FadeManagerConfiguration.Builder builder)220 private void parseUnfadeableContentTypes(XmlPullParser parser, 221 FadeManagerConfiguration.Builder builder) throws XmlPullParserException, IOException { 222 while (parser.next() != XmlPullParser.END_TAG) { 223 if (parser.getEventType() != XmlPullParser.START_TAG) { 224 continue; 225 } 226 if (Objects.equals(parser.getName(), CONFIG_UNFADEABLE_CONTENT_TYPES)) { 227 builder.addUnfadeableContentType(CarAudioParserUtils.parseContentTypeValue(parser)); 228 } 229 CarAudioParserUtils.skip(parser); 230 } 231 } 232 parseUnfadeableAudioAttributes(XmlPullParser parser, FadeManagerConfiguration.Builder builder, String configName)233 private void parseUnfadeableAudioAttributes(XmlPullParser parser, 234 FadeManagerConfiguration.Builder builder, String configName) 235 throws XmlPullParserException, IOException { 236 while (parser.next() != XmlPullParser.END_TAG) { 237 if (parser.getEventType() != XmlPullParser.START_TAG) { 238 continue; 239 } 240 if (Objects.equals(parser.getName(), CarAudioParserUtils.TAG_AUDIO_ATTRIBUTES)) { 241 builder.setUnfadeableAudioAttributes( 242 CarAudioParserUtils.parseAudioAttributes(parser, configName)); 243 } else { 244 CarAudioParserUtils.skip(parser); 245 } 246 } 247 } 248 parseFadeOutConfigurations(XmlPullParser parser, FadeManagerConfiguration.Builder builder)249 private void parseFadeOutConfigurations(XmlPullParser parser, 250 FadeManagerConfiguration.Builder builder) throws XmlPullParserException, IOException { 251 while (parser.next() != XmlPullParser.END_TAG) { 252 if (parser.getEventType() != XmlPullParser.START_TAG) { 253 continue; 254 } 255 if (Objects.equals(parser.getName(), CONFIG_FADE_CONFIGURATION)) { 256 parseFadeConfiguration(parser, builder, /* isFadeIn= */ false); 257 } else { 258 CarAudioParserUtils.skip(parser); 259 } 260 } 261 } 262 parseFadeInConfigurations(XmlPullParser parser, FadeManagerConfiguration.Builder builder)263 private void parseFadeInConfigurations(XmlPullParser parser, 264 FadeManagerConfiguration.Builder builder) throws XmlPullParserException, IOException { 265 while (parser.next() != XmlPullParser.END_TAG) { 266 if (parser.getEventType() != XmlPullParser.START_TAG) { 267 continue; 268 } 269 if (Objects.equals(parser.getName(), CONFIG_FADE_CONFIGURATION)) { 270 parseFadeConfiguration(parser, builder, /* isFadeIn= */ true); 271 } else { 272 CarAudioParserUtils.skip(parser); 273 } 274 } 275 } 276 parseFadeConfiguration(XmlPullParser parser, FadeManagerConfiguration.Builder builder, boolean isFadeIn)277 private void parseFadeConfiguration(XmlPullParser parser, 278 FadeManagerConfiguration.Builder builder, boolean isFadeIn) 279 throws XmlPullParserException, IOException { 280 long fadeDuration = CarAudioParserUtils.parsePositiveLongAttribute( 281 FADE_DURATION, 282 parser.getAttributeValue(NAMESPACE, FADE_DURATION)); 283 while (parser.next() != XmlPullParser.END_TAG) { 284 if (parser.getEventType() != XmlPullParser.START_TAG) { 285 continue; 286 } 287 if (Objects.equals(parser.getName(), CarAudioParserUtils.TAG_AUDIO_ATTRIBUTES)) { 288 parseAudioAttributesForFade(parser, builder, fadeDuration, isFadeIn); 289 } else { 290 CarAudioParserUtils.skip(parser); 291 } 292 } 293 } 294 parseAudioAttributesForFade(XmlPullParser parser, FadeManagerConfiguration.Builder builder, long fadeDuration, boolean isFadeIn)295 private void parseAudioAttributesForFade(XmlPullParser parser, 296 FadeManagerConfiguration.Builder builder, long fadeDuration, boolean isFadeIn) 297 throws XmlPullParserException, IOException { 298 while (parser.next() != XmlPullParser.END_TAG) { 299 if (parser.getEventType() != XmlPullParser.START_TAG) { 300 continue; 301 } 302 if (Objects.equals(parser.getName(), CarAudioParserUtils.TAG_USAGE)) { 303 parseFadeDurationForUsage(parser, builder, fadeDuration, isFadeIn); 304 } else if (Objects.equals(parser.getName(), CarAudioParserUtils.TAG_AUDIO_ATTRIBUTE)) { 305 parseFadeDurationForAudioAttributes(parser, builder, fadeDuration, isFadeIn); 306 } 307 CarAudioParserUtils.skip(parser); 308 } 309 } 310 parseFadeDurationForUsage(XmlPullParser parser, FadeManagerConfiguration.Builder builder, long fadeDuration, boolean isFadeIn)311 private void parseFadeDurationForUsage(XmlPullParser parser, 312 FadeManagerConfiguration.Builder builder, long fadeDuration, boolean isFadeIn) 313 throws XmlPullParserException, IOException { 314 int usage = CarAudioParserUtils.parseUsageValue(parser, 315 CarAudioParserUtils.ATTR_USAGE_VALUE); 316 if (isFadeIn) { 317 builder.setFadeInDurationForUsage(usage, fadeDuration); 318 } else { 319 builder.setFadeOutDurationForUsage(usage, fadeDuration); 320 } 321 } 322 parseFadeDurationForAudioAttributes(XmlPullParser parser, FadeManagerConfiguration.Builder builder, long fadeDuration, boolean isFadeIn)323 private void parseFadeDurationForAudioAttributes(XmlPullParser parser, 324 FadeManagerConfiguration.Builder builder, long fadeDuration, boolean isFadeIn) 325 throws XmlPullParserException, IOException { 326 AudioAttributes attr = CarAudioParserUtils.parseAudioAttribute(parser, 327 CONFIG_FADE_CONFIGURATION); 328 if (isFadeIn) { 329 builder.setFadeInDurationForAudioAttributes(attr, fadeDuration); 330 } else { 331 builder.setFadeOutDurationForAudioAttributes(attr, fadeDuration); 332 } 333 } 334 335 @ExcludeFromCodeCoverageGeneratedReport(reason = DUMP_INFO) dump(IndentingPrintWriter writer)336 void dump(IndentingPrintWriter writer) { 337 writer.increaseIndent(); 338 writer.printf("Available fade manager configurations: %d\n", 339 mNameToCarAudioFadeConfigurationMap.size()); 340 for (int index = 0; index < mNameToCarAudioFadeConfigurationMap.size(); index++) { 341 writer.printf((index + 1) + ". " + mNameToCarAudioFadeConfigurationMap.valueAt(index) 342 + "\n"); 343 } 344 writer.decreaseIndent(); 345 } 346 347 @ExcludeFromCodeCoverageGeneratedReport(reason = DUMP_INFO) dumpProto(ProtoOutputStream proto)348 void dumpProto(ProtoOutputStream proto) { 349 if (mNameToCarAudioFadeConfigurationMap == null) { 350 return; 351 } 352 353 for (int index = 0; index < mNameToCarAudioFadeConfigurationMap.size(); index++) { 354 CarAudioProtoUtils.dumpCarAudioFadeConfigurationProto( 355 mNameToCarAudioFadeConfigurationMap.valueAt(index), 356 CarAudioDumpProto.AVAILABLE_CAR_FADE_CONFIGURATIONS, proto); 357 } 358 } 359 } 360