Merge "Add core volume group call to set preferred device for strategy" into main
diff --git a/service/src/com/android/car/audio/CarAudioZone.java b/service/src/com/android/car/audio/CarAudioZone.java
index baefea9..f035042 100644
--- a/service/src/com/android/car/audio/CarAudioZone.java
+++ b/service/src/com/android/car/audio/CarAudioZone.java
@@ -225,6 +225,7 @@
             mCurrentConfigId = configInfoSwitchedTo.getConfigId();
             CarAudioZoneConfig current = mCarAudioZoneConfigs.get(mCurrentConfigId);
             current.setIsSelected(true);
+            current.updateVolumeDevices(mCarAudioContext.useCoreAudioRouting());
         }
     }
 
@@ -241,6 +242,7 @@
                 mCurrentConfigId = config.getZoneConfigId();
             }
             config.setIsSelected(true);
+            config.updateVolumeDevices(mCarAudioContext.useCoreAudioRouting());
         }
     }
 
diff --git a/service/src/com/android/car/audio/CarAudioZoneConfig.java b/service/src/com/android/car/audio/CarAudioZoneConfig.java
index 2c85c73..9c2ed6c 100644
--- a/service/src/com/android/car/audio/CarAudioZoneConfig.java
+++ b/service/src/com/android/car/audio/CarAudioZoneConfig.java
@@ -499,6 +499,12 @@
         return updated;
     }
 
+    void updateVolumeDevices(boolean useCoreAudioRouting) {
+        for (int c = 0; c < mVolumeGroups.size(); c++) {
+            mVolumeGroups.get(c).updateDevices(useCoreAudioRouting);
+        }
+    }
+
     static final class Builder {
         private final int mZoneId;
         private final int mZoneConfigId;
diff --git a/service/src/com/android/car/audio/CarVolumeGroup.java b/service/src/com/android/car/audio/CarVolumeGroup.java
index 79bdf94..de0d225 100644
--- a/service/src/com/android/car/audio/CarVolumeGroup.java
+++ b/service/src/com/android/car/audio/CarVolumeGroup.java
@@ -346,6 +346,10 @@
         return carAudioContexts;
     }
 
+    protected AudioAttributes[] getAudioAttributesForContext(int context) {
+        return mCarAudioContext.getAudioAttributesForContext(context);
+    }
+
     /**
      * Returns the id of the volume group.
      * <p> Note that all clients are already developed in the way that when they get the number of
@@ -920,6 +924,9 @@
         }
     }
 
+    void updateDevices(boolean useCoreAudioRouting) {
+    }
+
     /**
      * Calculates the new gain stages from list of assigned audio device infos
      *
diff --git a/service/src/com/android/car/audio/CoreAudioHelper.java b/service/src/com/android/car/audio/CoreAudioHelper.java
index 9302d35..11a76a4 100644
--- a/service/src/com/android/car/audio/CoreAudioHelper.java
+++ b/service/src/com/android/car/audio/CoreAudioHelper.java
@@ -102,7 +102,7 @@
      * given {@link AudioAttributes} if found, {@link #INVALID_STRATEGY} id otherwise.
      */
     public static int getStrategyForAudioAttributes(AudioAttributes attributes) {
-        Preconditions.checkNotNull(attributes, "Audio Attributes must not be null");
+        Preconditions.checkNotNull(attributes, "Audio Attributes can not be null");
         for (int index = 0; index < getAudioProductStrategies().size(); index++) {
             AudioProductStrategy strategy = getAudioProductStrategies().get(index);
             if (strategy.supportsAudioAttributes(attributes)) {
@@ -113,7 +113,7 @@
     }
 
     public static int getStrategyForContextName(String contextName) {
-        Preconditions.checkNotNull(contextName, "Context name must not be null");
+        Preconditions.checkNotNull(contextName, "Context name can not be null");
         for (int index = 0; index < getAudioProductStrategies().size(); index++) {
             AudioProductStrategy strategy = getAudioProductStrategies().get(index);
             if (Objects.equals(strategy.getName(), contextName)) {
@@ -139,6 +139,20 @@
                 ? getStrategyForAudioAttributes(DEFAULT_ATTRIBUTES) : strategyId;
     }
 
+    @Nullable
+    static AudioProductStrategy getProductStrategyForAudioAttributes(
+            AudioAttributes attributes) {
+        Preconditions.checkNotNull(attributes, "Audio attributes can not be null");
+        for (int index = 0; index < getAudioProductStrategies().size(); index++) {
+            AudioProductStrategy strategy = getAudioProductStrategies().get(index);
+            if (!strategy.supportsAudioAttributes(attributes)) {
+                continue;
+            }
+            return strategy;
+        }
+        return null;
+    }
+
     /**
      * Gets the {@link AudioProductStrategy} referred by its unique identifier.
      *
@@ -236,7 +250,7 @@
      */
     @Nullable
     public static String getVolumeGroupNameForAudioAttributes(AudioAttributes attributes) {
-        Preconditions.checkNotNull(attributes, "Audio Attributes must not be null");
+        Preconditions.checkNotNull(attributes, "Audio Attributes can not be null");
         int volumeGroupId = getVolumeGroupIdForAudioAttributes(attributes);
         return volumeGroupId != AudioVolumeGroup.DEFAULT_VOLUME_GROUP
                 ? getVolumeGroupNameFromCoreId(volumeGroupId) : null;
@@ -261,7 +275,7 @@
      * if found, {@link #INVALID_GROUP_ID} otherwise.
      */
     public static int getVolumeGroupIdForAudioAttributes(AudioAttributes attributes) {
-        Preconditions.checkNotNull(attributes, "Audio Attributes must not be null");
+        Preconditions.checkNotNull(attributes, "Audio Attributes can not be null");
         for (int index = 0; index < getAudioProductStrategies().size(); index++) {
             AudioProductStrategy strategy = getAudioProductStrategies().get(index);
             int volumeGroupId =
diff --git a/service/src/com/android/car/audio/CoreAudioVolumeGroup.java b/service/src/com/android/car/audio/CoreAudioVolumeGroup.java
index 7e41886..fea621f 100644
--- a/service/src/com/android/car/audio/CoreAudioVolumeGroup.java
+++ b/service/src/com/android/car/audio/CoreAudioVolumeGroup.java
@@ -20,18 +20,25 @@
 import static android.car.media.CarVolumeGroupEvent.EVENT_TYPE_VOLUME_GAIN_INDEX_CHANGED;
 
 import static com.android.car.CarLog.TAG_AUDIO;
+import static com.android.car.audio.CoreAudioHelper.getProductStrategyForAudioAttributes;
 import static com.android.car.internal.ExcludeFromCodeCoverageGeneratedReport.DUMP_INFO;
 
 import android.car.builtin.media.AudioManagerHelper;
 import android.car.builtin.util.Slogf;
 import android.media.AudioAttributes;
+import android.media.AudioDeviceAttributes;
 import android.media.AudioManager;
+import android.media.audiopolicy.AudioProductStrategy;
+import android.util.ArraySet;
 import android.util.SparseArray;
 
 import com.android.car.internal.ExcludeFromCodeCoverageGeneratedReport;
 import com.android.car.internal.util.IndentingPrintWriter;
 import com.android.internal.annotations.GuardedBy;
 
+import java.util.Arrays;
+import java.util.List;
+
 /**
  * A class encapsulates a volume group in car.
  *
@@ -283,6 +290,41 @@
     }
 
     @Override
+    void updateDevices(boolean useCoreAudioRouting) {
+        // If not using core audio routing, than device need to be updated to match the information
+        // for audio attributes to core volume groups.
+        if (useCoreAudioRouting) {
+            return;
+        }
+
+        int[] contexts = getContexts();
+        for (int c = 0; c < contexts.length; c++) {
+            int context = contexts[c];
+            AudioAttributes[] audioAttributes = getAudioAttributesForContext(context);
+            AudioDeviceAttributes device = getAudioDeviceForContext(context);
+            setPreferredDeviceForAudioAttribute(Arrays.asList(audioAttributes), device);
+        }
+
+    }
+
+    private void setPreferredDeviceForAudioAttribute(List<AudioAttributes> audioAttributes,
+            AudioDeviceAttributes audioDeviceAttributes) {
+        ArraySet<Integer> strategiesSet = new ArraySet<>();
+        for (int c = 0; c < audioAttributes.size(); c++) {
+            AudioProductStrategy strategy =
+                    getProductStrategyForAudioAttributes(audioAttributes.get(c));
+            if (strategy == null) {
+                continue;
+            }
+            if (!strategiesSet.add(strategy.getId())) {
+                continue;
+            }
+            mAudioManager.setPreferredDeviceForStrategy(strategy, audioDeviceAttributes);
+        }
+    }
+
+
+    @Override
     protected int getDefaultGainIndex() {
         return mDefaultGainIndex;
     }
diff --git a/tests/carservice_unit_test/src/com/android/car/audio/CarAudioZoneConfigUnitTest.java b/tests/carservice_unit_test/src/com/android/car/audio/CarAudioZoneConfigUnitTest.java
index 8eb88db..9f6b808 100644
--- a/tests/carservice_unit_test/src/com/android/car/audio/CarAudioZoneConfigUnitTest.java
+++ b/tests/carservice_unit_test/src/com/android/car/audio/CarAudioZoneConfigUnitTest.java
@@ -282,6 +282,30 @@
     }
 
     @Test
+    public void updateVolumeDevices_withUseCoreAudioRoutingEnabled() {
+        CarAudioZoneConfig zoneConfig = mTestAudioZoneConfigBuilder.addVolumeGroup(mMockMusicGroup)
+                .addVolumeGroup(mMockNavGroup).build();
+        boolean useCoreAudioRouting = true;
+
+        zoneConfig.updateVolumeDevices(useCoreAudioRouting);
+
+        verify(mMockMusicGroup).updateDevices(useCoreAudioRouting);
+        verify(mMockNavGroup).updateDevices(useCoreAudioRouting);
+    }
+
+    @Test
+    public void updateVolumeDevices_withUseCoreAudioRoutingDisabled() {
+        CarAudioZoneConfig zoneConfig = mTestAudioZoneConfigBuilder.addVolumeGroup(mMockMusicGroup)
+                .addVolumeGroup(mMockNavGroup).build();
+        boolean useCoreAudioRouting = false;
+
+        zoneConfig.updateVolumeDevices(useCoreAudioRouting);
+
+        verify(mMockMusicGroup).updateDevices(useCoreAudioRouting);
+        verify(mMockNavGroup).updateDevices(useCoreAudioRouting);
+    }
+
+    @Test
     public void validateCanUseDynamicMixRouting_addressSharedAmongGroups_forbidUseDynamicRouting() {
         CarAudioDeviceInfo musicCarAudioDeviceInfo = Mockito.mock(CarAudioDeviceInfo.class);
         CarVolumeGroup mockMusicGroup = new VolumeGroupBuilder()
diff --git a/tests/carservice_unit_test/src/com/android/car/audio/CarAudioZoneUnitTest.java b/tests/carservice_unit_test/src/com/android/car/audio/CarAudioZoneUnitTest.java
index eb53ca1..13cc386 100644
--- a/tests/carservice_unit_test/src/com/android/car/audio/CarAudioZoneUnitTest.java
+++ b/tests/carservice_unit_test/src/com/android/car/audio/CarAudioZoneUnitTest.java
@@ -260,7 +260,7 @@
     public void isCurrentZoneConfig_forCurrentConfig_returnsFalse() {
         mTestAudioZone.addZoneConfig(mMockZoneConfig0);
         mTestAudioZone.addZoneConfig(mMockZoneConfig1);
-        CarAudioZoneConfigInfo nonCurrentZoneConfigInfo = getNonCurrentZoneConfigInfo();
+        CarAudioZoneConfigInfo nonCurrentZoneConfigInfo = getFirstNonCurrentZoneConfigInfo();
 
         expectWithMessage("Non-current zone config info")
                 .that(mTestAudioZone.isCurrentZoneConfig(nonCurrentZoneConfigInfo))
@@ -268,10 +268,10 @@
     }
 
     @Test
-    public void setCurrentCarZoneConfig() {
+    public void setCurrentCarZoneConfig_withCoreAudioRoutingDisabled() {
         mTestAudioZone.addZoneConfig(mMockZoneConfig0);
         mTestAudioZone.addZoneConfig(mMockZoneConfig1);
-        CarAudioZoneConfigInfo currentZoneConfigInfoToSwitch = getNonCurrentZoneConfigInfo();
+        CarAudioZoneConfigInfo currentZoneConfigInfoToSwitch = getFirstNonCurrentZoneConfigInfo();
 
         mTestAudioZone.setCurrentCarZoneConfig(currentZoneConfigInfoToSwitch);
 
@@ -279,6 +279,29 @@
                 .that(mTestAudioZone.isCurrentZoneConfig(currentZoneConfigInfoToSwitch))
                 .isTrue();
         verify(mMockZoneConfig1).setIsSelected(true);
+        verify(mMockZoneConfig1).updateVolumeDevices(/* useCoreAudioRouting= */ false);
+        verify(mMockZoneConfig0).setIsSelected(false);
+    }
+
+    @Test
+    public void setCurrentCarZoneConfig_withCoreAudioRoutingEnabled() {
+        CarAudioContext contextWithCoreAudioRouting =
+                new CarAudioContext(CarAudioContext.getAllContextsInfo(),
+                        /* useCoreAudioRouting= */ true);
+        CarAudioZone testAudioZone = new CarAudioZone(contextWithCoreAudioRouting, TEST_ZONE_NAME,
+                TEST_ZONE_ID);
+        testAudioZone.addZoneConfig(mMockZoneConfig0);
+        testAudioZone.addZoneConfig(mMockZoneConfig1);
+        CarAudioZoneConfigInfo currentZoneConfigInfoToSwitch =
+                getFirstNonCurrentZoneConfigInfo(testAudioZone);
+
+        testAudioZone.setCurrentCarZoneConfig(currentZoneConfigInfoToSwitch);
+
+        expectWithMessage("Current zone config info after switching zone configuration"
+                + "with core audio routing")
+                .that(testAudioZone.isCurrentZoneConfig(currentZoneConfigInfoToSwitch)).isTrue();
+        verify(mMockZoneConfig1).setIsSelected(true);
+        verify(mMockZoneConfig1).updateVolumeDevices(/* useCoreAudioRouting= */ true);
         verify(mMockZoneConfig0).setIsSelected(false);
     }
 
@@ -819,11 +842,10 @@
                 .hasMessageThat().contains("Audio devices");
     }
 
-    private CarAudioZoneConfigInfo getNonCurrentZoneConfigInfo() {
-        CarAudioZoneConfigInfo currentZoneConfigInfo = mTestAudioZone
-                .getCurrentCarAudioZoneConfig().getCarAudioZoneConfigInfo();
-        List<CarAudioZoneConfigInfo> zoneConfigInfoList = mTestAudioZone
-                .getCarAudioZoneConfigInfos();
+    private CarAudioZoneConfigInfo getFirstNonCurrentZoneConfigInfo(CarAudioZone audioZone) {
+        CarAudioZoneConfigInfo currentZoneConfigInfo = audioZone.getCurrentCarAudioZoneConfig()
+                .getCarAudioZoneConfigInfo();
+        List<CarAudioZoneConfigInfo> zoneConfigInfoList = audioZone.getCarAudioZoneConfigInfos();
         for (int index = 0; index < zoneConfigInfoList.size(); index++) {
             CarAudioZoneConfigInfo zoneConfigInfo = zoneConfigInfoList.get(index);
             if (!currentZoneConfigInfo.equals(zoneConfigInfo)) {
@@ -833,6 +855,10 @@
         return null;
     }
 
+    private CarAudioZoneConfigInfo getFirstNonCurrentZoneConfigInfo() {
+        return getFirstNonCurrentZoneConfigInfo(mTestAudioZone);
+    }
+
     private static final class TestCarAudioZoneConfigBuilder {
         private static final int INVALID_GROUP_ID = -1;
         private static final int INVALID_EVENT_TYPE = 0;
diff --git a/tests/carservice_unit_test/src/com/android/car/audio/CoreAudioHelperTest.java b/tests/carservice_unit_test/src/com/android/car/audio/CoreAudioHelperTest.java
index 3fa9ab2..6b329b7 100644
--- a/tests/carservice_unit_test/src/com/android/car/audio/CoreAudioHelperTest.java
+++ b/tests/carservice_unit_test/src/com/android/car/audio/CoreAudioHelperTest.java
@@ -45,6 +45,8 @@
 import static com.android.car.audio.CoreAudioRoutingUtils.UNSUPPORTED_ATTRIBUTES;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
 
+import static org.junit.Assert.assertThrows;
+
 import android.car.test.mocks.AbstractExtendedMockitoTestCase;
 import android.media.AudioManager;
 import android.media.audiopolicy.AudioProductStrategy;
@@ -123,6 +125,35 @@
     }
 
     @Test
+    public void getProductStrategyForAudioAttributes_withValidAttributes_succeeds() {
+        expectWithMessage("Music product strategy")
+                .that(CoreAudioHelper.getProductStrategyForAudioAttributes(MUSIC_ATTRIBUTES))
+                .isEqualTo(MUSIC_STRATEGY);
+        expectWithMessage("Navigation product strategy")
+                .that(CoreAudioHelper.getProductStrategyForAudioAttributes(NAV_ATTRIBUTES))
+                .isEqualTo(NAV_STRATEGY);
+        expectWithMessage("OEM product strategy")
+                .that(CoreAudioHelper.getProductStrategyForAudioAttributes(OEM_ATTRIBUTES))
+                .isEqualTo(OEM_STRATEGY);
+    }
+
+    @Test
+    public void getProductStrategyForAudioAttributes_withInvalidAttributes_returnsNull() {
+        expectWithMessage("Null product strategy for invalid audio attribute")
+                .that(CoreAudioHelper.getProductStrategyForAudioAttributes(UNSUPPORTED_ATTRIBUTES))
+                .isNull();
+    }
+
+    @Test
+    public void getProductStrategyForAudioAttributes_withNullAttributes_fails() {
+        NullPointerException exception = assertThrows(NullPointerException.class, () ->
+                CoreAudioHelper.getProductStrategyForAudioAttributes(null));
+
+        expectWithMessage("Null audio attributes exception").that(exception).hasMessageThat()
+                .contains("Audio attributes");
+    }
+
+    @Test
     public void getStrategyForContextName_succeeds() {
         expectWithMessage("Music strategy for context name (%s)", MUSIC_CONTEXT_NAME)
                 .that(CoreAudioHelper.getStrategyForContextName(MUSIC_CONTEXT_NAME))
diff --git a/tests/carservice_unit_test/src/com/android/car/audio/CoreAudioVolumeGroupTest.java b/tests/carservice_unit_test/src/com/android/car/audio/CoreAudioVolumeGroupTest.java
index 403337b..da34646 100644
--- a/tests/carservice_unit_test/src/com/android/car/audio/CoreAudioVolumeGroupTest.java
+++ b/tests/carservice_unit_test/src/com/android/car/audio/CoreAudioVolumeGroupTest.java
@@ -23,6 +23,7 @@
 import static android.car.test.mocks.AndroidMockitoHelper.mockCarGetPlatformVersion;
 
 import static com.android.car.audio.CoreAudioRoutingUtils.MEDIA_CONTEXT_INFO;
+import static com.android.car.audio.CoreAudioRoutingUtils.MOVIE_ATTRIBUTES;
 import static com.android.car.audio.CoreAudioRoutingUtils.MUSIC_AM_INIT_INDEX;
 import static com.android.car.audio.CoreAudioRoutingUtils.MUSIC_ATTRIBUTES;
 import static com.android.car.audio.CoreAudioRoutingUtils.MUSIC_CAR_GROUP_ID;
@@ -31,6 +32,7 @@
 import static com.android.car.audio.CoreAudioRoutingUtils.MUSIC_GROUP_NAME;
 import static com.android.car.audio.CoreAudioRoutingUtils.MUSIC_MAX_INDEX;
 import static com.android.car.audio.CoreAudioRoutingUtils.MUSIC_MIN_INDEX;
+import static com.android.car.audio.CoreAudioRoutingUtils.MUSIC_STRATEGY;
 import static com.android.car.audio.CoreAudioRoutingUtils.MUSIC_STRATEGY_ID;
 import static com.android.car.audio.CoreAudioRoutingUtils.NAV_ATTRIBUTES;
 import static com.android.car.audio.CoreAudioRoutingUtils.NAV_CAR_GROUP_ID;
@@ -57,11 +59,14 @@
 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.when;
 
 import android.car.Car;
 import android.car.builtin.media.AudioManagerHelper;
 import android.car.test.mocks.AbstractExtendedMockitoTestCase;
+import android.media.AudioDeviceAttributes;
+import android.media.AudioDeviceInfo;
 import android.media.AudioManager;
 import android.util.SparseArray;
 
@@ -93,6 +98,7 @@
     private CoreAudioVolumeGroup mMusicCoreAudioVolumeGroup;
     private CoreAudioVolumeGroup mNavCoreAudioVolumeGroup;
     private CoreAudioVolumeGroup mOemCoreAudioVolumeGroup;
+    private AudioDeviceAttributes mMusicDeviceAttributes;
 
     public CoreAudioVolumeGroupTest() {
         super(CoreAudioVolumeGroup.TAG);
@@ -107,8 +113,11 @@
     void setupMock() {
         doReturn(MUSIC_GROUP_ID)
                 .when(() -> CoreAudioHelper.getVolumeGroupIdForAudioAttributes(MUSIC_ATTRIBUTES));
-        doReturn(MUSIC_ATTRIBUTES)
-                .when(() ->
+        doReturn(MUSIC_STRATEGY)
+                .when(() -> CoreAudioHelper.getProductStrategyForAudioAttributes(MUSIC_ATTRIBUTES));
+        doReturn(MUSIC_STRATEGY)
+                .when(() -> CoreAudioHelper.getProductStrategyForAudioAttributes(MOVIE_ATTRIBUTES));
+        doReturn(MUSIC_ATTRIBUTES).when(() ->
                         CoreAudioHelper.selectAttributesForVolumeGroupName(eq(MUSIC_GROUP_NAME)));
         when(mMockAudioManager.getMinVolumeIndexForAttributes(MUSIC_ATTRIBUTES))
                 .thenReturn(MUSIC_MIN_INDEX);
@@ -156,9 +165,12 @@
         mOemContext = new CarAudioContext(
                 List.of(OEM_CONTEXT_INFO), /* useCoreAudioRouting= */ true);
 
+        mMusicDeviceAttributes = new AudioDeviceAttributes(AudioDeviceInfo.TYPE_BLUETOOTH_A2DP,
+                MUSIC_DEVICE_ADDRESS);
         SparseArray<CarAudioDeviceInfo> musicContextToDeviceInfo = new SparseArray<>();
         musicContextToDeviceInfo.put(MUSIC_STRATEGY_ID, mOemInfoMock);
         when(mOemInfoMock.getAddress()).thenReturn(MUSIC_DEVICE_ADDRESS);
+        when(mOemInfoMock.getAudioDevice()).thenReturn(mMusicDeviceAttributes);
         mMusicCoreAudioVolumeGroup = new CoreAudioVolumeGroup(mMockAudioManager, mMusicContext,
                 mSettingsMock, musicContextToDeviceInfo, PRIMARY_AUDIO_ZONE, ZONE_CONFIG_ID,
                 MUSIC_CAR_GROUP_ID, MUSIC_GROUP_NAME, /* useCarVolumeGroupMute= */ false);
@@ -513,4 +525,24 @@
                 .that(mMusicCoreAudioVolumeGroup.getCurrentGainIndex())
                 .isEqualTo(MUSIC_AM_INIT_INDEX + 1);
     }
+
+    @Test
+    public void updateDevices_withCoreAudioRoutingDisabled() {
+        boolean useCoreAudioRouting = false;
+
+        mMusicCoreAudioVolumeGroup.updateDevices(useCoreAudioRouting);
+
+        verify(mMockAudioManager).setPreferredDeviceForStrategy(MUSIC_STRATEGY,
+                mMusicDeviceAttributes);
+    }
+
+    @Test
+    public void updateDevices_withCoreAudioRoutingEnabled() {
+        boolean useCoreAudioRouting = true;
+
+        mMusicCoreAudioVolumeGroup.updateDevices(useCoreAudioRouting);
+
+        verify(mMockAudioManager, never()).setPreferredDeviceForStrategy(MUSIC_STRATEGY,
+                mMusicDeviceAttributes);
+    }
 }