blob: a1c0e6c6273bf581b3792df81be90b49f33c7430 [file] [log] [blame]
/*
* Copyright (C) 2022 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.car.audio;
import static android.media.AudioAttributes.USAGE_ASSISTANCE_NAVIGATION_GUIDANCE;
import static android.media.AudioAttributes.USAGE_MEDIA;
import static com.android.car.audio.CarAudioContext.AudioContext;
import static org.mockito.Mockito.any;
import static org.mockito.Mockito.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import android.car.media.CarAudioManager;
import android.media.AudioAttributes;
import android.media.AudioDeviceInfo;
import android.media.AudioFormat;
import android.media.AudioSystem;
import android.media.audiopolicy.AudioMix;
import android.media.audiopolicy.AudioPolicy;
import android.util.ArrayMap;
import android.util.SparseArray;
import com.google.common.collect.ImmutableList;
import com.google.common.truth.Expect;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
import org.mockito.Mockito;
import org.mockito.junit.MockitoJUnitRunner;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
@RunWith(MockitoJUnitRunner.class)
public final class CarAudioDynamicRoutingTest {
private static final String MUSIC_ADDRESS = "bus0_music";
private static final String NAV_ADDRESS = "bus1_nav";
private static final AudioAttributes TEST_MEDIA_ATTRIBUTE =
CarAudioContext.getAudioAttributeFromUsage(USAGE_MEDIA);
private static final AudioAttributes TEST_NAVIGATION_ATTRIBUTE =
CarAudioContext.getAudioAttributeFromUsage(USAGE_ASSISTANCE_NAVIGATION_GUIDANCE);
private static final CarAudioContext TEST_CAR_AUDIO_CONTEXT =
new CarAudioContext(CarAudioContext.getAllContextsInfo(),
/* useCoreAudioRouting= */ false);
private static final @AudioContext int TEST_MEDIA_CONTEXT =
TEST_CAR_AUDIO_CONTEXT.getContextForAudioAttribute(TEST_MEDIA_ATTRIBUTE);
private static final @AudioContext int TEST_NAVIGATION_CONTEXT =
TEST_CAR_AUDIO_CONTEXT.getContextForAudioAttribute(TEST_NAVIGATION_ATTRIBUTE);
@Rule
public final Expect expect = Expect.create();
@Test
public void setupAudioDynamicRouting() {
AudioPolicy.Builder mockBuilder = Mockito.mock(AudioPolicy.Builder.class);
CarAudioDeviceInfo musicCarAudioDeviceInfo = getCarAudioDeviceInfo(MUSIC_ADDRESS,
/* canBeRoutedWithDynamicPolicyMix= */ true);
CarAudioDeviceInfo navCarAudioDeviceInfo = getCarAudioDeviceInfo(NAV_ADDRESS,
/* canBeRoutedWithDynamicPolicyMix= */ false);
CarVolumeGroup mockMusicGroup = new VolumeGroupBuilder()
.addDeviceAddressAndContexts(TEST_MEDIA_CONTEXT, MUSIC_ADDRESS)
.addCarAudioDeviceInfoMock(musicCarAudioDeviceInfo)
.build();
CarVolumeGroup mockNavGroupRoutingOnMusic = new VolumeGroupBuilder()
.addDeviceAddressAndContexts(TEST_NAVIGATION_CONTEXT, NAV_ADDRESS)
.addCarAudioDeviceInfoMock(navCarAudioDeviceInfo)
.build();
CarAudioZoneConfig carAudioZoneConfig =
new CarAudioZoneConfig.Builder("Primary zone config 0",
CarAudioManager.PRIMARY_AUDIO_ZONE, /* zoneConfigId= */ 0,
/* isDefault= */ true)
.addVolumeGroup(mockMusicGroup)
.addVolumeGroup(mockNavGroupRoutingOnMusic)
.build();
CarAudioZone carAudioZone = new CarAudioZone(TEST_CAR_AUDIO_CONTEXT, "Primary zone",
CarAudioManager.PRIMARY_AUDIO_ZONE);
carAudioZone.addZoneConfig(carAudioZoneConfig);
SparseArray<CarAudioZone> zones = new SparseArray<>();
zones.put(CarAudioManager.PRIMARY_AUDIO_ZONE, carAudioZone);
CarAudioDynamicRouting.setupAudioDynamicRouting(mockBuilder, zones, TEST_CAR_AUDIO_CONTEXT);
ArgumentCaptor<AudioMix> audioMixCaptor = ArgumentCaptor.forClass(AudioMix.class);
verify(mockBuilder).addMix(audioMixCaptor.capture());
AudioMix audioMix = audioMixCaptor.getValue();
expect.withMessage("Music address registered").that(audioMix.getRegistration())
.isEqualTo(MUSIC_ADDRESS);
expect.withMessage("Contains Music device")
.that(audioMix.isRoutedToDevice(AudioSystem.DEVICE_OUT_BUS, MUSIC_ADDRESS))
.isTrue();
expect.withMessage("Does not contain Nav device")
.that(audioMix.isRoutedToDevice(AudioSystem.DEVICE_OUT_BUS, NAV_ADDRESS))
.isFalse();
expect.withMessage("Affected media usage")
.that(audioMix.isAffectingUsage(AudioAttributes.USAGE_MEDIA))
.isTrue();
expect.withMessage("Affected game usage")
.that(audioMix.isAffectingUsage(AudioAttributes.USAGE_GAME))
.isTrue();
expect.withMessage("Affected unknown usage")
.that(audioMix.isAffectingUsage(AudioAttributes.USAGE_UNKNOWN))
.isTrue();
expect.withMessage("Non-affected voice comm usage")
.that(audioMix.isAffectingUsage(AudioAttributes.USAGE_VOICE_COMMUNICATION))
.isFalse();
expect.withMessage("Non-affected voice comm signalling usage")
.that(audioMix.isAffectingUsage(
AudioAttributes.USAGE_VOICE_COMMUNICATION_SIGNALLING))
.isFalse();
expect.withMessage("Non-affected alarm usage")
.that(audioMix.isAffectingUsage(AudioAttributes.USAGE_ALARM))
.isFalse();
expect.withMessage("Non-affected notification usage")
.that(audioMix.isAffectingUsage(AudioAttributes.USAGE_NOTIFICATION))
.isFalse();
expect.withMessage("Non-affected notification ringtone usage")
.that(audioMix.isAffectingUsage(AudioAttributes.USAGE_NOTIFICATION_RINGTONE))
.isFalse();
expect.withMessage("Non-affected notification event usage")
.that(audioMix.isAffectingUsage(AudioAttributes.USAGE_NOTIFICATION_EVENT))
.isFalse();
expect.withMessage("Non-affected assistance accessibility usage")
.that(audioMix.isAffectingUsage(AudioAttributes.USAGE_ASSISTANCE_ACCESSIBILITY))
.isFalse();
expect.withMessage("Non-affected nav guidance usage")
.that(audioMix.isAffectingUsage(
AudioAttributes.USAGE_ASSISTANCE_NAVIGATION_GUIDANCE))
.isFalse();
expect.withMessage("Non-affected assistance sonification usage")
.that(audioMix.isAffectingUsage(AudioAttributes.USAGE_ASSISTANCE_SONIFICATION))
.isFalse();
expect.withMessage("Non-affected assistant usage")
.that(audioMix.isAffectingUsage(AudioAttributes.USAGE_ASSISTANT))
.isFalse();
expect.withMessage("Non-affected call assistant usage")
.that(audioMix.isAffectingUsage(AudioAttributes.USAGE_CALL_ASSISTANT))
.isFalse();
expect.withMessage("Non-affected emergency usage")
.that(audioMix.isAffectingUsage(AudioAttributes.USAGE_EMERGENCY))
.isFalse();
expect.withMessage("Non-affected safety usage")
.that(audioMix.isAffectingUsage(AudioAttributes.USAGE_SAFETY))
.isFalse();
expect.withMessage("Non-affected vehicle status usage")
.that(audioMix.isAffectingUsage(AudioAttributes.USAGE_VEHICLE_STATUS))
.isFalse();
expect.withMessage("Non-affected announcement usage")
.that(audioMix.isAffectingUsage(AudioAttributes.USAGE_ANNOUNCEMENT))
.isFalse();
}
@Test
public void setupAudioDynamicRoutingForMirrorDevice() {
AudioPolicy.Builder mockBuilder = Mockito.mock(AudioPolicy.Builder.class);
CarAudioDeviceInfo carMirrorDeviceInfo = getCarAudioDeviceInfo(MUSIC_ADDRESS,
/* canBeRoutedWithDynamicPolicyMix= */ true);
CarAudioDynamicRouting.setupAudioDynamicRoutingForMirrorDevice(mockBuilder,
List.of(carMirrorDeviceInfo));
ArgumentCaptor<AudioMix> audioMixCaptor = ArgumentCaptor.forClass(AudioMix.class);
verify(mockBuilder).addMix(audioMixCaptor.capture());
AudioMix audioMix = audioMixCaptor.getValue();
expect.withMessage("Music address registered").that(audioMix.getRegistration())
.isEqualTo(MUSIC_ADDRESS);
expect.withMessage("Contains Music device")
.that(audioMix.isRoutedToDevice(AudioSystem.DEVICE_OUT_BUS, MUSIC_ADDRESS))
.isTrue();
expect.withMessage("Affected media usage")
.that(audioMix.isAffectingUsage(AudioAttributes.USAGE_MEDIA))
.isTrue();
}
private CarAudioDeviceInfo getCarAudioDeviceInfo(String address,
boolean canBeRoutedWithDynamicPolicyMix) {
AudioDeviceInfo audioDeviceInfo = Mockito.mock(AudioDeviceInfo.class);
when(audioDeviceInfo.isSink()).thenReturn(true);
when(audioDeviceInfo.getType()).thenReturn(AudioDeviceInfo.TYPE_BUS);
when(audioDeviceInfo.getAddress()).thenReturn(address);
CarAudioDeviceInfo carAudioDeviceInfo = Mockito.mock(CarAudioDeviceInfo.class);
when(carAudioDeviceInfo.getAddress()).thenReturn(address);
when(carAudioDeviceInfo.getAudioDeviceInfo()).thenReturn(audioDeviceInfo);
when(carAudioDeviceInfo.getEncodingFormat())
.thenReturn(AudioFormat.ENCODING_PCM_16BIT);
when(carAudioDeviceInfo.getChannelCount()).thenReturn(2);
when(carAudioDeviceInfo.canBeRoutedWithDynamicPolicyMix()).thenReturn(
canBeRoutedWithDynamicPolicyMix);
return carAudioDeviceInfo;
}
private static final class VolumeGroupBuilder {
private SparseArray<String> mDeviceAddresses = new SparseArray<>();
private CarAudioDeviceInfo mCarAudioDeviceInfoMock;
private ArrayMap<String, List<Integer>> mUsagesDeviceAddresses = new ArrayMap<>();
VolumeGroupBuilder addDeviceAddressAndContexts(@AudioContext int context, String address) {
mDeviceAddresses.put(context, address);
return this;
}
VolumeGroupBuilder addDeviceAddressAndUsages(int usage, String address) {
if (!mUsagesDeviceAddresses.containsKey(address)) {
mUsagesDeviceAddresses.put(address, new ArrayList<>());
}
mUsagesDeviceAddresses.get(address).add(usage);
return this;
}
VolumeGroupBuilder addCarAudioDeviceInfoMock(CarAudioDeviceInfo infoMock) {
mCarAudioDeviceInfoMock = infoMock;
return this;
}
CarVolumeGroup build() {
CarVolumeGroup carVolumeGroup = mock(CarVolumeGroup.class);
Map<String, ArrayList<Integer>> addressToContexts = new ArrayMap<>();
@AudioContext int[] contexts = new int[mDeviceAddresses.size()];
for (int index = 0; index < mDeviceAddresses.size(); index++) {
@AudioContext int context = mDeviceAddresses.keyAt(index);
String address = mDeviceAddresses.get(context);
when(carVolumeGroup.getAddressForContext(context)).thenReturn(address);
if (!addressToContexts.containsKey(address)) {
addressToContexts.put(address, new ArrayList<>());
}
addressToContexts.get(address).add(context);
contexts[index] = context;
}
for (int index = 0; index < mUsagesDeviceAddresses.size(); index++) {
String address = mUsagesDeviceAddresses.keyAt(index);
List<Integer> usagesForAddress = mUsagesDeviceAddresses.get(address);
when(carVolumeGroup.getAllSupportedUsagesForAddress(eq(address)))
.thenReturn(usagesForAddress);
}
when(carVolumeGroup.getContexts()).thenReturn(contexts);
for (String address : addressToContexts.keySet()) {
when(carVolumeGroup.getContextsForAddress(address))
.thenReturn(ImmutableList.copyOf(addressToContexts.get(address)));
}
when(carVolumeGroup.getAddresses())
.thenReturn(ImmutableList.copyOf(addressToContexts.keySet()));
when(carVolumeGroup.getCarAudioDeviceInfoForAddress(any()))
.thenReturn(mCarAudioDeviceInfoMock);
return carVolumeGroup;
}
}
}