Merge changes Ife97c7b8,I237785fa into sc-dev
* changes:
Adding audioUseHalDuckingSignals config
Updating CarDucking to call AudioControl API
diff --git a/service/res/values/config.xml b/service/res/values/config.xml
index d16484a..9feafda 100644
--- a/service/res/values/config.xml
+++ b/service/res/values/config.xml
@@ -32,6 +32,12 @@
volume group can be muted separately. -->
<bool name="audioUseCarVolumeGroupMuting">false</bool>
+ <!-- Configuration to enable IAudioControl#onDevicesToDuckChange API to inform HAL when to
+ duck. If this is set to true, the API will receive signals indicating which output devices
+ to duck as well as what usages are currently holding focus. If set to false, the API will
+ not be called. -->
+ <bool name="audioUseHalDuckingSignals">true</bool>
+
<!-- Configuration to select version of volume adjustment context priority list.
Version 1 includes all audio contexts arranged in the following order:
NAVIGATION, CALL, MUSIC, ANNOUNCEMENT, VOICE_COMMAND, CALL_RING, SYSTEM_SOUND, SAFETY,
diff --git a/service/src/com/android/car/audio/CarAudioService.java b/service/src/com/android/car/audio/CarAudioService.java
index 7b7fc59..676d8db 100644
--- a/service/src/com/android/car/audio/CarAudioService.java
+++ b/service/src/com/android/car/audio/CarAudioService.java
@@ -137,6 +137,7 @@
private final AudioManager mAudioManager;
private final boolean mUseDynamicRouting;
private final boolean mUseCarVolumeGroupMuting;
+ private final boolean mUseHalDuckingSignals;
private final @CarVolume.CarVolumeListVersion int mAudioVolumeAdjustmentContextsVersion;
private final boolean mPersistMasterMuteState;
private final CarAudioSettings mCarAudioSettings;
@@ -250,6 +251,7 @@
mContext = context;
mTelephonyManager = (TelephonyManager) mContext.getSystemService(Context.TELEPHONY_SERVICE);
mAudioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
+
mUseDynamicRouting = mContext.getResources().getBoolean(R.bool.audioUseDynamicRouting);
mUseCarVolumeGroupMuting = mUseDynamicRouting && mContext.getResources().getBoolean(
R.bool.audioUseCarVolumeGroupMuting);
@@ -257,6 +259,9 @@
R.bool.audioPersistMasterMuteState);
mKeyEventTimeoutMs =
mContext.getResources().getInteger(R.integer.audioVolumeKeyEventTimeoutMs);
+ mUseHalDuckingSignals = mContext.getResources().getBoolean(
+ R.bool.audioUseHalDuckingSignals);
+
mUidToZoneMap = new HashMap<>();
mCarVolumeCallbackHandler = new CarVolumeCallbackHandler();
mCarAudioSettings = new CarAudioSettings(mContext.getContentResolver());
@@ -338,6 +343,7 @@
writer.printf("Run in legacy mode? %b\n", !mUseDynamicRouting);
writer.printf("Persist master mute state? %b\n", mPersistMasterMuteState);
writer.printf("Master muted? %b\n", mAudioManager.isMasterMute());
+ writer.printf("Use hal ducking signals %b\n", mUseHalDuckingSignals);
writer.printf("Volume context priority list version: %d\n",
mAudioVolumeAdjustmentContextsVersion);
writer.printf("Volume key event timeout ms: %d\n", mKeyEventTimeoutMs);
@@ -601,9 +607,11 @@
builder.setAudioPolicyVolumeCallback(mAudioPolicyVolumeCallback);
if (sUseCarAudioFocus) {
- AudioControlWrapper audioControlWrapper = getAudioControlWrapperLocked();
- if (audioControlWrapper.supportsFeature(AUDIOCONTROL_FEATURE_AUDIO_DUCKING)) {
- mCarDucking = new CarDucking(mCarAudioZones);
+ if (mUseHalDuckingSignals) {
+ AudioControlWrapper audioControlWrapper = getAudioControlWrapperLocked();
+ if (audioControlWrapper.supportsFeature(AUDIOCONTROL_FEATURE_AUDIO_DUCKING)) {
+ mCarDucking = new CarDucking(mCarAudioZones, audioControlWrapper);
+ }
}
// Configure our AudioPolicy to handle focus events.
diff --git a/service/src/com/android/car/audio/CarDucking.java b/service/src/com/android/car/audio/CarDucking.java
index 690e39d..3e05b95 100644
--- a/service/src/com/android/car/audio/CarDucking.java
+++ b/service/src/com/android/car/audio/CarDucking.java
@@ -22,23 +22,28 @@
import android.util.SparseArray;
import com.android.car.audio.CarZonesAudioFocus.CarFocusCallback;
+import com.android.car.audio.hal.AudioControlWrapper;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import java.util.ArrayList;
import java.util.List;
+import java.util.Objects;
final class CarDucking implements CarFocusCallback {
private static final String TAG = CarDucking.class.getSimpleName();
private final SparseArray<CarAudioZone> mCarAudioZones;
+ private final AudioControlWrapper mAudioControlWrapper;
private final Object mLock = new Object();
@GuardedBy("mLock")
private final SparseArray<CarDuckingInfo> mCurrentDuckingInfo = new SparseArray<>();
- CarDucking(@NonNull SparseArray<CarAudioZone> carAudioZones) {
- mCarAudioZones = carAudioZones;
+ CarDucking(@NonNull SparseArray<CarAudioZone> carAudioZones,
+ @NonNull AudioControlWrapper audioControlWrapper) {
+ mCarAudioZones = Objects.requireNonNull(carAudioZones);
+ mAudioControlWrapper = Objects.requireNonNull(audioControlWrapper);
for (int i = 0; i < carAudioZones.size(); i++) {
int zoneId = carAudioZones.keyAt(i);
mCurrentDuckingInfo.put(zoneId,
@@ -59,7 +64,7 @@
CarDuckingInfo newDuckingInfo = generateNewDuckingInfoLocked(oldDuckingInfo,
focusHolders);
mCurrentDuckingInfo.put(audioZoneId, newDuckingInfo);
- // TODO(b/158242859): Notify HAL of change
+ mAudioControlWrapper.onDevicesToDuckChange(newDuckingInfo);
}
}
diff --git a/service/src/com/android/car/audio/CarZonesAudioFocus.java b/service/src/com/android/car/audio/CarZonesAudioFocus.java
index 5bf5c99..632ec80 100644
--- a/service/src/com/android/car/audio/CarZonesAudioFocus.java
+++ b/service/src/com/android/car/audio/CarZonesAudioFocus.java
@@ -61,13 +61,13 @@
CarFocusCallback carFocusCallback) {
//Create the zones here, the policy will be set setOwningPolicy,
// which is called right after this constructor.
- Objects.requireNonNull(audioManager);
- Objects.requireNonNull(packageManager);
- Objects.requireNonNull(carAudioZones);
- Objects.requireNonNull(carAudioSettings);
- mCarFocusCallback = carFocusCallback;
+ Objects.requireNonNull(audioManager, "AudioManager cannot be null");
+ Objects.requireNonNull(packageManager, "PackageManager cannot be null");
+ Objects.requireNonNull(carAudioZones, "CarAudioZones cannot be null");
Preconditions.checkArgument(carAudioZones.size() != 0,
"There must be a minimum of one audio zone");
+ Objects.requireNonNull(carAudioSettings, "CarAudioSettings cannot be null");
+ mCarFocusCallback = carFocusCallback;
//Create focus for all the zones
for (int i = 0; i < carAudioZones.size(); i++) {
@@ -202,6 +202,7 @@
if (mCarFocusCallback == null) {
return;
}
+
List<AudioFocusInfo> focusHolders = mFocusZones.get(audioZoneId).getAudioFocusHolders();
mCarFocusCallback.onFocusChange(audioZoneId, focusHolders);
}
diff --git a/tests/carservice_test/src/com/android/car/audio/CarZonesAudioFocusTest.java b/tests/carservice_test/src/com/android/car/audio/CarZonesAudioFocusTest.java
index 54062cb..b5b7958 100644
--- a/tests/carservice_test/src/com/android/car/audio/CarZonesAudioFocusTest.java
+++ b/tests/carservice_test/src/com/android/car/audio/CarZonesAudioFocusTest.java
@@ -29,10 +29,10 @@
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.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 android.car.media.CarAudioManager;
import android.content.ContentResolver;
@@ -89,13 +89,60 @@
private CarAudioSettings mCarAudioSettings;
@Mock
private CarFocusCallback mMockCarFocusCallback;
+ @Mock
+ private PackageManager mMockPackageManager;
+
+ private SparseArray<CarAudioZone> mCarAudioZones;
@Before
public void setUp() {
+ mCarAudioZones = generateAudioZones();
when(mCarAudioService.getZoneIdForUid(MEDIA_CLIENT_UID_1)).thenReturn(PRIMARY_ZONE_ID);
}
@Test
+ public void constructor_withNullAudioManager_throws() {
+ assertThrows(NullPointerException.class, () -> new CarZonesAudioFocus(null,
+ mMockPackageManager, mCarAudioZones, mCarAudioSettings, true, mMockCarFocusCallback)
+ );
+ }
+
+ @Test
+ public void constructor_withNullPackageManager_throws() {
+ assertThrows(NullPointerException.class, () -> new CarZonesAudioFocus(mMockAudioManager,
+ null, mCarAudioZones, mCarAudioSettings, true, mMockCarFocusCallback)
+ );
+ }
+
+ @Test
+ public void constructor_withNullCarAudioZones_throws() {
+ assertThrows(NullPointerException.class, () -> new CarZonesAudioFocus(mMockAudioManager,
+ mMockPackageManager, null, mCarAudioSettings, true, mMockCarFocusCallback)
+ );
+ }
+
+ @Test
+ public void constructor_withEmptyCarAudioZones_throws() {
+ assertThrows(IllegalArgumentException.class, () -> new CarZonesAudioFocus(mMockAudioManager,
+ mMockPackageManager, new SparseArray<>(), mCarAudioSettings, true,
+ mMockCarFocusCallback)
+ );
+ }
+
+ @Test
+ public void constructor_withNullCarAudioSettings_throws() {
+ assertThrows(NullPointerException.class, () -> new CarZonesAudioFocus(mMockAudioManager,
+ mMockPackageManager, mCarAudioZones, null, true, mMockCarFocusCallback)
+ );
+ }
+
+ @Test
+ public void constructor_withNullCarFocusCallback_succeeds() {
+ new CarZonesAudioFocus(mMockAudioManager, mMockPackageManager, mCarAudioZones,
+ mCarAudioSettings, true, null);
+ }
+
+ @Test
public void onAudioFocusRequest_withNoCurrentFocusHolder_requestGranted() {
CarZonesAudioFocus carZonesAudioFocus = getCarZonesAudioFocus(false);
AudioFocusInfo audioFocusInfo = generateMediaRequestForPrimaryZone();
@@ -335,10 +382,8 @@
}
private CarZonesAudioFocus getCarZonesAudioFocus(boolean enableDelayedFocus) {
- SparseArray<CarAudioZone> zones = generateAudioZones();
- PackageManager mockPackageManager = mock(PackageManager.class);
CarZonesAudioFocus carZonesAudioFocus =
- new CarZonesAudioFocus(mMockAudioManager, mockPackageManager, zones,
+ new CarZonesAudioFocus(mMockAudioManager, mMockPackageManager, mCarAudioZones,
mCarAudioSettings, enableDelayedFocus, mMockCarFocusCallback);
carZonesAudioFocus.setOwningPolicy(mCarAudioService, mAudioPolicy);
diff --git a/tests/carservice_unit_test/src/com/android/car/audio/CarDuckingTest.java b/tests/carservice_unit_test/src/com/android/car/audio/CarDuckingTest.java
index 25becf8..5fa1bfd 100644
--- a/tests/carservice_unit_test/src/com/android/car/audio/CarDuckingTest.java
+++ b/tests/carservice_unit_test/src/com/android/car/audio/CarDuckingTest.java
@@ -26,20 +26,27 @@
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import android.media.AudioAttributes;
import android.media.AudioFocusInfo;
import android.util.SparseArray;
-import androidx.test.ext.junit.runners.AndroidJUnit4;
+import com.android.car.audio.hal.AudioControlWrapper;
+import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnitRunner;
import java.util.List;
-@RunWith(AndroidJUnit4.class)
+@RunWith(MockitoJUnitRunner.class)
public final class CarDuckingTest {
private static final int PRIMARY_ZONE_ID = 0;
private static final int PASSENGER_ZONE_ID = 1;
@@ -49,12 +56,26 @@
private static final String REAR_MEDIA_ADDRESS = "rear_media";
private final SparseArray<CarAudioZone> mCarAudioZones = generateZoneMocks();
+ private final AudioFocusInfo mMediaFocusInfo = generateAudioFocusInfoForUsage(USAGE_MEDIA);
+ private final AudioFocusInfo mNavigationFocusInfo =
+ generateAudioFocusInfoForUsage(USAGE_ASSISTANCE_NAVIGATION_GUIDANCE);
+
+ @Mock
+ private AudioControlWrapper mMockAudioControlWrapper;
+
+ @Captor
+ private ArgumentCaptor<CarDuckingInfo> mCarDuckingInfoCaptor;
+
+ private CarDucking mCarDucking;
+
+ @Before
+ public void setUp() {
+ mCarDucking = new CarDucking(mCarAudioZones, mMockAudioControlWrapper);
+ }
@Test
public void constructor_initializesEmptyDuckingInfoForZones() {
- CarDucking carDucking = new CarDucking(mCarAudioZones);
-
- SparseArray<CarDuckingInfo> currentDuckingInfo = carDucking.getCurrentDuckingInfo();
+ SparseArray<CarDuckingInfo> currentDuckingInfo = mCarDucking.getCurrentDuckingInfo();
assertThat(currentDuckingInfo.size()).isEqualTo(mCarAudioZones.size());
for (int i = 0; i < mCarAudioZones.size(); i++) {
@@ -69,60 +90,87 @@
@Test
public void onFocusChange_forPrimaryZone_updatesUsagesHoldingFocus() {
- CarDucking carDucking = new CarDucking(mCarAudioZones);
+ List<AudioFocusInfo> focusHolders = List.of(mMediaFocusInfo);
- List<AudioFocusInfo> focusHolders = List.of(generateAudioFocusInfoForUsage(USAGE_MEDIA));
+ mCarDucking.onFocusChange(PRIMARY_ZONE_ID, focusHolders);
- carDucking.onFocusChange(PRIMARY_ZONE_ID, focusHolders);
- SparseArray<CarDuckingInfo> newDuckingInfo = carDucking.getCurrentDuckingInfo();
-
+ SparseArray<CarDuckingInfo> newDuckingInfo = mCarDucking.getCurrentDuckingInfo();
assertThat(newDuckingInfo.get(PRIMARY_ZONE_ID).mUsagesHoldingFocus)
.asList().containsExactly(USAGE_MEDIA);
}
@Test
public void onFocusChange_forPrimaryZone_doesNotUpdateSecondaryZones() {
- CarDucking carDucking = new CarDucking(mCarAudioZones);
+ List<AudioFocusInfo> focusHolders = List.of(mMediaFocusInfo);
- List<AudioFocusInfo> focusHolders = List.of(generateAudioFocusInfoForUsage(USAGE_MEDIA));
+ mCarDucking.onFocusChange(PRIMARY_ZONE_ID, focusHolders);
- carDucking.onFocusChange(PRIMARY_ZONE_ID, focusHolders);
- SparseArray<CarDuckingInfo> newDuckingInfo = carDucking.getCurrentDuckingInfo();
-
+ SparseArray<CarDuckingInfo> newDuckingInfo = mCarDucking.getCurrentDuckingInfo();
assertThat(newDuckingInfo.get(PASSENGER_ZONE_ID).mUsagesHoldingFocus).isEmpty();
assertThat(newDuckingInfo.get(REAR_ZONE_ID).mUsagesHoldingFocus).isEmpty();
}
@Test
- public void onFocusChange_withMultipleFocusHolders_updatesUsagesToDuck() {
- CarDucking carDucking = new CarDucking(mCarAudioZones);
+ public void onFocusChange_withMultipleFocusHolders_updatesAddressesToDuck() {
+ List<AudioFocusInfo> focusHolders = List.of(mMediaFocusInfo, mNavigationFocusInfo);
- List<AudioFocusInfo> focusHolders = List.of(generateAudioFocusInfoForUsage(USAGE_MEDIA),
- generateAudioFocusInfoForUsage(USAGE_ASSISTANCE_NAVIGATION_GUIDANCE));
+ mCarDucking.onFocusChange(PRIMARY_ZONE_ID, focusHolders);
- carDucking.onFocusChange(PRIMARY_ZONE_ID, focusHolders);
- SparseArray<CarDuckingInfo> newDuckingInfo = carDucking.getCurrentDuckingInfo();
-
+ SparseArray<CarDuckingInfo> newDuckingInfo = mCarDucking.getCurrentDuckingInfo();
assertThat(newDuckingInfo.get(PRIMARY_ZONE_ID).mAddressesToDuck)
.containsExactly(PRIMARY_MEDIA_ADDRESS);
}
@Test
- public void onFocusChange_withDuckedDevices_updatesUsagesToUnduck() {
- CarDucking carDucking = new CarDucking(mCarAudioZones);
+ public void onFocusChange_withDuckedDevices_updatesAddressesToUnduck() {
+ List<AudioFocusInfo> focusHolders = List.of(mMediaFocusInfo, mNavigationFocusInfo);
+ mCarDucking.onFocusChange(PRIMARY_ZONE_ID, focusHolders);
- List<AudioFocusInfo> focusHolders = List.of(generateAudioFocusInfoForUsage(USAGE_MEDIA),
- generateAudioFocusInfoForUsage(USAGE_ASSISTANCE_NAVIGATION_GUIDANCE));
- carDucking.onFocusChange(PRIMARY_ZONE_ID, focusHolders);
+ List<AudioFocusInfo> updatedHolders = List.of(mMediaFocusInfo);
+ mCarDucking.onFocusChange(PRIMARY_ZONE_ID, updatedHolders);
- List<AudioFocusInfo> updatedHolders = List.of(generateAudioFocusInfoForUsage(USAGE_MEDIA));
- carDucking.onFocusChange(PRIMARY_ZONE_ID, updatedHolders);
- SparseArray<CarDuckingInfo> newDuckingInfo = carDucking.getCurrentDuckingInfo();
-
+ SparseArray<CarDuckingInfo> newDuckingInfo = mCarDucking.getCurrentDuckingInfo();
assertThat(newDuckingInfo.get(PRIMARY_ZONE_ID).mAddressesToUnduck)
.containsExactly(PRIMARY_MEDIA_ADDRESS);
}
+ @Test
+ public void onFocusChange_notifiesHalOfUsagesHoldingFocus() {
+ List<AudioFocusInfo> focusHolders = List.of(mMediaFocusInfo);
+
+ mCarDucking.onFocusChange(PRIMARY_ZONE_ID, focusHolders);
+
+ ArgumentCaptor<CarDuckingInfo> captor = ArgumentCaptor.forClass(CarDuckingInfo.class);
+ verify(mMockAudioControlWrapper).onDevicesToDuckChange(captor.capture());
+ int[] usagesHoldingFocus = captor.getValue().mUsagesHoldingFocus;
+ assertThat(usagesHoldingFocus).asList().containsExactly(USAGE_MEDIA);
+ }
+
+ @Test
+ public void onFocusChange_notifiesHalOfAddressesToDuck() {
+ List<AudioFocusInfo> focusHolders = List.of(mMediaFocusInfo, mNavigationFocusInfo);
+
+ mCarDucking.onFocusChange(PRIMARY_ZONE_ID, focusHolders);
+
+ verify(mMockAudioControlWrapper).onDevicesToDuckChange(mCarDuckingInfoCaptor.capture());
+ List<String> addressesToDuck = mCarDuckingInfoCaptor.getValue().mAddressesToDuck;
+ assertThat(addressesToDuck).containsExactly(PRIMARY_MEDIA_ADDRESS);
+ }
+
+ @Test
+ public void onFocusChange_notifiesHalOfAddressesToUnduck() {
+ List<AudioFocusInfo> focusHolders = List.of(mMediaFocusInfo, mNavigationFocusInfo);
+ mCarDucking.onFocusChange(PRIMARY_ZONE_ID, focusHolders);
+
+ List<AudioFocusInfo> updatedHolders = List.of(mMediaFocusInfo);
+ mCarDucking.onFocusChange(PRIMARY_ZONE_ID, updatedHolders);
+
+ verify(mMockAudioControlWrapper, times(2))
+ .onDevicesToDuckChange(mCarDuckingInfoCaptor.capture());
+ List<String> addressesToUnduck = mCarDuckingInfoCaptor.getValue().mAddressesToUnduck;
+ assertThat(addressesToUnduck).containsExactly(PRIMARY_MEDIA_ADDRESS);
+ }
+
private AudioFocusInfo generateAudioFocusInfoForUsage(int usage) {
AudioAttributes attributes = new AudioAttributes.Builder().setUsage(usage).build();
return new AudioFocusInfo(attributes, 0, "client_id", "package.name",