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",