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