1 /* 2 * Copyright (C) 2020 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 17 package com.android.car.audio; 18 19 import static com.android.car.internal.ExcludeFromCodeCoverageGeneratedReport.DUMP_INFO; 20 21 import android.annotation.IntDef; 22 import android.annotation.Nullable; 23 import android.car.builtin.media.AudioManagerHelper; 24 import android.car.builtin.util.Slogf; 25 import android.media.AudioAttributes; 26 import android.util.ArrayMap; 27 import android.util.ArraySet; 28 import android.util.SparseArray; 29 30 import com.android.car.CarLog; 31 import com.android.car.internal.ExcludeFromCodeCoverageGeneratedReport; 32 import com.android.car.internal.annotation.AttributeUsage; 33 import com.android.car.internal.util.IndentingPrintWriter; 34 import com.android.internal.annotations.VisibleForTesting; 35 import com.android.internal.util.Preconditions; 36 37 import java.lang.annotation.Retention; 38 import java.lang.annotation.RetentionPolicy; 39 import java.util.ArrayList; 40 import java.util.Collections; 41 import java.util.List; 42 import java.util.Map; 43 import java.util.Objects; 44 import java.util.Set; 45 46 /** 47 * Groupings of {@link AttributeUsage}s to simplify configuration of car audio routing, volume 48 * groups, and focus interactions for similar usages. 49 */ 50 public final class CarAudioContext { 51 52 private static final String TAG = CarLog.tagFor(CarAudioContext.class); 53 54 /* 55 * Shouldn't be used 56 * ::android::hardware::automotive::audiocontrol::V1_0::ContextNumber.INVALID 57 */ 58 private static final int INVALID = 0; 59 /* 60 * Music playback 61 * ::android::hardware::automotive::audiocontrol::V1_0::ContextNumber.INVALID implicitly + 1 62 */ 63 static final int MUSIC = 1; 64 /* 65 * Navigation directions 66 * ::android::hardware::automotive::audiocontrol::V1_0::ContextNumber.MUSIC implicitly + 1 67 */ 68 static final int NAVIGATION = 2; 69 /* 70 * Voice command session 71 * ::android::hardware::automotive::audiocontrol::V1_0::ContextNumber.NAVIGATION implicitly + 1 72 */ 73 static final int VOICE_COMMAND = 3; 74 /* 75 * Voice call ringing 76 * ::android::hardware::automotive::audiocontrol::V1_0::ContextNumber 77 * .VOICE_COMMAND implicitly + 1 78 */ 79 static final int CALL_RING = 4; 80 /* 81 * Voice call 82 * ::android::hardware::automotive::audiocontrol::V1_0::ContextNumber.CALL_RING implicitly + 1 83 */ 84 static final int CALL = 5; 85 /* 86 * Alarm sound from Android 87 * ::android::hardware::automotive::audiocontrol::V1_0::ContextNumber.CALL implicitly + 1 88 */ 89 static final int ALARM = 6; 90 /* 91 * Notifications 92 * ::android::hardware::automotive::audiocontrol::V1_0::ContextNumber.ALARM implicitly + 1 93 */ 94 static final int NOTIFICATION = 7; 95 /* 96 * System sounds 97 * ::android::hardware::automotive::audiocontrol::V1_0::ContextNumber 98 * .NOTIFICATION implicitly + 1 99 */ 100 static final int SYSTEM_SOUND = 8; 101 /* 102 * Emergency related sounds such as collision warnings 103 */ 104 static final int EMERGENCY = 9; 105 /* 106 * Safety sounds such as obstacle detection when backing up or when changing lanes 107 */ 108 static final int SAFETY = 10; 109 /* 110 * Vehicle Status related sounds such as check engine light or seat belt chimes 111 */ 112 static final int VEHICLE_STATUS = 11; 113 /* 114 * Announcement such as traffic announcements 115 */ 116 static final int ANNOUNCEMENT = 12; 117 118 @IntDef({ 119 INVALID, 120 MUSIC, 121 NAVIGATION, 122 VOICE_COMMAND, 123 CALL_RING, 124 CALL, 125 ALARM, 126 NOTIFICATION, 127 SYSTEM_SOUND, 128 EMERGENCY, 129 SAFETY, 130 VEHICLE_STATUS, 131 ANNOUNCEMENT 132 }) 133 @Retention(RetentionPolicy.SOURCE) 134 public @interface AudioContext { 135 } 136 137 private static final List<Integer> CONTEXTS = List.of( 138 // The items are in a sorted order 139 // Starting at one 140 MUSIC, 141 NAVIGATION, 142 VOICE_COMMAND, 143 CALL_RING, 144 CALL, 145 ALARM, 146 NOTIFICATION, 147 SYSTEM_SOUND, 148 EMERGENCY, 149 SAFETY, 150 VEHICLE_STATUS, 151 ANNOUNCEMENT 152 ); 153 154 // Contexts related to non-car audio system, this covers the general use case of context 155 // that would exist in the phone. 156 private static final List<Integer> NON_CAR_SYSTEM_CONTEXTS = List.of( 157 MUSIC, 158 NAVIGATION, 159 VOICE_COMMAND, 160 CALL_RING, 161 CALL, 162 ALARM, 163 NOTIFICATION, 164 SYSTEM_SOUND 165 ); 166 167 // Contexts related to car audio system, this covers the general use case of context 168 // that are generally related to car system. 169 private static final List<Integer> CAR_SYSTEM_CONTEXTS = List.of( 170 EMERGENCY, 171 SAFETY, 172 VEHICLE_STATUS, 173 ANNOUNCEMENT 174 ); 175 176 private static final AudioAttributes[] SYSTEM_ATTRIBUTES = new AudioAttributes[] { 177 getAudioAttributeFromUsage(AudioAttributes.USAGE_CALL_ASSISTANT), 178 getAudioAttributeFromUsage(AudioAttributes.USAGE_EMERGENCY), 179 getAudioAttributeFromUsage(AudioAttributes.USAGE_SAFETY), 180 getAudioAttributeFromUsage(AudioAttributes.USAGE_VEHICLE_STATUS), 181 getAudioAttributeFromUsage(AudioAttributes.USAGE_ANNOUNCEMENT) 182 }; 183 184 private static final CarAudioContextInfo CAR_CONTEXT_INFO_MUSIC = 185 new CarAudioContextInfo(new AudioAttributes[] { 186 getAudioAttributeFromUsage(AudioAttributes.USAGE_UNKNOWN), 187 getAudioAttributeFromUsage(AudioAttributes.USAGE_GAME), 188 getAudioAttributeFromUsage(AudioAttributes.USAGE_MEDIA) 189 }, "MUSIC", MUSIC); 190 191 private static final CarAudioContextInfo CAR_CONTEXT_INFO_NAVIGATION = 192 new CarAudioContextInfo(new AudioAttributes[] { 193 getAudioAttributeFromUsage(AudioAttributes 194 .USAGE_ASSISTANCE_NAVIGATION_GUIDANCE) 195 }, "NAVIGATION", NAVIGATION); 196 197 private static final CarAudioContextInfo CAR_CONTEXT_INFO_VOICE_COMMAND = 198 new CarAudioContextInfo(new AudioAttributes[] { 199 getAudioAttributeFromUsage(AudioAttributes.USAGE_ASSISTANCE_ACCESSIBILITY), 200 getAudioAttributeFromUsage(AudioAttributes.USAGE_ASSISTANT) 201 }, "VOICE_COMMAND", VOICE_COMMAND); 202 203 private static final CarAudioContextInfo CAR_CONTEXT_INFO_CALL_RING = 204 new CarAudioContextInfo(new AudioAttributes[] { 205 getAudioAttributeFromUsage(AudioAttributes.USAGE_NOTIFICATION_RINGTONE) 206 }, "CALL_RING", CALL_RING); 207 208 private static final CarAudioContextInfo CAR_CONTEXT_INFO_CALL = 209 new CarAudioContextInfo(new AudioAttributes[] { 210 getAudioAttributeFromUsage(AudioAttributes.USAGE_VOICE_COMMUNICATION), 211 getAudioAttributeFromUsage(AudioAttributes.USAGE_CALL_ASSISTANT), 212 getAudioAttributeFromUsage(AudioAttributes 213 .USAGE_VOICE_COMMUNICATION_SIGNALLING), 214 }, "CALL", CALL); 215 216 private static final CarAudioContextInfo CAR_CONTEXT_INFO_ALARM = 217 new CarAudioContextInfo(new AudioAttributes[]{ 218 getAudioAttributeFromUsage(AudioAttributes.USAGE_ALARM) 219 }, "ALARM", ALARM); 220 221 private static final CarAudioContextInfo CAR_CONTEXT_INFO_NOTIFICATION = 222 new CarAudioContextInfo(new AudioAttributes[]{ 223 getAudioAttributeFromUsage(AudioAttributes.USAGE_NOTIFICATION), 224 getAudioAttributeFromUsage(AudioAttributes.USAGE_NOTIFICATION_EVENT) 225 }, "NOTIFICATION", NOTIFICATION); 226 227 private static final CarAudioContextInfo CAR_CONTEXT_INFO_SYSTEM_SOUND = 228 new CarAudioContextInfo(new AudioAttributes[]{ 229 getAudioAttributeFromUsage(AudioAttributes.USAGE_ASSISTANCE_SONIFICATION) 230 }, "SYSTEM_SOUND", SYSTEM_SOUND); 231 232 private static final CarAudioContextInfo CAR_CONTEXT_INFO_EMERGENCY = 233 new CarAudioContextInfo(new AudioAttributes[]{ 234 getAudioAttributeFromUsage(AudioAttributes.USAGE_EMERGENCY) 235 }, "EMERGENCY", EMERGENCY); 236 237 private static final CarAudioContextInfo CAR_CONTEXT_INFO_SAFETY = 238 new CarAudioContextInfo(new AudioAttributes[]{ 239 getAudioAttributeFromUsage(AudioAttributes.USAGE_SAFETY) 240 }, "SAFETY", SAFETY); 241 242 private static final CarAudioContextInfo CAR_CONTEXT_INFO_VEHICLE_STATUS = 243 new CarAudioContextInfo(new AudioAttributes[]{ 244 getAudioAttributeFromUsage(AudioAttributes.USAGE_VEHICLE_STATUS) 245 }, "VEHICLE_STATUS", VEHICLE_STATUS); 246 247 private static final CarAudioContextInfo CAR_CONTEXT_INFO_ANNOUNCEMENT = 248 new CarAudioContextInfo(new AudioAttributes[]{ 249 getAudioAttributeFromUsage(AudioAttributes.USAGE_ANNOUNCEMENT) 250 }, "ANNOUNCEMENT", ANNOUNCEMENT); 251 252 private static final CarAudioContextInfo CAR_CONTEXT_INFO_INVALID = 253 new CarAudioContextInfo(new AudioAttributes[]{ 254 getAudioAttributeFromUsage(AudioManagerHelper.getUsageVirtualSource()) 255 }, "INVALID", INVALID); 256 257 private static final List<CarAudioContextInfo> CAR_CONTEXT_INFO = List.of( 258 CAR_CONTEXT_INFO_MUSIC, 259 CAR_CONTEXT_INFO_NAVIGATION, 260 CAR_CONTEXT_INFO_VOICE_COMMAND, 261 CAR_CONTEXT_INFO_CALL_RING, 262 CAR_CONTEXT_INFO_CALL, 263 CAR_CONTEXT_INFO_ALARM, 264 CAR_CONTEXT_INFO_NOTIFICATION, 265 CAR_CONTEXT_INFO_SYSTEM_SOUND, 266 CAR_CONTEXT_INFO_EMERGENCY, 267 CAR_CONTEXT_INFO_SAFETY, 268 CAR_CONTEXT_INFO_VEHICLE_STATUS, 269 CAR_CONTEXT_INFO_ANNOUNCEMENT, 270 CAR_CONTEXT_INFO_INVALID 271 ); 272 273 @VisibleForTesting 274 static final SparseArray<List<Integer>> sContextsToDuck = 275 new SparseArray<>(/* initialCapacity= */ 13); 276 277 static { 278 // INVALID ducks nothing sContextsToDuck.append(INVALID, Collections.emptyList())279 sContextsToDuck.append(INVALID, Collections.emptyList()); 280 // MUSIC ducks nothing sContextsToDuck.append(MUSIC, Collections.emptyList())281 sContextsToDuck.append(MUSIC, Collections.emptyList()); sContextsToDuck.append(NAVIGATION, List.of( MUSIC, CALL_RING, CALL, ALARM, NOTIFICATION, SYSTEM_SOUND, VEHICLE_STATUS, ANNOUNCEMENT ))282 sContextsToDuck.append(NAVIGATION, List.of( 283 MUSIC, 284 CALL_RING, 285 CALL, 286 ALARM, 287 NOTIFICATION, 288 SYSTEM_SOUND, 289 VEHICLE_STATUS, 290 ANNOUNCEMENT 291 )); sContextsToDuck.append(VOICE_COMMAND, List.of( CALL_RING ))292 sContextsToDuck.append(VOICE_COMMAND, List.of( 293 CALL_RING 294 )); sContextsToDuck.append(CALL_RING, Collections.emptyList())295 sContextsToDuck.append(CALL_RING, Collections.emptyList()); sContextsToDuck.append(CALL, List.of( CALL_RING, ALARM, NOTIFICATION, VEHICLE_STATUS ))296 sContextsToDuck.append(CALL, List.of( 297 CALL_RING, 298 ALARM, 299 NOTIFICATION, 300 VEHICLE_STATUS 301 )); sContextsToDuck.append(ALARM, List.of( MUSIC ))302 sContextsToDuck.append(ALARM, List.of( 303 MUSIC 304 )); sContextsToDuck.append(NOTIFICATION, List.of( MUSIC, ALARM, ANNOUNCEMENT ))305 sContextsToDuck.append(NOTIFICATION, List.of( 306 MUSIC, 307 ALARM, 308 ANNOUNCEMENT 309 )); sContextsToDuck.append(SYSTEM_SOUND, List.of( MUSIC, ALARM, ANNOUNCEMENT ))310 sContextsToDuck.append(SYSTEM_SOUND, List.of( 311 MUSIC, 312 ALARM, 313 ANNOUNCEMENT 314 )); sContextsToDuck.append(EMERGENCY, List.of( CALL ))315 sContextsToDuck.append(EMERGENCY, List.of( 316 CALL 317 )); sContextsToDuck.append(SAFETY, List.of( MUSIC, NAVIGATION, VOICE_COMMAND, CALL_RING, CALL, ALARM, NOTIFICATION, SYSTEM_SOUND, VEHICLE_STATUS, ANNOUNCEMENT ))318 sContextsToDuck.append(SAFETY, List.of( 319 MUSIC, 320 NAVIGATION, 321 VOICE_COMMAND, 322 CALL_RING, 323 CALL, 324 ALARM, 325 NOTIFICATION, 326 SYSTEM_SOUND, 327 VEHICLE_STATUS, 328 ANNOUNCEMENT 329 )); sContextsToDuck.append(VEHICLE_STATUS, List.of( MUSIC, CALL_RING, ANNOUNCEMENT ))330 sContextsToDuck.append(VEHICLE_STATUS, List.of( 331 MUSIC, 332 CALL_RING, 333 ANNOUNCEMENT 334 )); 335 // ANNOUNCEMENT ducks nothing sContextsToDuck.append(ANNOUNCEMENT, Collections.emptyList())336 sContextsToDuck.append(ANNOUNCEMENT, Collections.emptyList()); 337 } 338 getSystemUsages()339 static int[] getSystemUsages() { 340 return convertAttributesToUsage(SYSTEM_ATTRIBUTES); 341 } 342 343 private static final SparseArray<String> CONTEXT_NAMES = new SparseArray<>(CONTEXTS.size() + 1); 344 private static final SparseArray<AudioAttributes[]> CONTEXT_TO_ATTRIBUTES = new SparseArray<>(); 345 private static final Map<AudioAttributesWrapper, Integer> AUDIO_ATTRIBUTE_TO_CONTEXT = 346 new ArrayMap<>(); 347 private static final List<AudioAttributesWrapper> ALL_SUPPORTED_ATTRIBUTES = new ArrayList<>(); 348 private final SparseArray<List<Integer>> mContextsToDuck; 349 350 static { 351 for (int index = 0; index < CAR_CONTEXT_INFO.size(); index++) { 352 CarAudioContextInfo info = CAR_CONTEXT_INFO.get(index); info.getName()353 CONTEXT_NAMES.append(info.getId(), info.getName()); info.getAudioAttributes()354 CONTEXT_TO_ATTRIBUTES.put(info.getId(), info.getAudioAttributes()); 355 356 AudioAttributes[] attributes = info.getAudioAttributes(); 357 for (int attributeIndex = 0; attributeIndex < attributes.length; attributeIndex++) { 358 AudioAttributesWrapper attributesWrapper = 359 new AudioAttributesWrapper(attributes[attributeIndex]); 360 if (AUDIO_ATTRIBUTE_TO_CONTEXT.containsKey(attributesWrapper)) { 361 int mappedContext = AUDIO_ATTRIBUTE_TO_CONTEXT.get(attributesWrapper); Slogf.wtf(TAG, "%s already mapped to context %s, can not remap to context %s", attributesWrapper, mappedContext, info.getId())362 Slogf.wtf(TAG, "%s already mapped to context %s, can not remap to context %s", 363 attributesWrapper, mappedContext, info.getId()); 364 } AUDIO_ATTRIBUTE_TO_CONTEXT.put(attributesWrapper, info.getId())365 AUDIO_ATTRIBUTE_TO_CONTEXT.put(attributesWrapper, info.getId()); 366 if (isInvalidContextId(info.getId())) { 367 continue; 368 } 369 ALL_SUPPORTED_ATTRIBUTES.add(attributesWrapper); 370 } 371 } 372 } 373 374 private final boolean mUseCoreAudioRouting; 375 private final List<CarAudioContextInfo> mCarAudioContextInfos; 376 private final Map<AudioAttributesWrapper, Integer> mAudioAttributesToContext = 377 new ArrayMap<>(); 378 private final SparseArray<String> mContextToNames = new SparseArray<>(); 379 private final SparseArray<AudioAttributes[]> mContextToAttributes = new SparseArray<>(); 380 /** 381 * Oem Extension CarAudioContext cannot be addressed by usage only 382 */ 383 private final List<Integer> mOemExtensionContexts = new ArrayList<>(); 384 385 /** 386 * Creates a car audio context which contains the logical grouping of 387 * audio attributes into contexts 388 * 389 * @param carAudioContexts list of audio attributes grouping 390 * @param useCoreAudioRouting if set, indicate contexts are mapped on core 391 * {@link android.media.audiopolicy.AudioProductStrategy} . 392 */ CarAudioContext(List<CarAudioContextInfo> carAudioContexts, boolean useCoreAudioRouting)393 public CarAudioContext(List<CarAudioContextInfo> carAudioContexts, 394 boolean useCoreAudioRouting) { 395 Objects.requireNonNull(carAudioContexts, 396 "Car audio contexts must not be null"); 397 Preconditions.checkArgument(!carAudioContexts.isEmpty(), 398 "Car audio contexts must not be empty"); 399 mCarAudioContextInfos = carAudioContexts; 400 mUseCoreAudioRouting = useCoreAudioRouting; 401 if (!mUseCoreAudioRouting) { 402 mContextsToDuck = sContextsToDuck.clone(); 403 } else { 404 mContextsToDuck = new SparseArray<>(carAudioContexts.size()); 405 } 406 for (int index = 0; index < carAudioContexts.size(); index++) { 407 CarAudioContextInfo info = carAudioContexts.get(index); 408 int contextId = info.getId(); 409 mContextToNames.put(info.getId(), info.getName()); 410 mContextToAttributes.put(info.getId(), info.getAudioAttributes()); 411 if (mUseCoreAudioRouting) { 412 mContextsToDuck.put(contextId, Collections.emptyList()); 413 int[] sdkUsages = convertAttributesToUsage(info.getAudioAttributes()); 414 boolean isOemExtension = false; 415 // At least one of the attributes prevents this context from being addressed only 416 // by usage, so we will not be able to use DynamicPolicyMixes. 417 for (int indexUsage = 0; indexUsage < sdkUsages.length; indexUsage++) { 418 int usage = sdkUsages[indexUsage]; 419 AudioAttributes attributes = getAudioAttributeFromUsage(usage); 420 if (CoreAudioHelper.getStrategyForAudioAttributes(attributes) != contextId) { 421 isOemExtension = true; 422 break; 423 } 424 } 425 if (isOemExtension) { 426 mOemExtensionContexts.add(info.getId()); 427 } 428 // Bypass initialization of Map attribute to context as relying on strategy rules 429 continue; 430 } 431 AudioAttributes[] attributes = info.getAudioAttributes(); 432 for (int attributeIndex = 0; attributeIndex < attributes.length; attributeIndex++) { 433 AudioAttributesWrapper attributesWrapper = 434 new AudioAttributesWrapper(attributes[attributeIndex]); 435 if (mAudioAttributesToContext.containsKey(attributesWrapper)) { 436 int mappedContext = mAudioAttributesToContext.get(attributesWrapper); 437 Slogf.wtf(TAG, "%s already mapped to context %s, can not remap to context %s", 438 attributesWrapper, mappedContext, info.getId()); 439 } 440 if (isInvalidContextId(info.getId())) { 441 continue; 442 } 443 mAudioAttributesToContext.put(attributesWrapper, info.getId()); 444 } 445 } 446 } 447 getLegacyContextFromInfo(CarAudioContextInfo carAudioContextInfo)448 static @AudioContext int getLegacyContextFromInfo(CarAudioContextInfo carAudioContextInfo) { 449 AudioAttributes[] attributes = carAudioContextInfo.getAudioAttributes(); 450 for (int index = 0; index < attributes.length; index++) { 451 AudioAttributesWrapper wrapper = 452 new AudioAttributesWrapper(attributes[index]); 453 int context = AUDIO_ATTRIBUTE_TO_CONTEXT.getOrDefault(wrapper, INVALID); 454 if (isInvalidContextId(context)) { 455 continue; 456 } 457 return context; 458 } 459 return INVALID; 460 } 461 evaluateAudioAttributesToDuck( List<AudioAttributes> activePlaybackAttributes)462 static List<AudioAttributes> evaluateAudioAttributesToDuck( 463 List<AudioAttributes> activePlaybackAttributes) { 464 ArraySet<AudioAttributesWrapper> attributesToDuck = new ArraySet<>(); 465 List<AudioAttributesWrapper> wrappers = new ArrayList<>(activePlaybackAttributes.size()); 466 for (int index = 0; index < activePlaybackAttributes.size(); index++) { 467 AudioAttributesWrapper wrapper = 468 new AudioAttributesWrapper(activePlaybackAttributes.get(index)); 469 wrappers.add(wrapper); 470 int context = AUDIO_ATTRIBUTE_TO_CONTEXT.getOrDefault(wrapper, INVALID); 471 if (isInvalidContextId(context)) { 472 continue; 473 } 474 List<Integer> contextsToDuck = sContextsToDuck.get(context); 475 for (int contextIndex = 0; contextIndex < contextsToDuck.size(); contextIndex++) { 476 AudioAttributes[] duckedAttributes = 477 CONTEXT_TO_ATTRIBUTES.get(contextsToDuck.get(contextIndex)); 478 for (int i = 0; i < duckedAttributes.length; i++) { 479 attributesToDuck.add(new AudioAttributesWrapper(duckedAttributes[i])); 480 } 481 } 482 } 483 attributesToDuck.retainAll(wrappers); 484 485 List<AudioAttributes> duckedAudioAttributes = new ArrayList<>(attributesToDuck.size()); 486 for (int index = 0; index < attributesToDuck.size(); index++) { 487 duckedAudioAttributes.add(attributesToDuck.valueAt(index).getAudioAttributes()); 488 } 489 490 return duckedAudioAttributes; 491 } 492 useCoreAudioRouting()493 boolean useCoreAudioRouting() { 494 return mUseCoreAudioRouting; 495 } 496 isOemExtensionAudioContext(@udioContext int audioContext)497 boolean isOemExtensionAudioContext(@AudioContext int audioContext) { 498 return mOemExtensionContexts.contains(audioContext); 499 } 500 501 /** 502 * Checks if the audio attribute usage is valid, throws an {@link IllegalArgumentException} 503 * if the {@code usage} is not valid. 504 * 505 * @param usage audio attribute usage to check 506 * @throws IllegalArgumentException in case of invalid audio attribute usage 507 */ checkAudioAttributeUsage(@ttributeUsage int usage)508 public static void checkAudioAttributeUsage(@AttributeUsage int usage) 509 throws IllegalArgumentException { 510 if (isValidAudioAttributeUsage(usage)) { 511 return; 512 } 513 514 throw new IllegalArgumentException("Invalid audio attribute " + usage); 515 } 516 517 /** 518 * Determines if the audio attribute usage is valid 519 * 520 * @param usage audio attribute usage to check 521 * @return {@code true} if valid, {@code false} otherwise 522 */ isValidAudioAttributeUsage(@ttributeUsage int usage)523 public static boolean isValidAudioAttributeUsage(@AttributeUsage int usage) { 524 switch (usage) { 525 case AudioAttributes.USAGE_UNKNOWN: 526 case AudioAttributes.USAGE_MEDIA: 527 case AudioAttributes.USAGE_VOICE_COMMUNICATION: 528 case AudioAttributes.USAGE_VOICE_COMMUNICATION_SIGNALLING: 529 case AudioAttributes.USAGE_ALARM: 530 case AudioAttributes.USAGE_NOTIFICATION: 531 case AudioAttributes.USAGE_NOTIFICATION_RINGTONE: 532 case AudioAttributes.USAGE_NOTIFICATION_COMMUNICATION_REQUEST: 533 case AudioAttributes.USAGE_NOTIFICATION_COMMUNICATION_INSTANT: 534 case AudioAttributes.USAGE_NOTIFICATION_COMMUNICATION_DELAYED: 535 case AudioAttributes.USAGE_NOTIFICATION_EVENT: 536 case AudioAttributes.USAGE_ASSISTANCE_ACCESSIBILITY: 537 case AudioAttributes.USAGE_ASSISTANCE_NAVIGATION_GUIDANCE: 538 case AudioAttributes.USAGE_ASSISTANCE_SONIFICATION: 539 case AudioAttributes.USAGE_GAME: 540 case AudioAttributes.USAGE_ASSISTANT: 541 case AudioAttributes.USAGE_CALL_ASSISTANT: 542 case AudioAttributes.USAGE_EMERGENCY: 543 case AudioAttributes.USAGE_SAFETY: 544 case AudioAttributes.USAGE_VEHICLE_STATUS: 545 case AudioAttributes.USAGE_ANNOUNCEMENT: 546 return true; 547 default: 548 // Virtual usage is hidden and thus it must be taken care here. 549 return usage == AudioManagerHelper.getUsageVirtualSource(); 550 } 551 } 552 553 /** 554 * Checks if the audio context is within the valid range from MUSIC to SYSTEM_SOUND 555 */ preconditionCheckAudioContext(@udioContext int audioContext)556 void preconditionCheckAudioContext(@AudioContext int audioContext) { 557 558 Preconditions.checkArgument(!isInvalidContextId(audioContext) 559 && mContextToAttributes.indexOfKey(audioContext) >= 0, 560 "Car audio context %d is invalid", audioContext); 561 } 562 getAudioAttributesForContext(@udioContext int carAudioContext)563 AudioAttributes[] getAudioAttributesForContext(@AudioContext int carAudioContext) { 564 preconditionCheckAudioContext(carAudioContext); 565 return mContextToAttributes.get(carAudioContext); 566 } 567 getContextForAttributes(AudioAttributes attributes)568 @AudioContext int getContextForAttributes(AudioAttributes attributes) { 569 return getContextForAudioAttribute(attributes); 570 } 571 572 /** 573 * @return Context number for a given audio usage, {@code INVALID} if the given usage is 574 * unrecognized. 575 */ getContextForAudioAttribute(AudioAttributes attributes)576 public @AudioContext int getContextForAudioAttribute(AudioAttributes attributes) { 577 if (mUseCoreAudioRouting) { 578 int strategyId = CoreAudioHelper.getStrategyForAudioAttributes(attributes); 579 if ((strategyId != CoreAudioHelper.INVALID_STRATEGY) 580 && (mContextToNames.indexOfKey(strategyId) >= 0)) { 581 return strategyId; 582 } 583 return INVALID; 584 } 585 return mAudioAttributesToContext.getOrDefault( 586 new AudioAttributesWrapper(attributes), INVALID); 587 } 588 589 /** 590 * Returns an audio attribute for a given usage 591 * @param usage input usage, can be an audio attribute system usage 592 */ getAudioAttributeFromUsage(@ttributeUsage int usage)593 public static AudioAttributes getAudioAttributeFromUsage(@AttributeUsage int usage) { 594 AudioAttributes.Builder builder = new AudioAttributes.Builder(); 595 if (AudioAttributes.isSystemUsage(usage)) { 596 builder.setSystemUsage(usage); 597 } else { 598 builder.setUsage(usage); 599 } 600 return builder.build(); 601 } 602 603 /** 604 * Returns an audio attribute wrapper for a given usage 605 * @param usage input usage, can be an audio attribute system usage 606 */ getAudioAttributeWrapperFromUsage( @ttributeUsage int usage)607 public static AudioAttributesWrapper getAudioAttributeWrapperFromUsage( 608 @AttributeUsage int usage) { 609 return new AudioAttributesWrapper(getAudioAttributeFromUsage(usage)); 610 } 611 getUniqueContextsForAudioAttributes(List<AudioAttributes> audioAttributes)612 Set<Integer> getUniqueContextsForAudioAttributes(List<AudioAttributes> audioAttributes) { 613 Objects.requireNonNull(audioAttributes, "Audio attributes can not be null"); 614 Set<Integer> uniqueContexts = new ArraySet<>(); 615 for (int index = 0; index < audioAttributes.size(); index++) { 616 uniqueContexts.add(getContextForAudioAttribute(audioAttributes.get(index))); 617 } 618 619 uniqueContexts.remove(INVALID); 620 return uniqueContexts; 621 } 622 623 @ExcludeFromCodeCoverageGeneratedReport(reason = DUMP_INFO) dump(IndentingPrintWriter writer)624 void dump(IndentingPrintWriter writer) { 625 writer.println("CarAudioContext"); 626 writer.increaseIndent(); 627 for (int index = 0; index < mCarAudioContextInfos.size(); index++) { 628 mCarAudioContextInfos.get(index).dump(writer); 629 } 630 writer.decreaseIndent(); 631 } 632 isNotificationAudioAttribute(AudioAttributes attributes)633 static boolean isNotificationAudioAttribute(AudioAttributes attributes) { 634 AudioAttributesWrapper wrapper = new AudioAttributesWrapper(attributes); 635 return getAudioAttributeWrapperFromUsage(AudioAttributes.USAGE_NOTIFICATION).equals(wrapper) 636 || getAudioAttributeWrapperFromUsage(AudioAttributes.USAGE_NOTIFICATION_EVENT) 637 .equals(wrapper); 638 } 639 isCriticalAudioAudioAttribute(AudioAttributes attributes)640 static boolean isCriticalAudioAudioAttribute(AudioAttributes attributes) { 641 AudioAttributesWrapper wrapper = new AudioAttributesWrapper(attributes); 642 return getAudioAttributeWrapperFromUsage(AudioAttributes.USAGE_EMERGENCY).equals(wrapper) 643 || getAudioAttributeWrapperFromUsage(AudioAttributes.USAGE_SAFETY).equals(wrapper); 644 } 645 isRingerOrCallAudioAttribute(AudioAttributes attributes)646 static boolean isRingerOrCallAudioAttribute(AudioAttributes attributes) { 647 AudioAttributesWrapper wrapper = new AudioAttributesWrapper(attributes); 648 return getAudioAttributeWrapperFromUsage(AudioAttributes.USAGE_NOTIFICATION_RINGTONE) 649 .equals(wrapper) 650 || getAudioAttributeWrapperFromUsage(AudioAttributes.USAGE_VOICE_COMMUNICATION) 651 .equals(wrapper) 652 || getAudioAttributeWrapperFromUsage(AudioAttributes.USAGE_CALL_ASSISTANT) 653 .equals(wrapper) 654 || getAudioAttributeWrapperFromUsage( 655 AudioAttributes.USAGE_VOICE_COMMUNICATION_SIGNALLING) 656 .equals(wrapper); 657 658 } 659 toString(@udioContext int audioContext)660 String toString(@AudioContext int audioContext) { 661 String name = mContextToNames.get(audioContext); 662 if (name != null) { 663 return name; 664 } 665 return "Unsupported Context 0x" + Integer.toHexString(audioContext); 666 } 667 getUniqueAttributesHoldingFocus( List<AudioAttributes> audioAttributes)668 static List<AudioAttributes> getUniqueAttributesHoldingFocus( 669 List<AudioAttributes> audioAttributes) { 670 Set<AudioAttributesWrapper> uniqueAudioAttributes = new ArraySet<>(); 671 List<AudioAttributes> uniqueAttributes = new ArrayList<>(uniqueAudioAttributes.size()); 672 for (int index = 0; index < audioAttributes.size(); index++) { 673 AudioAttributes audioAttribute = audioAttributes.get(index); 674 if (uniqueAudioAttributes.contains(new AudioAttributesWrapper(audioAttribute))) { 675 continue; 676 } 677 uniqueAudioAttributes.add(new AudioAttributesWrapper(audioAttributes.get(index))); 678 uniqueAttributes.add(new AudioAttributes.Builder(audioAttribute).build()); 679 } 680 681 return uniqueAttributes; 682 } 683 getAllContextsIds()684 List<Integer> getAllContextsIds() { 685 List<Integer> contextIds = new ArrayList<>(mContextToAttributes.size()); 686 for (int index = 0; index < mContextToAttributes.size(); index++) { 687 if (isInvalidContextId(mContextToAttributes.keyAt(index))) { 688 continue; 689 } 690 contextIds.add(mContextToAttributes.keyAt(index)); 691 } 692 return contextIds; 693 } 694 getNonCarSystemContextIds()695 static List<Integer> getNonCarSystemContextIds() { 696 return NON_CAR_SYSTEM_CONTEXTS; 697 } 698 getCarSystemContextIds()699 static List<Integer> getCarSystemContextIds() { 700 return CAR_SYSTEM_CONTEXTS; 701 } 702 703 /** 704 * Return static list of logical audio attributes grouping. 705 */ getAllContextsInfo()706 public static List<CarAudioContextInfo> getAllContextsInfo() { 707 return CAR_CONTEXT_INFO; 708 } 709 710 @Nullable getContextsInfo()711 public List<CarAudioContextInfo> getContextsInfo() { 712 return mCarAudioContextInfos; 713 } 714 getAllNonCarSystemContextsInfo()715 static List<CarAudioContextInfo> getAllNonCarSystemContextsInfo() { 716 return List.of( 717 CAR_CONTEXT_INFO_MUSIC, 718 CAR_CONTEXT_INFO_NAVIGATION, 719 CAR_CONTEXT_INFO_VOICE_COMMAND, 720 CAR_CONTEXT_INFO_CALL_RING, 721 CAR_CONTEXT_INFO_CALL, 722 CAR_CONTEXT_INFO_ALARM, 723 CAR_CONTEXT_INFO_NOTIFICATION, 724 CAR_CONTEXT_INFO_SYSTEM_SOUND 725 ); 726 } 727 getAllCarSystemContextsInfo()728 static List<CarAudioContextInfo> getAllCarSystemContextsInfo() { 729 return List.of( 730 CAR_CONTEXT_INFO_EMERGENCY, 731 CAR_CONTEXT_INFO_SAFETY, 732 CAR_CONTEXT_INFO_VEHICLE_STATUS, 733 CAR_CONTEXT_INFO_ANNOUNCEMENT 734 ); 735 } 736 getContextsToDuck(@udioContext int context)737 List<Integer> getContextsToDuck(@AudioContext int context) { 738 return mContextsToDuck.get(context); 739 } 740 getInvalidContext()741 static @AudioContext int getInvalidContext() { 742 return INVALID; 743 } 744 isInvalidContextId(@udioContext int id)745 static boolean isInvalidContextId(@AudioContext int id) { 746 return id == INVALID; 747 } 748 validateAllAudioAttributesSupported(List<Integer> contexts)749 boolean validateAllAudioAttributesSupported(List<Integer> contexts) { 750 ArraySet<AudioAttributesWrapper> supportedAudioAttributes = 751 new ArraySet<>(ALL_SUPPORTED_ATTRIBUTES); 752 753 for (int contextIndex = 0; contextIndex < contexts.size(); contextIndex++) { 754 int contextId = contexts.get(contextIndex); 755 AudioAttributes[] attributes = getAudioAttributesForContext(contextId); 756 List<AudioAttributesWrapper> wrappers = new ArrayList<>(attributes.length); 757 for (int index = 0; index < attributes.length; index++) { 758 wrappers.add(new AudioAttributesWrapper(attributes[index])); 759 } 760 761 supportedAudioAttributes.removeAll(wrappers); 762 } 763 764 for (int index = 0; index < supportedAudioAttributes.size(); index++) { 765 AudioAttributesWrapper wrapper = supportedAudioAttributes.valueAt(index); 766 Slogf.e(CarLog.TAG_AUDIO, 767 "AudioAttribute %s not supported in current configuration", wrapper); 768 } 769 770 return supportedAudioAttributes.isEmpty(); 771 } 772 convertAttributesToUsage(AudioAttributes[] audioAttributes)773 private static int[] convertAttributesToUsage(AudioAttributes[] audioAttributes) { 774 int[] usages = new int[audioAttributes.length]; 775 for (int index = 0; index < audioAttributes.length; index++) { 776 usages[index] = audioAttributes[index].getSystemUsage(); 777 } 778 return usages; 779 } 780 781 /** 782 * Class wraps an audio attributes object. This can be used for comparing audio attributes. 783 * Current the audio attributes class compares all the attributes in the two objects. 784 * In automotive only the audio attribute usage is currently used, thus this class can be used 785 * to compare that audio attribute usage. 786 */ 787 public static final class AudioAttributesWrapper { 788 789 private final AudioAttributes mAudioAttributes; 790 AudioAttributesWrapper(AudioAttributes audioAttributes)791 AudioAttributesWrapper(AudioAttributes audioAttributes) { 792 mAudioAttributes = audioAttributes; 793 } 794 audioAttributeMatches(AudioAttributes audioAttributes, AudioAttributes inputAudioAttribute)795 static boolean audioAttributeMatches(AudioAttributes audioAttributes, 796 AudioAttributes inputAudioAttribute) { 797 return audioAttributes.getSystemUsage() == inputAudioAttribute.getSystemUsage(); 798 } 799 800 @Override equals(Object object)801 public boolean equals(Object object) { 802 if (this == object) return true; 803 if (object == null || !(object instanceof AudioAttributesWrapper)) { 804 return false; 805 } 806 807 AudioAttributesWrapper that = (AudioAttributesWrapper) object; 808 809 return audioAttributeMatches(mAudioAttributes, that.mAudioAttributes); 810 } 811 812 @Override hashCode()813 public int hashCode() { 814 return Integer.hashCode(mAudioAttributes.getSystemUsage()); 815 } 816 817 @Override toString()818 public String toString() { 819 return mAudioAttributes.toString(); 820 } 821 822 /** 823 * Returns the audio attributes for the wrapper 824 */ getAudioAttributes()825 public AudioAttributes getAudioAttributes() { 826 return mAudioAttributes; 827 } 828 } 829 } 830