1 /*
2  * Copyright (C) 2021 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.audio.CarAudioContext.ALARM;
20 import static com.android.car.audio.CarAudioContext.ANNOUNCEMENT;
21 import static com.android.car.audio.CarAudioContext.CALL;
22 import static com.android.car.audio.CarAudioContext.CALL_RING;
23 import static com.android.car.audio.CarAudioContext.EMERGENCY;
24 import static com.android.car.audio.CarAudioContext.INVALID;
25 import static com.android.car.audio.CarAudioContext.MUSIC;
26 import static com.android.car.audio.CarAudioContext.NAVIGATION;
27 import static com.android.car.audio.CarAudioContext.NOTIFICATION;
28 import static com.android.car.audio.CarAudioContext.SAFETY;
29 import static com.android.car.audio.CarAudioContext.SYSTEM_SOUND;
30 import static com.android.car.audio.CarAudioContext.VEHICLE_STATUS;
31 import static com.android.car.audio.CarAudioContext.VOICE_COMMAND;
32 
33 import android.media.AudioFocusInfo;
34 import android.util.SparseArray;
35 
36 import com.android.internal.annotations.VisibleForTesting;
37 
38 import java.util.ArrayList;
39 import java.util.HashSet;
40 import java.util.List;
41 import java.util.Set;
42 
43 final class CarDuckingUtils {
44     @VisibleForTesting
45     static final SparseArray<int[]> sContextsToDuck = new SparseArray<>();
46 
47     static {
48         // INVALID ducks nothing
sContextsToDuck.append(INVALID, new int[0])49         sContextsToDuck.append(INVALID, new int[0]);
50         // MUSIC ducks nothing
sContextsToDuck.append(MUSIC, new int[0])51         sContextsToDuck.append(MUSIC, new int[0]);
sContextsToDuck.append(NAVIGATION, new int[]{ MUSIC, CALL_RING, CALL, ALARM, NOTIFICATION, SYSTEM_SOUND, VEHICLE_STATUS, ANNOUNCEMENT })52         sContextsToDuck.append(NAVIGATION, new int[]{
53                 MUSIC,
54                 CALL_RING,
55                 CALL,
56                 ALARM,
57                 NOTIFICATION,
58                 SYSTEM_SOUND,
59                 VEHICLE_STATUS,
60                 ANNOUNCEMENT
61         });
sContextsToDuck.append(VOICE_COMMAND, new int[]{ CALL_RING })62         sContextsToDuck.append(VOICE_COMMAND, new int[]{
63                 CALL_RING
64         });
sContextsToDuck.append(CALL_RING, new int[0])65         sContextsToDuck.append(CALL_RING, new int[0]);
sContextsToDuck.append(CALL, new int[]{ CALL_RING, ALARM, NOTIFICATION, VEHICLE_STATUS })66         sContextsToDuck.append(CALL, new int[]{
67                 CALL_RING,
68                 ALARM,
69                 NOTIFICATION,
70                 VEHICLE_STATUS
71         });
sContextsToDuck.append(ALARM, new int[]{ MUSIC })72         sContextsToDuck.append(ALARM, new int[]{
73                 MUSIC
74         });
sContextsToDuck.append(NOTIFICATION, new int[]{ MUSIC, ALARM, ANNOUNCEMENT })75         sContextsToDuck.append(NOTIFICATION, new int[]{
76                 MUSIC,
77                 ALARM,
78                 ANNOUNCEMENT
79         });
sContextsToDuck.append(SYSTEM_SOUND, new int[]{ MUSIC, ALARM, ANNOUNCEMENT })80         sContextsToDuck.append(SYSTEM_SOUND, new int[]{
81                 MUSIC,
82                 ALARM,
83                 ANNOUNCEMENT
84         });
sContextsToDuck.append(EMERGENCY, new int[]{ CALL })85         sContextsToDuck.append(EMERGENCY, new int[]{
86                 CALL
87         });
sContextsToDuck.append(SAFETY, new int[]{ MUSIC, NAVIGATION, VOICE_COMMAND, CALL_RING, CALL, ALARM, NOTIFICATION, SYSTEM_SOUND, VEHICLE_STATUS, ANNOUNCEMENT })88         sContextsToDuck.append(SAFETY, new int[]{
89                 MUSIC,
90                 NAVIGATION,
91                 VOICE_COMMAND,
92                 CALL_RING,
93                 CALL,
94                 ALARM,
95                 NOTIFICATION,
96                 SYSTEM_SOUND,
97                 VEHICLE_STATUS,
98                 ANNOUNCEMENT
99         });
sContextsToDuck.append(VEHICLE_STATUS, new int[]{ MUSIC, CALL_RING, ANNOUNCEMENT })100         sContextsToDuck.append(VEHICLE_STATUS, new int[]{
101                 MUSIC,
102                 CALL_RING,
103                 ANNOUNCEMENT
104         });
105         // ANNOUNCEMENT ducks nothing
sContextsToDuck.append(ANNOUNCEMENT, new int[0])106         sContextsToDuck.append(ANNOUNCEMENT, new int[0]);
107     }
108 
CarDuckingUtils()109     private CarDuckingUtils() {
110     }
111 
getUsagesHoldingFocus(List<AudioFocusInfo> focusHolders)112     static int[] getUsagesHoldingFocus(List<AudioFocusInfo> focusHolders) {
113         Set<Integer> uniqueUsages = new HashSet<>();
114         for (AudioFocusInfo focusInfo : focusHolders) {
115             uniqueUsages.add(focusInfo.getAttributes().getSystemUsage());
116         }
117 
118         int index = 0;
119         int[] usagesHoldingFocus = new int[uniqueUsages.size()];
120         for (int usage : uniqueUsages) {
121             usagesHoldingFocus[index] = usage;
122             index++;
123         }
124         return usagesHoldingFocus;
125     }
126 
getAddressesToDuck(int[] usages, CarAudioZone zone)127     static List<String> getAddressesToDuck(int[] usages, CarAudioZone zone) {
128         Set<Integer> uniqueContexts = CarAudioContext.getUniqueContextsForUsages(usages);
129         uniqueContexts.remove(INVALID);
130         Set<Integer> contextsToDuck = getContextsToDuck(uniqueContexts);
131         Set<String> addressesToDuck = getAddressesForContexts(contextsToDuck, zone);
132 
133         Set<Integer> unduckedContexts = getUnduckedContexts(uniqueContexts, contextsToDuck);
134         Set<String> unduckedAddresses = getAddressesForContexts(unduckedContexts, zone);
135 
136         // We should not duck any device that's associated with an unducked context holding focus.
137         addressesToDuck.removeAll(unduckedAddresses);
138         return new ArrayList<>(addressesToDuck);
139     }
140 
getAddressesToUnduck(List<String> addressesToDuck, List<String> oldAddressesToDuck)141     static List<String> getAddressesToUnduck(List<String> addressesToDuck,
142             List<String> oldAddressesToDuck) {
143         List<String> addressesToUnduck = new ArrayList<>(oldAddressesToDuck);
144         addressesToUnduck.removeAll(addressesToDuck);
145         return addressesToUnduck;
146     }
147 
getUnduckedContexts(Set<Integer> contexts, Set<Integer> duckedContexts)148     private static Set<Integer> getUnduckedContexts(Set<Integer> contexts,
149             Set<Integer> duckedContexts) {
150         Set<Integer> unduckedContexts = new HashSet<>(contexts);
151         unduckedContexts.removeAll(duckedContexts);
152         return unduckedContexts;
153     }
154 
getAddressesForContexts(Set<Integer> contexts, CarAudioZone zone)155     private static Set<String> getAddressesForContexts(Set<Integer> contexts, CarAudioZone zone) {
156         Set<String> addresses = new HashSet<>();
157         for (Integer context : contexts) {
158             addresses.add(zone.getAddressForContext(context));
159         }
160         return addresses;
161     }
162 
getContextsToDuck(Set<Integer> contexts)163     private static Set<Integer> getContextsToDuck(Set<Integer> contexts) {
164         Set<Integer> contextsToDuck = new HashSet<>();
165 
166         for (Integer context : contexts) {
167             int[] duckedContexts = sContextsToDuck.get(context);
168             for (int i = 0; i < duckedContexts.length; i++) {
169                 contextsToDuck.add(duckedContexts[i]);
170             }
171         }
172 
173         // Reduce contextsToDuck down to subset of contexts currently holding focus
174         contextsToDuck.retainAll(contexts);
175         return contextsToDuck;
176     }
177 }
178