• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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     @Override
630     @ExcludeFromCodeCoverageGeneratedReport(reason = DUMP_INFO)
toString()631     public String toString() {
632         StringBuilder builder = new StringBuilder();
633         builder.append("CarAudioContext { useCoreRouting: ");
634         builder.append(mUseCoreAudioRouting).append(", contextInfos: { ");
635         for (int index = 0; index < mCarAudioContextInfos.size(); index++) {
636             builder.append(mCarAudioContextInfos.get(index).toString());
637             if (index < (mCarAudioContextInfos.size() - 1)) {
638                 builder.append(", ");
639             }
640         }
641         builder.append("}}");
642         return builder.toString();
643     }
644 
645     @Override
equals(Object object)646     public boolean equals(Object object) {
647         if (this == object) return true;
648         if (!(object instanceof CarAudioContext that)) {
649             return false;
650         }
651 
652         return mUseCoreAudioRouting == that.mUseCoreAudioRouting
653                 && hasSameContextInfo(that.mCarAudioContextInfos);
654     }
655 
hasSameContextInfo(List<CarAudioContextInfo> infos)656     private boolean hasSameContextInfo(List<CarAudioContextInfo> infos) {
657         return mCarAudioContextInfos.size() == infos.size()
658                 && new ArraySet<>(mCarAudioContextInfos).containsAll(infos);
659     }
660 
661     @Override
hashCode()662     public int hashCode() {
663         return Objects.hash(mUseCoreAudioRouting, mCarAudioContextInfos.hashCode());
664     }
665 
666     @ExcludeFromCodeCoverageGeneratedReport(reason = DUMP_INFO)
dumpProto(ProtoOutputStream proto)667     void dumpProto(ProtoOutputStream proto) {
668         long carAudioContextInfosToken = proto.start(CarAudioDumpProto.CAR_AUDIO_CONTEXT);
669         for (int index = 0; index < mCarAudioContextInfos.size(); index++) {
670             mCarAudioContextInfos.get(index).dumpProto(proto);
671         }
672         proto.end(carAudioContextInfosToken);
673     }
674 
isNotificationAudioAttribute(AudioAttributes attributes)675     static boolean isNotificationAudioAttribute(AudioAttributes attributes) {
676         AudioAttributesWrapper wrapper = new AudioAttributesWrapper(attributes);
677         return getAudioAttributeWrapperFromUsage(AudioAttributes.USAGE_NOTIFICATION).equals(wrapper)
678                 || getAudioAttributeWrapperFromUsage(AudioAttributes.USAGE_NOTIFICATION_EVENT)
679                         .equals(wrapper);
680     }
681 
isCriticalAudioAudioAttribute(AudioAttributes attributes)682     static boolean isCriticalAudioAudioAttribute(AudioAttributes attributes) {
683         AudioAttributesWrapper wrapper = new AudioAttributesWrapper(attributes);
684         return getAudioAttributeWrapperFromUsage(AudioAttributes.USAGE_EMERGENCY).equals(wrapper)
685                 || getAudioAttributeWrapperFromUsage(AudioAttributes.USAGE_SAFETY).equals(wrapper);
686     }
687 
isRingerOrCallAudioAttribute(AudioAttributes attributes)688     static boolean isRingerOrCallAudioAttribute(AudioAttributes attributes) {
689         AudioAttributesWrapper wrapper = new AudioAttributesWrapper(attributes);
690         return getAudioAttributeWrapperFromUsage(AudioAttributes.USAGE_NOTIFICATION_RINGTONE)
691                 .equals(wrapper)
692                 || getAudioAttributeWrapperFromUsage(AudioAttributes.USAGE_VOICE_COMMUNICATION)
693                         .equals(wrapper)
694                 || getAudioAttributeWrapperFromUsage(AudioAttributes.USAGE_CALL_ASSISTANT)
695                 .equals(wrapper)
696                 || getAudioAttributeWrapperFromUsage(
697                         AudioAttributes.USAGE_VOICE_COMMUNICATION_SIGNALLING)
698                         .equals(wrapper);
699 
700     }
701 
toString(@udioContext int audioContext)702     String toString(@AudioContext int audioContext) {
703         String name = mContextToNames.get(audioContext);
704         if (name != null) {
705             return name;
706         }
707         return "Unsupported Context 0x" + Integer.toHexString(audioContext);
708     }
709 
getUniqueAttributesHoldingFocus( List<AudioAttributes> audioAttributes)710     static List<AudioAttributes> getUniqueAttributesHoldingFocus(
711             List<AudioAttributes> audioAttributes) {
712         Set<AudioAttributesWrapper> uniqueAudioAttributes = new ArraySet<>();
713         List<AudioAttributes> uniqueAttributes = new ArrayList<>(uniqueAudioAttributes.size());
714         for (int index = 0; index < audioAttributes.size(); index++) {
715             AudioAttributes audioAttribute = audioAttributes.get(index);
716             if (uniqueAudioAttributes.contains(new AudioAttributesWrapper(audioAttribute))) {
717                 continue;
718             }
719             uniqueAudioAttributes.add(new AudioAttributesWrapper(audioAttributes.get(index)));
720             uniqueAttributes.add(new AudioAttributes.Builder(audioAttribute).build());
721         }
722 
723         return uniqueAttributes;
724     }
725 
getAllContextsIds()726     List<Integer> getAllContextsIds() {
727         List<Integer> contextIds = new ArrayList<>(mContextToAttributes.size());
728         for (int index = 0; index < mContextToAttributes.size(); index++) {
729             if (isInvalidContextId(mContextToAttributes.keyAt(index))) {
730                 continue;
731             }
732             contextIds.add(mContextToAttributes.keyAt(index));
733         }
734         return contextIds;
735     }
736 
getNonCarSystemContextIds()737     static List<Integer> getNonCarSystemContextIds() {
738         return NON_CAR_SYSTEM_CONTEXTS;
739     }
740 
getCarSystemContextIds()741     static List<Integer> getCarSystemContextIds() {
742         return CAR_SYSTEM_CONTEXTS;
743     }
744 
745     /**
746      * Return static list of logical audio attributes grouping.
747      */
getAllContextsInfo()748     public static List<CarAudioContextInfo> getAllContextsInfo() {
749         return CAR_CONTEXT_INFO;
750     }
751 
752     @Nullable
getContextsInfo()753     public List<CarAudioContextInfo> getContextsInfo() {
754         return mCarAudioContextInfos;
755     }
756 
getAllNonCarSystemContextsInfo()757     static List<CarAudioContextInfo> getAllNonCarSystemContextsInfo() {
758         return List.of(
759                 CAR_CONTEXT_INFO_MUSIC,
760                 CAR_CONTEXT_INFO_NAVIGATION,
761                 CAR_CONTEXT_INFO_VOICE_COMMAND,
762                 CAR_CONTEXT_INFO_CALL_RING,
763                 CAR_CONTEXT_INFO_CALL,
764                 CAR_CONTEXT_INFO_ALARM,
765                 CAR_CONTEXT_INFO_NOTIFICATION,
766                 CAR_CONTEXT_INFO_SYSTEM_SOUND
767         );
768     }
769 
getAllCarSystemContextsInfo()770     static List<CarAudioContextInfo> getAllCarSystemContextsInfo() {
771         return List.of(
772                 CAR_CONTEXT_INFO_EMERGENCY,
773                 CAR_CONTEXT_INFO_SAFETY,
774                 CAR_CONTEXT_INFO_VEHICLE_STATUS,
775                 CAR_CONTEXT_INFO_ANNOUNCEMENT
776         );
777     }
778 
getInvalidContext()779     static @AudioContext int getInvalidContext() {
780         return INVALID;
781     }
782 
isInvalidContextId(@udioContext int id)783     static boolean isInvalidContextId(@AudioContext int id) {
784         return id == INVALID;
785     }
786 
validateAllAudioAttributesSupported(List<Integer> contexts)787     boolean validateAllAudioAttributesSupported(List<Integer> contexts) {
788         ArraySet<AudioAttributesWrapper> supportedAudioAttributes =
789                 new ArraySet<>(ALL_SUPPORTED_ATTRIBUTES);
790 
791         for (int contextIndex = 0; contextIndex < contexts.size(); contextIndex++) {
792             int contextId = contexts.get(contextIndex);
793             AudioAttributes[] attributes = getAudioAttributesForContext(contextId);
794             List<AudioAttributesWrapper> wrappers = new ArrayList<>(attributes.length);
795             for (int index = 0; index < attributes.length; index++) {
796                 wrappers.add(new AudioAttributesWrapper(attributes[index]));
797             }
798 
799             supportedAudioAttributes.removeAll(wrappers);
800         }
801 
802         for (int index = 0; index < supportedAudioAttributes.size(); index++) {
803             AudioAttributesWrapper wrapper = supportedAudioAttributes.valueAt(index);
804             Slogf.e(CarLog.TAG_AUDIO,
805                     "AudioAttribute %s not supported in current configuration", wrapper);
806         }
807 
808         return supportedAudioAttributes.isEmpty();
809     }
810 
convertAttributesToUsage(AudioAttributes[] audioAttributes)811     private static int[] convertAttributesToUsage(AudioAttributes[] audioAttributes) {
812         int[] usages = new int[audioAttributes.length];
813         for (int index = 0; index < audioAttributes.length; index++) {
814             usages[index] = audioAttributes[index].getSystemUsage();
815         }
816         return usages;
817     }
818 
819     /**
820      * Class wraps an audio attributes object. This can be used for comparing audio attributes.
821      * Current the audio attributes class compares all the attributes in the two objects.
822      *
823      * In automotive only the audio attribute usage is currently used, thus this class can be used
824      * to compare that audio attribute usage.
825      *
826      * When core routing is enabled, rules are based on all attributes fields, thus makes more
827      * sense to compare associated audio context id.
828      */
829     public static final class AudioAttributesWrapper {
830 
831         private final AudioAttributes mAudioAttributes;
832         // Legacy wrapper does not make use of context id to match, keep it uninitialized.
833         private final int mCarAudioContextId;
834 
AudioAttributesWrapper(AudioAttributes audioAttributes)835         AudioAttributesWrapper(AudioAttributes audioAttributes) {
836             mAudioAttributes = audioAttributes;
837             mCarAudioContextId = INVALID;
838         }
839 
AudioAttributesWrapper(AudioAttributes audioAttributes, int carAudioContextId)840         AudioAttributesWrapper(AudioAttributes audioAttributes, int carAudioContextId) {
841             Preconditions.checkArgument(!isInvalidContextId(carAudioContextId),
842                     "Car audio contexts can not be invalid");
843             mAudioAttributes = audioAttributes;
844             mCarAudioContextId = carAudioContextId;
845         }
846 
audioAttributeMatches(AudioAttributes audioAttributes, AudioAttributes inputAudioAttribute)847         static boolean audioAttributeMatches(AudioAttributes audioAttributes,
848                 AudioAttributes inputAudioAttribute) {
849             return audioAttributes.getSystemUsage() == inputAudioAttribute.getSystemUsage();
850         }
851 
852         @Override
equals(Object object)853         public boolean equals(Object object) {
854             if (this == object) return true;
855             if (object == null || !(object instanceof AudioAttributesWrapper)) {
856                 return false;
857             }
858 
859             AudioAttributesWrapper that = (AudioAttributesWrapper) object;
860             // Wrapping on context: equality is based on associated context, otherwise based on
861             // attributes matching (limited to usage matching).
862             return (mCarAudioContextId != INVALID || that.mCarAudioContextId != INVALID)
863                     ? mCarAudioContextId == that.mCarAudioContextId
864                     : audioAttributeMatches(mAudioAttributes, that.mAudioAttributes);
865         }
866 
867         @Override
hashCode()868         public int hashCode() {
869             return Integer.hashCode(mCarAudioContextId == INVALID
870                     ? mAudioAttributes.getSystemUsage() : mCarAudioContextId);
871         }
872 
873         @Override
toString()874         public String toString() {
875             return mAudioAttributes.toString();
876         }
877 
878         /**
879          * Returns the audio attributes for the wrapper
880          */
getAudioAttributes()881         public AudioAttributes getAudioAttributes() {
882             return mAudioAttributes;
883         }
884 
885         /**
886          * Returns the id of the {@code CarAudioContextInfo} for the wrapper
887          */
getCarAudioContextId()888         public int getCarAudioContextId() {
889             return mCarAudioContextId;
890         }
891     }
892 }
893