blob: fb86448ce0641a9d29fb60565bcf5e293dfbfceb [file] [log] [blame]
/*
* Copyright (C) 2019 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_ALARM;
import static android.media.AudioAttributes.USAGE_ASSISTANCE_NAVIGATION_GUIDANCE;
import static android.media.AudioAttributes.USAGE_ASSISTANCE_SONIFICATION;
import static android.media.AudioAttributes.USAGE_ASSISTANT;
import static android.media.AudioAttributes.USAGE_MEDIA;
import static com.android.car.audio.CarAudioContext.ALARM;
import static com.android.car.audio.CarAudioContext.AudioContext;
import static com.android.car.audio.CarAudioContext.INVALID;
import static com.android.car.audio.CarAudioContext.MUSIC;
import static com.android.car.audio.CarAudioContext.NAVIGATION;
import static com.android.car.audio.CarAudioContext.SYSTEM_SOUND;
import static com.android.car.audio.CarAudioContext.VOICE_COMMAND;
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.Mockito.any;
import static org.mockito.Mockito.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import static org.testng.Assert.assertThrows;
import static org.testng.Assert.expectThrows;
import android.car.media.CarAudioManager;
import android.hardware.automotive.audiocontrol.AudioGainConfigInfo;
import android.hardware.automotive.audiocontrol.Reasons;
import android.media.AudioAttributes;
import android.media.AudioDeviceInfo;
import android.media.AudioPlaybackConfiguration;
import android.util.SparseArray;
import com.google.common.collect.ImmutableList;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.junit.MockitoJUnitRunner;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@RunWith(MockitoJUnitRunner.class)
public class CarAudioZoneTest {
private static final String MUSIC_ADDRESS = "bus0_music";
private static final String NAV_ADDRESS = "bus1_nav";
private static final String VOICE_ADDRESS = "bus3_voice";
private static final String ASSISTANT_ADDRESS = "bus10_assistant";
private static final String ALARM_ADDRESS = "bus11_alarm";
private static final String ANNOUNCEMENT_ADDRESS = "bus12_announcement";
@Mock
private CarVolumeGroup mMockMusicGroup;
@Mock
private CarVolumeGroup mMockNavGroup;
@Mock
private CarVolumeGroup mMockVoiceGroup;
private CarAudioZone mTestAudioZone =
new CarAudioZone(CarAudioManager.PRIMARY_AUDIO_ZONE, "Primary zone");
@Before
public void setUp() {
mMockMusicGroup = new VolumeGroupBuilder()
.addDeviceAddressAndContexts(MUSIC, MUSIC_ADDRESS).build();
mMockNavGroup = new VolumeGroupBuilder()
.addDeviceAddressAndContexts(NAVIGATION, NAV_ADDRESS).build();
mMockVoiceGroup = new VolumeGroupBuilder()
.addDeviceAddressAndContexts(VOICE_COMMAND, VOICE_ADDRESS).build();
}
@Test
public void getAddressForContext_returnsExpectedDeviceAddress() {
mTestAudioZone.addVolumeGroup(mMockMusicGroup);
mTestAudioZone.addVolumeGroup(mMockNavGroup);
String musicAddress = mTestAudioZone.getAddressForContext(MUSIC);
assertThat(musicAddress).isEqualTo(MUSIC_ADDRESS);
String navAddress = mTestAudioZone.getAddressForContext(NAVIGATION);
assertThat(navAddress).matches(NAV_ADDRESS);
}
@Test
public void getAddressForContext_throwsOnInvalidContext() {
IllegalArgumentException thrown =
expectThrows(IllegalArgumentException.class,
() -> mTestAudioZone.getAddressForContext(INVALID));
assertThat(thrown).hasMessageThat().contains("audioContext 0 is invalid");
}
@Test
public void getAddressForContext_throwsOnNonExistentContext() {
IllegalStateException thrown =
expectThrows(IllegalStateException.class,
() -> mTestAudioZone.getAddressForContext(MUSIC));
assertThat(thrown).hasMessageThat().contains("Could not find output device in zone");
}
@Test
public void findActiveContextsFromPlaybackConfigurations_returnsAllActiveContext() {
mTestAudioZone.addVolumeGroup(mMockMusicGroup);
mTestAudioZone.addVolumeGroup(mMockNavGroup);
mTestAudioZone.addVolumeGroup(mMockVoiceGroup);
List<AudioPlaybackConfiguration> activeConfigurations = ImmutableList.of(
new Builder().setUsage(USAGE_MEDIA).setDeviceAddress(MUSIC_ADDRESS).build(),
new Builder().setUsage(USAGE_ASSISTANCE_NAVIGATION_GUIDANCE)
.setDeviceAddress(NAV_ADDRESS).build()
);
List<Integer> activeContexts = mTestAudioZone
.findActiveContextsFromPlaybackConfigurations(activeConfigurations);
assertThat(activeContexts).containsExactly(MUSIC, NAVIGATION);
}
@Test
public void findActiveContextsFromPlaybackConfigurations_returnsNoMatchingContexts() {
mTestAudioZone.addVolumeGroup(mMockMusicGroup);
mTestAudioZone.addVolumeGroup(mMockNavGroup);
mTestAudioZone.addVolumeGroup(mMockVoiceGroup);
List<AudioPlaybackConfiguration> activeConfigurations = ImmutableList.of(
new Builder().setUsage(USAGE_ASSISTANT)
.setDeviceAddress(ANNOUNCEMENT_ADDRESS).build(),
new Builder().setUsage(USAGE_ALARM)
.setDeviceAddress(ALARM_ADDRESS).build()
);
List<Integer> activeContexts = mTestAudioZone
.findActiveContextsFromPlaybackConfigurations(activeConfigurations);
assertThat(activeContexts).isEmpty();
}
@Test
public void findActiveContextsFromPlaybackConfigurations_withMultiDevices_returnsContexts() {
mTestAudioZone.addVolumeGroup(mMockMusicGroup);
mTestAudioZone.addVolumeGroup(mMockNavGroup);
mTestAudioZone.addVolumeGroup(new VolumeGroupBuilder()
.addDeviceAddressAndContexts(VOICE_COMMAND, ASSISTANT_ADDRESS)
.addDeviceAddressAndContexts(ALARM, ALARM_ADDRESS)
.build());
List<AudioPlaybackConfiguration> activeConfigurations = ImmutableList.of(
new Builder().setUsage(USAGE_ASSISTANT)
.setDeviceAddress(ASSISTANT_ADDRESS).build(),
new Builder().setUsage(USAGE_ALARM)
.setDeviceAddress(ALARM_ADDRESS).build()
);
List<Integer> activeContexts = mTestAudioZone
.findActiveContextsFromPlaybackConfigurations(activeConfigurations);
assertThat(activeContexts).containsExactly(VOICE_COMMAND, ALARM);
}
@Test
public void
findActiveContextsFromPlaybackConfigurations_deviceWithMultiContext_returnsContext() {
mTestAudioZone.addVolumeGroup(new VolumeGroupBuilder()
.addDeviceAddressAndContexts(VOICE_COMMAND, ASSISTANT_ADDRESS)
.addDeviceAddressAndContexts(ALARM, ASSISTANT_ADDRESS)
.addDeviceAddressAndContexts(MUSIC, ASSISTANT_ADDRESS)
.build());
List<AudioPlaybackConfiguration> activeConfigurations = ImmutableList.of(
new Builder().setUsage(USAGE_ALARM)
.setDeviceAddress(ASSISTANT_ADDRESS).build(),
new Builder().setUsage(USAGE_MEDIA)
.setDeviceAddress(MUSIC_ADDRESS).build()
);
List<Integer> activeContexts = mTestAudioZone
.findActiveContextsFromPlaybackConfigurations(activeConfigurations);
assertThat(activeContexts).containsExactly(ALARM);
}
@Test
public void
findActiveContextsFromPlaybackConfigurations_withNonMatchingContext_returnsContext() {
mTestAudioZone.addVolumeGroup(mMockMusicGroup);
mTestAudioZone.addVolumeGroup(mMockNavGroup);
mTestAudioZone.addVolumeGroup(new VolumeGroupBuilder()
.addDeviceAddressAndContexts(VOICE_COMMAND, ASSISTANT_ADDRESS)
.build());
List<AudioPlaybackConfiguration> activeConfigurations = ImmutableList.of(
new Builder().setUsage(USAGE_ASSISTANCE_SONIFICATION)
.setDeviceAddress(ASSISTANT_ADDRESS).build()
);
List<Integer> activeContexts = mTestAudioZone
.findActiveContextsFromPlaybackConfigurations(activeConfigurations);
assertThat(activeContexts).containsExactly(SYSTEM_SOUND);
}
@Test
public void findActiveContextsFromPlaybackConfigurations_withMultiGroupMatch_returnsContexts() {
mTestAudioZone.addVolumeGroup(mMockMusicGroup);
mTestAudioZone.addVolumeGroup(mMockNavGroup);
mTestAudioZone.addVolumeGroup(new VolumeGroupBuilder()
.addDeviceAddressAndContexts(VOICE_COMMAND, ASSISTANT_ADDRESS)
.addDeviceAddressAndContexts(ALARM, ALARM_ADDRESS)
.build());
List<AudioPlaybackConfiguration> activeConfigurations = ImmutableList.of(
new Builder().setUsage(USAGE_ALARM)
.setDeviceAddress(ALARM_ADDRESS).build(),
new Builder().setUsage(USAGE_MEDIA)
.setDeviceAddress(MUSIC_ADDRESS).build()
);
List<Integer> activeContexts = mTestAudioZone
.findActiveContextsFromPlaybackConfigurations(activeConfigurations);
assertThat(activeContexts).containsExactly(ALARM, USAGE_MEDIA);
}
@Test
public void
findActiveContextsFromPlaybackConfigurations_onEmptyConfigurations_returnsNoContexts() {
mTestAudioZone.addVolumeGroup(mMockMusicGroup);
mTestAudioZone.addVolumeGroup(mMockNavGroup);
mTestAudioZone.addVolumeGroup(mMockVoiceGroup);
List<AudioPlaybackConfiguration> activeConfigurations = ImmutableList.of();
List<Integer> activeContexts = mTestAudioZone
.findActiveContextsFromPlaybackConfigurations(activeConfigurations);
assertThat(activeContexts).isEmpty();
}
@Test
public void findActiveContextsFromPlaybackConfigurations_onNullConfigurations_fails() {
mTestAudioZone.addVolumeGroup(mMockMusicGroup);
mTestAudioZone.addVolumeGroup(mMockNavGroup);
mTestAudioZone.addVolumeGroup(mMockVoiceGroup);
List<AudioPlaybackConfiguration> activeConfigurations = null;
assertThrows(NullPointerException.class,
() -> mTestAudioZone
.findActiveContextsFromPlaybackConfigurations(activeConfigurations));
}
@Test
public void isAudioDeviceInfoValidForZone_withNullAudioDeviceInfo_returnsFalse() {
mTestAudioZone.addVolumeGroup(mMockMusicGroup);
assertThat(mTestAudioZone.isAudioDeviceInfoValidForZone(null)).isFalse();
}
@Test
public void isAudioDeviceInfoValidForZone_withNullDeviceAddress_returnsFalse() {
mTestAudioZone.addVolumeGroup(mMockMusicGroup);
AudioDeviceInfo nullAddressDeviceInfo = Mockito.mock(AudioDeviceInfo.class);
when(nullAddressDeviceInfo.getAddress()).thenReturn(null);
assertThat(mTestAudioZone.isAudioDeviceInfoValidForZone(nullAddressDeviceInfo)).isFalse();
}
@Test
public void isAudioDeviceInfoValidForZone_withEmptyDeviceAddress_returnsFalse() {
mTestAudioZone.addVolumeGroup(mMockMusicGroup);
AudioDeviceInfo nullAddressDeviceInfo = Mockito.mock(AudioDeviceInfo.class);
when(nullAddressDeviceInfo.getAddress()).thenReturn("");
assertThat(mTestAudioZone.isAudioDeviceInfoValidForZone(nullAddressDeviceInfo)).isFalse();
}
@Test
public void isAudioDeviceInfoValidForZone_withDeviceAddressNotInZone_returnsFalse() {
mTestAudioZone.addVolumeGroup(mMockMusicGroup);
AudioDeviceInfo nullAddressDeviceInfo = Mockito.mock(AudioDeviceInfo.class);
when(nullAddressDeviceInfo.getAddress()).thenReturn(VOICE_ADDRESS);
assertThat(mTestAudioZone.isAudioDeviceInfoValidForZone(nullAddressDeviceInfo)).isFalse();
}
@Test
public void isAudioDeviceInfoValidForZone_withDeviceAddressInZone_returnsTrue() {
mTestAudioZone.addVolumeGroup(mMockMusicGroup);
AudioDeviceInfo nullAddressDeviceInfo = Mockito.mock(AudioDeviceInfo.class);
when(nullAddressDeviceInfo.getAddress()).thenReturn(MUSIC_ADDRESS);
assertThat(mTestAudioZone.isAudioDeviceInfoValidForZone(nullAddressDeviceInfo)).isTrue();
}
@Test
public void onAudioGainChanged_withDeviceAddressesInZone() {
mTestAudioZone.addVolumeGroup(mMockMusicGroup);
mTestAudioZone.addVolumeGroup(mMockNavGroup);
List<Integer> reasons = List.of(Reasons.REMOTE_MUTE, Reasons.NAV_DUCKING);
AudioGainConfigInfo musicGainInfo = new AudioGainConfigInfo();
musicGainInfo.zoneId = CarAudioManager.PRIMARY_AUDIO_ZONE;
musicGainInfo.devicePortAddress = MUSIC_ADDRESS;
musicGainInfo.volumeIndex = 666;
CarAudioGainConfigInfo carMusicGainInfo = new CarAudioGainConfigInfo(musicGainInfo);
AudioGainConfigInfo navGainInfo = new AudioGainConfigInfo();
navGainInfo.zoneId = CarAudioManager.PRIMARY_AUDIO_ZONE;
navGainInfo.devicePortAddress = NAV_ADDRESS;
navGainInfo.volumeIndex = 999;
CarAudioGainConfigInfo carNavGainInfo = new CarAudioGainConfigInfo(navGainInfo);
List<CarAudioGainConfigInfo> carGains = List.of(carMusicGainInfo, carNavGainInfo);
mTestAudioZone.onAudioGainChanged(reasons, carGains);
verify(mMockMusicGroup).onAudioGainChanged(eq(reasons), eq(carMusicGainInfo));
verify(mMockNavGroup).onAudioGainChanged(eq(reasons), eq(carNavGainInfo));
verify(mMockVoiceGroup, never()).onAudioGainChanged(any(), any());
}
@Test
public void onAudioGainChanged_withoutAnyDeviceAddressInZone() {
List<Integer> reasons = List.of(Reasons.REMOTE_MUTE, Reasons.NAV_DUCKING);
AudioGainConfigInfo musicGainInfo = new AudioGainConfigInfo();
musicGainInfo.zoneId = CarAudioManager.PRIMARY_AUDIO_ZONE;
musicGainInfo.devicePortAddress = MUSIC_ADDRESS;
musicGainInfo.volumeIndex = 666;
CarAudioGainConfigInfo carMusicGainInfo = new CarAudioGainConfigInfo(musicGainInfo);
AudioGainConfigInfo navGainInfo = new AudioGainConfigInfo();
navGainInfo.zoneId = CarAudioManager.PRIMARY_AUDIO_ZONE;
navGainInfo.devicePortAddress = NAV_ADDRESS;
navGainInfo.volumeIndex = 999;
CarAudioGainConfigInfo carNavGainInfo = new CarAudioGainConfigInfo(navGainInfo);
AudioGainConfigInfo voiceGainInfo = new AudioGainConfigInfo();
voiceGainInfo.zoneId = CarAudioManager.PRIMARY_AUDIO_ZONE;
voiceGainInfo.devicePortAddress = VOICE_ADDRESS;
voiceGainInfo.volumeIndex = 777;
CarAudioGainConfigInfo carVoiceGainInfo = new CarAudioGainConfigInfo(voiceGainInfo);
List<CarAudioGainConfigInfo> carGains =
List.of(carMusicGainInfo, carNavGainInfo, carVoiceGainInfo);
mTestAudioZone.onAudioGainChanged(reasons, carGains);
verify(mMockMusicGroup, never()).onAudioGainChanged(any(), any());
verify(mMockNavGroup, never()).onAudioGainChanged(any(), any());
verify(mMockVoiceGroup, never()).onAudioGainChanged(any(), any());
}
private static class VolumeGroupBuilder {
private SparseArray<String> mDeviceAddresses = new SparseArray<>();
VolumeGroupBuilder addDeviceAddressAndContexts(@AudioContext int context, String address) {
mDeviceAddresses.put(context, address);
return this;
}
CarVolumeGroup build() {
CarVolumeGroup carVolumeGroup = mock(CarVolumeGroup.class);
Map<String, ArrayList<Integer>> addressToContexts = new HashMap<>();
@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;
}
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()));
return carVolumeGroup;
}
}
private static class Builder {
private @AudioAttributes.AttributeUsage int mUsage = USAGE_MEDIA;
private boolean mIsActive = true;
private String mDeviceAddress = "";
Builder setUsage(@AudioAttributes.AttributeUsage int usage) {
mUsage = usage;
return this;
}
Builder setDeviceAddress(String deviceAddress) {
mDeviceAddress = deviceAddress;
return this;
}
Builder setInactive() {
mIsActive = false;
return this;
}
AudioPlaybackConfiguration build() {
AudioPlaybackConfiguration configuration = mock(AudioPlaybackConfiguration.class);
AudioAttributes attributes = new AudioAttributes.Builder().setUsage(mUsage).build();
AudioDeviceInfo outputDevice = generateOutAudioDeviceInfo(mDeviceAddress);
when(configuration.getAudioAttributes()).thenReturn(attributes);
when(configuration.getAudioDeviceInfo()).thenReturn(outputDevice);
when(configuration.isActive()).thenReturn(mIsActive);
return configuration;
}
private AudioDeviceInfo generateOutAudioDeviceInfo(String address) {
AudioDeviceInfo audioDeviceInfo = mock(AudioDeviceInfo.class);
when(audioDeviceInfo.getAddress()).thenReturn(address);
when(audioDeviceInfo.getType()).thenReturn(AudioDeviceInfo.TYPE_BUS);
when(audioDeviceInfo.isSource()).thenReturn(false);
when(audioDeviceInfo.isSink()).thenReturn(true);
when(audioDeviceInfo.getInternalType()).thenReturn(AudioDeviceInfo
.convertDeviceTypeToInternalInputDevice(AudioDeviceInfo.TYPE_BUS));
return audioDeviceInfo;
}
}
}