• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2022 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 package com.android.car.audio;
17 
18 import static android.media.AudioAttributes.USAGE_ASSISTANCE_NAVIGATION_GUIDANCE;
19 import static android.media.AudioAttributes.USAGE_MEDIA;
20 
21 import static com.android.car.audio.CarAudioContext.AudioContext;
22 
23 import static org.junit.Assert.assertThrows;
24 import static org.mockito.Mockito.any;
25 import static org.mockito.Mockito.anyInt;
26 import static org.mockito.Mockito.eq;
27 import static org.mockito.Mockito.mock;
28 import static org.mockito.Mockito.times;
29 import static org.mockito.Mockito.verify;
30 import static org.mockito.Mockito.when;
31 
32 import android.car.media.CarAudioManager;
33 import android.car.test.AbstractExpectableTestCase;
34 import android.media.AudioAttributes;
35 import android.media.AudioDeviceAttributes;
36 import android.media.AudioDeviceInfo;
37 import android.media.AudioFormat;
38 import android.media.AudioSystem;
39 import android.media.audiopolicy.AudioMix;
40 import android.media.audiopolicy.AudioPolicy;
41 import android.util.ArrayMap;
42 import android.util.SparseArray;
43 
44 import com.google.common.collect.ImmutableList;
45 
46 import org.junit.Test;
47 import org.junit.runner.RunWith;
48 import org.mockito.ArgumentCaptor;
49 import org.mockito.Mock;
50 import org.mockito.Mockito;
51 import org.mockito.junit.MockitoJUnitRunner;
52 
53 import java.util.ArrayList;
54 import java.util.List;
55 import java.util.Map;
56 
57 @RunWith(MockitoJUnitRunner.class)
58 public final class CarAudioDynamicRoutingTest extends AbstractExpectableTestCase {
59     private static final String MUSIC_ADDRESS = "bus0_music";
60     private static final String NAV_ADDRESS = "bus1_nav";
61 
62     private static final AudioAttributes TEST_MEDIA_ATTRIBUTE =
63             CarAudioContext.getAudioAttributeFromUsage(USAGE_MEDIA);
64     private static final AudioAttributes TEST_NAVIGATION_ATTRIBUTE =
65             CarAudioContext.getAudioAttributeFromUsage(USAGE_ASSISTANCE_NAVIGATION_GUIDANCE);
66 
67     private static final CarAudioContext TEST_CAR_AUDIO_CONTEXT =
68             new CarAudioContext(CarAudioContext.getAllContextsInfo(),
69                     /* useCoreAudioRouting= */ false);
70 
71     private static final @AudioContext int TEST_MEDIA_CONTEXT =
72             TEST_CAR_AUDIO_CONTEXT.getContextForAudioAttribute(TEST_MEDIA_ATTRIBUTE);
73     private static final @AudioContext int TEST_NAVIGATION_CONTEXT =
74             TEST_CAR_AUDIO_CONTEXT.getContextForAudioAttribute(TEST_NAVIGATION_ATTRIBUTE);
75 
76     @Mock
77     private AudioManagerWrapper mAudioManager;
78 
79     @Test
setupAudioDynamicRouting()80     public void setupAudioDynamicRouting() {
81         AudioPolicy.Builder mockBuilder = Mockito.mock(AudioPolicy.Builder.class);
82         SparseArray<CarAudioZone> zones = getTestCarAudioZones(/* defaultConfigSelected= */ false,
83                 /* secondaryConfigSelected= */ true);
84 
85         CarAudioDynamicRouting.setupAudioDynamicRouting(TEST_CAR_AUDIO_CONTEXT, mAudioManager,
86                 mockBuilder, zones);
87 
88         ArgumentCaptor<AudioMix> audioMixCaptor = ArgumentCaptor.forClass(AudioMix.class);
89         verify(mockBuilder, times(2)).addMix(audioMixCaptor.capture());
90         AudioMix audioMix = audioMixCaptor.getValue();
91         expectWithMessage("Music address registered").that(audioMix.getRegistration())
92                 .isEqualTo(MUSIC_ADDRESS);
93         expectWithMessage("Contains Music device")
94                 .that(audioMix.isRoutedToDevice(AudioSystem.DEVICE_OUT_BUS, MUSIC_ADDRESS))
95                 .isTrue();
96         expectWithMessage("Does not contain Nav device")
97                 .that(audioMix.isRoutedToDevice(AudioSystem.DEVICE_OUT_BUS, NAV_ADDRESS))
98                 .isFalse();
99         expectWithMessage("Affected media usage")
100                 .that(audioMix.isAffectingUsage(AudioAttributes.USAGE_MEDIA))
101                 .isTrue();
102         expectWithMessage("Affected game usage")
103                 .that(audioMix.isAffectingUsage(AudioAttributes.USAGE_GAME))
104                 .isTrue();
105         expectWithMessage("Affected unknown usage")
106                 .that(audioMix.isAffectingUsage(AudioAttributes.USAGE_UNKNOWN))
107                 .isTrue();
108         expectWithMessage("Non-affected voice comm usage")
109                 .that(audioMix.isAffectingUsage(AudioAttributes.USAGE_VOICE_COMMUNICATION))
110                 .isFalse();
111         expectWithMessage("Non-affected voice comm signalling usage")
112                 .that(audioMix.isAffectingUsage(
113                         AudioAttributes.USAGE_VOICE_COMMUNICATION_SIGNALLING))
114                 .isFalse();
115         expectWithMessage("Non-affected alarm usage")
116                 .that(audioMix.isAffectingUsage(AudioAttributes.USAGE_ALARM))
117                 .isFalse();
118         expectWithMessage("Non-affected notification usage")
119                 .that(audioMix.isAffectingUsage(AudioAttributes.USAGE_NOTIFICATION))
120                 .isFalse();
121         expectWithMessage("Non-affected notification ringtone usage")
122                 .that(audioMix.isAffectingUsage(AudioAttributes.USAGE_NOTIFICATION_RINGTONE))
123                 .isFalse();
124         expectWithMessage("Non-affected notification event usage")
125                 .that(audioMix.isAffectingUsage(AudioAttributes.USAGE_NOTIFICATION_EVENT))
126                 .isFalse();
127         expectWithMessage("Non-affected assistance accessibility usage")
128                 .that(audioMix.isAffectingUsage(AudioAttributes.USAGE_ASSISTANCE_ACCESSIBILITY))
129                 .isFalse();
130         expectWithMessage("Non-affected nav guidance usage")
131                 .that(audioMix.isAffectingUsage(
132                         AudioAttributes.USAGE_ASSISTANCE_NAVIGATION_GUIDANCE))
133                 .isFalse();
134         expectWithMessage("Non-affected assistance sonification usage")
135                 .that(audioMix.isAffectingUsage(AudioAttributes.USAGE_ASSISTANCE_SONIFICATION))
136                 .isFalse();
137         expectWithMessage("Non-affected assistant usage")
138                 .that(audioMix.isAffectingUsage(AudioAttributes.USAGE_ASSISTANT))
139                 .isFalse();
140         expectWithMessage("Non-affected call assistant usage")
141                 .that(audioMix.isAffectingUsage(AudioAttributes.USAGE_CALL_ASSISTANT))
142                 .isFalse();
143         expectWithMessage("Non-affected emergency usage")
144                 .that(audioMix.isAffectingUsage(AudioAttributes.USAGE_EMERGENCY))
145                 .isFalse();
146         expectWithMessage("Non-affected safety usage")
147                 .that(audioMix.isAffectingUsage(AudioAttributes.USAGE_SAFETY))
148                 .isFalse();
149         expectWithMessage("Non-affected vehicle status usage")
150                 .that(audioMix.isAffectingUsage(AudioAttributes.USAGE_VEHICLE_STATUS))
151                 .isFalse();
152         expectWithMessage("Non-affected announcement usage")
153                 .that(audioMix.isAffectingUsage(AudioAttributes.USAGE_ANNOUNCEMENT))
154                 .isFalse();
155     }
156 
157     @Test
setupAudioDynamicRouting_withDefaultConfigSelected()158     public void setupAudioDynamicRouting_withDefaultConfigSelected() {
159         AudioPolicy.Builder mockBuilder = Mockito.mock(AudioPolicy.Builder.class);
160         SparseArray<CarAudioZone> zones = getTestCarAudioZones(/* defaultConfigSelected= */ true,
161                 /* secondaryConfigSelected= */ false);
162 
163         CarAudioDynamicRouting.setupAudioDynamicRouting(TEST_CAR_AUDIO_CONTEXT, mAudioManager,
164                 mockBuilder, zones);
165 
166         ArgumentCaptor<AudioMix> audioMixCaptor = ArgumentCaptor.forClass(AudioMix.class);
167         verify(mockBuilder, times(1)).addMix(audioMixCaptor.capture());
168         AudioMix audioMix = audioMixCaptor.getValue();
169         expectWithMessage("Music address registered with default config selected")
170                 .that(audioMix.getRegistration()).isEqualTo(MUSIC_ADDRESS);
171         expectWithMessage("Contains Music device with default config selected")
172                 .that(audioMix.isRoutedToDevice(AudioSystem.DEVICE_OUT_BUS, MUSIC_ADDRESS))
173                 .isTrue();
174         expectWithMessage("Does not contain Nav device with default config selected")
175                 .that(audioMix.isRoutedToDevice(AudioSystem.DEVICE_OUT_BUS, NAV_ADDRESS))
176                 .isFalse();
177         expectWithMessage("Affected media usage with default config selected")
178                 .that(audioMix.isAffectingUsage(AudioAttributes.USAGE_MEDIA))
179                 .isTrue();
180         expectWithMessage("Affected game usage with default config selected")
181                 .that(audioMix.isAffectingUsage(AudioAttributes.USAGE_GAME))
182                 .isTrue();
183         expectWithMessage("Affected unknown usage with default config selected")
184                 .that(audioMix.isAffectingUsage(AudioAttributes.USAGE_UNKNOWN))
185                 .isTrue();
186         expectWithMessage("Non-affected voice comm usage with default config selected")
187                 .that(audioMix.isAffectingUsage(AudioAttributes.USAGE_VOICE_COMMUNICATION))
188                 .isFalse();
189         expectWithMessage(
190                 "Non-affected voice comm signalling usage with default config selected")
191                 .that(audioMix.isAffectingUsage(
192                         AudioAttributes.USAGE_VOICE_COMMUNICATION_SIGNALLING))
193                 .isFalse();
194         expectWithMessage("Non-affected alarm usage")
195                 .that(audioMix.isAffectingUsage(AudioAttributes.USAGE_ALARM))
196                 .isFalse();
197         expectWithMessage("Non-affected notification usage with default config selected")
198                 .that(audioMix.isAffectingUsage(AudioAttributes.USAGE_NOTIFICATION))
199                 .isFalse();
200         expectWithMessage(
201                 "Non-affected notification ringtone usage with default config selected")
202                 .that(audioMix.isAffectingUsage(AudioAttributes.USAGE_NOTIFICATION_RINGTONE))
203                 .isFalse();
204         expectWithMessage("Non-affected notification event usage with default config selected")
205                 .that(audioMix.isAffectingUsage(AudioAttributes.USAGE_NOTIFICATION_EVENT))
206                 .isFalse();
207         expectWithMessage(
208                 "Non-affected assistance accessibility usage with default config selected")
209                 .that(audioMix.isAffectingUsage(AudioAttributes.USAGE_ASSISTANCE_ACCESSIBILITY))
210                 .isFalse();
211         expectWithMessage("Non-affected nav guidance usage")
212                 .that(audioMix.isAffectingUsage(
213                         AudioAttributes.USAGE_ASSISTANCE_NAVIGATION_GUIDANCE))
214                 .isFalse();
215         expectWithMessage("Non-affected assistance sonification usage")
216                 .that(audioMix.isAffectingUsage(AudioAttributes.USAGE_ASSISTANCE_SONIFICATION))
217                 .isFalse();
218         expectWithMessage("Non-affected assistant usage with default config selected")
219                 .that(audioMix.isAffectingUsage(AudioAttributes.USAGE_ASSISTANT))
220                 .isFalse();
221         expectWithMessage("Non-affected call assistant usage with default config selected")
222                 .that(audioMix.isAffectingUsage(AudioAttributes.USAGE_CALL_ASSISTANT))
223                 .isFalse();
224         expectWithMessage("Non-affected emergency usage with default config selected")
225                 .that(audioMix.isAffectingUsage(AudioAttributes.USAGE_EMERGENCY))
226                 .isFalse();
227         expectWithMessage("Non-affected safety usage with default config selected")
228                 .that(audioMix.isAffectingUsage(AudioAttributes.USAGE_SAFETY))
229                 .isFalse();
230         expectWithMessage("Non-affected vehicle status usage")
231                 .that(audioMix.isAffectingUsage(AudioAttributes.USAGE_VEHICLE_STATUS))
232                 .isFalse();
233         expectWithMessage("Non-affected announcement usage with default config selected")
234                 .that(audioMix.isAffectingUsage(AudioAttributes.USAGE_ANNOUNCEMENT))
235                 .isFalse();
236     }
237 
238     @Test
setupAudioDynamicRouting_withoutSelectedConfig()239     public void setupAudioDynamicRouting_withoutSelectedConfig() {
240         AudioPolicy.Builder mockBuilder = Mockito.mock(AudioPolicy.Builder.class);
241         SparseArray<CarAudioZone> zones = getTestCarAudioZones(/* defaultConfigSelected= */ false,
242                 /* secondaryConfigSelected= */ false);
243 
244         IllegalStateException thrown = assertThrows(IllegalStateException.class,
245                 () -> CarAudioDynamicRouting.setupAudioDynamicRouting(TEST_CAR_AUDIO_CONTEXT,
246                         mAudioManager, mockBuilder, zones));
247 
248         expectWithMessage("No selected config exception").that(thrown).hasMessageThat()
249                 .contains("Selected configuration for zone");
250     }
251 
252     @Test
setupAudioDynamicRoutingForMirrorDevice()253     public void setupAudioDynamicRoutingForMirrorDevice() {
254         AudioPolicy.Builder mockBuilder = Mockito.mock(AudioPolicy.Builder.class);
255         AudioDeviceInfo mirrorDevice = Mockito.mock(AudioDeviceInfo.class);
256         CarAudioDeviceInfo carMirrorDeviceInfo = getCarAudioDeviceInfo(MUSIC_ADDRESS,
257                 /* canBeRoutedWithDynamicPolicyMix= */ true, mirrorDevice);
258         when(mAudioManager.getDevices(anyInt())).thenReturn(
259                 new AudioDeviceInfo[]{mirrorDevice});
260 
261 
262         CarAudioDynamicRouting.setupAudioDynamicRoutingForMirrorDevice(mockBuilder,
263                 List.of(carMirrorDeviceInfo), mAudioManager);
264 
265         ArgumentCaptor<AudioMix> audioMixCaptor = ArgumentCaptor.forClass(AudioMix.class);
266 
267         verify(mockBuilder).addMix(audioMixCaptor.capture());
268         AudioMix audioMix = audioMixCaptor.getValue();
269         expectWithMessage("Music address registered").that(audioMix.getRegistration())
270                 .isEqualTo(MUSIC_ADDRESS);
271         expectWithMessage("Contains Music device")
272                 .that(audioMix.isRoutedToDevice(AudioSystem.DEVICE_OUT_BUS, MUSIC_ADDRESS))
273                 .isTrue();
274         expectWithMessage("Affected media usage")
275                 .that(audioMix.isAffectingUsage(AudioAttributes.USAGE_MEDIA))
276                 .isTrue();
277     }
278 
getTestCarAudioZones(boolean defaultConfigSelected, boolean secondaryConfigSelected)279     private SparseArray<CarAudioZone> getTestCarAudioZones(boolean defaultConfigSelected,
280             boolean secondaryConfigSelected) {
281         AudioDeviceInfo musicDeviceInfo = Mockito.mock(AudioDeviceInfo.class);
282         CarAudioDeviceInfo musicCarAudioDeviceInfo = getCarAudioDeviceInfo(MUSIC_ADDRESS,
283                 /* canBeRoutedWithDynamicPolicyMix= */ true, musicDeviceInfo);
284         AudioDeviceInfo navDeviceInfo = Mockito.mock(AudioDeviceInfo.class);
285         CarAudioDeviceInfo navCarAudioDeviceInfo = getCarAudioDeviceInfo(NAV_ADDRESS,
286                 /* canBeRoutedWithDynamicPolicyMix= */ false, navDeviceInfo);
287         when(mAudioManager.getDevices(anyInt())).thenReturn(
288                 new AudioDeviceInfo[]{musicDeviceInfo, navDeviceInfo});
289         CarVolumeGroup mockMusicGroup = new VolumeGroupBuilder()
290                 .addDeviceAddressAndContexts(TEST_MEDIA_CONTEXT, MUSIC_ADDRESS)
291                 .addCarAudioDeviceInfoMock(musicCarAudioDeviceInfo)
292                 .build();
293         CarVolumeGroup mockNavGroupRoutingOnMusic = new VolumeGroupBuilder()
294                 .addDeviceAddressAndContexts(TEST_NAVIGATION_CONTEXT, NAV_ADDRESS)
295                 .addCarAudioDeviceInfoMock(navCarAudioDeviceInfo)
296                 .build();
297         CarAudioZoneConfig defaultAudioZoneConfig =
298                 new CarAudioZoneConfig.Builder("Default zone config 0",
299                         CarAudioManager.PRIMARY_AUDIO_ZONE, /* zoneConfigId= */ 0,
300                         /* isDefault= */ true).addVolumeGroup(mockMusicGroup)
301                         .addVolumeGroup(mockNavGroupRoutingOnMusic)
302                         .build();
303         defaultAudioZoneConfig.setIsSelected(defaultConfigSelected);
304         CarAudioZoneConfig secondaryAudioZoneConfig =
305                 new CarAudioZoneConfig.Builder("Selected zone config 0",
306                         CarAudioManager.PRIMARY_AUDIO_ZONE, /* zoneConfigId= */ 1,
307                         /* isDefault= */ false).addVolumeGroup(mockMusicGroup)
308                         .addVolumeGroup(mockNavGroupRoutingOnMusic)
309                         .build();
310         secondaryAudioZoneConfig.setIsSelected(secondaryConfigSelected);
311         CarAudioZone carAudioZone = new CarAudioZone(TEST_CAR_AUDIO_CONTEXT, "Primary zone",
312                 CarAudioManager.PRIMARY_AUDIO_ZONE);
313         carAudioZone.addZoneConfig(defaultAudioZoneConfig);
314         carAudioZone.addZoneConfig(secondaryAudioZoneConfig);
315         SparseArray<CarAudioZone> zones = new SparseArray<>();
316         zones.put(CarAudioManager.PRIMARY_AUDIO_ZONE, carAudioZone);
317         return zones;
318     }
319 
getCarAudioDeviceInfo(String address, boolean canBeRoutedWithDynamicPolicyMix, AudioDeviceInfo audioDeviceInfo)320     private CarAudioDeviceInfo getCarAudioDeviceInfo(String address,
321             boolean canBeRoutedWithDynamicPolicyMix, AudioDeviceInfo audioDeviceInfo) {
322         when(audioDeviceInfo.isSink()).thenReturn(true);
323         when(audioDeviceInfo.getType()).thenReturn(AudioDeviceInfo.TYPE_BUS);
324         when(audioDeviceInfo.getAddress()).thenReturn(address);
325         AudioDeviceAttributes deviceAttributes = new AudioDeviceAttributes(audioDeviceInfo);
326         CarAudioDeviceInfo carAudioDeviceInfo = Mockito.mock(CarAudioDeviceInfo.class);
327         when(carAudioDeviceInfo.getAddress()).thenReturn(address);
328         when(carAudioDeviceInfo.getAudioDevice()).thenReturn(deviceAttributes);
329         when(carAudioDeviceInfo.getEncodingFormat())
330                 .thenReturn(AudioFormat.ENCODING_PCM_16BIT);
331         when(carAudioDeviceInfo.getChannelCount()).thenReturn(2);
332         when(carAudioDeviceInfo.canBeRoutedWithDynamicPolicyMix()).thenReturn(
333                 canBeRoutedWithDynamicPolicyMix);
334         return carAudioDeviceInfo;
335     }
336 
337     private static final class VolumeGroupBuilder {
338         private SparseArray<String> mDeviceAddresses = new SparseArray<>();
339         private CarAudioDeviceInfo mCarAudioDeviceInfoMock;
340         private ArrayMap<String, List<Integer>> mUsagesDeviceAddresses = new ArrayMap<>();
341         private boolean mIsActive = true;
342 
addDeviceAddressAndContexts(@udioContext int context, String address)343         VolumeGroupBuilder addDeviceAddressAndContexts(@AudioContext int context, String address) {
344             mDeviceAddresses.put(context, address);
345             return this;
346         }
347 
addCarAudioDeviceInfoMock(CarAudioDeviceInfo infoMock)348         VolumeGroupBuilder addCarAudioDeviceInfoMock(CarAudioDeviceInfo infoMock) {
349             mCarAudioDeviceInfoMock = infoMock;
350             return this;
351         }
352 
build()353         CarVolumeGroup build() {
354             CarVolumeGroup carVolumeGroup = mock(CarVolumeGroup.class);
355             Map<String, ArrayList<Integer>> addressToContexts = new ArrayMap<>();
356             @AudioContext int[] contexts = new int[mDeviceAddresses.size()];
357 
358             for (int index = 0; index < mDeviceAddresses.size(); index++) {
359                 @AudioContext int context = mDeviceAddresses.keyAt(index);
360                 String address = mDeviceAddresses.get(context);
361                 when(carVolumeGroup.getAddressForContext(context)).thenReturn(address);
362                 if (!addressToContexts.containsKey(address)) {
363                     addressToContexts.put(address, new ArrayList<>());
364                 }
365                 addressToContexts.get(address).add(context);
366                 contexts[index] = context;
367             }
368 
369             for (int index = 0; index < mUsagesDeviceAddresses.size(); index++) {
370                 String address = mUsagesDeviceAddresses.keyAt(index);
371                 List<Integer> usagesForAddress = mUsagesDeviceAddresses.get(address);
372                 when(carVolumeGroup.getAllSupportedUsagesForAddress(eq(address)))
373                         .thenReturn(usagesForAddress);
374             }
375 
376             when(carVolumeGroup.getContexts()).thenReturn(contexts);
377 
378             for (String address : addressToContexts.keySet()) {
379                 when(carVolumeGroup.getContextsForAddress(address))
380                         .thenReturn(ImmutableList.copyOf(addressToContexts.get(address)));
381             }
382             when(carVolumeGroup.getAddresses())
383                     .thenReturn(ImmutableList.copyOf(addressToContexts.keySet()));
384 
385             when(carVolumeGroup.getCarAudioDeviceInfoForAddress(any()))
386                     .thenReturn(mCarAudioDeviceInfoMock);
387             when(carVolumeGroup.isActive()).thenReturn(mIsActive);
388 
389             return carVolumeGroup;
390         }
391     }
392 }
393