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