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