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