Restricting muting on audio disable

- CarAudioService#setIsAudioEnabled now calls setRestrictMuting
- CarVolumeGroupMuting#setRestrictMuting tells the HAL to mute unless
it's a critical CarAudioContext

Bug: 176258537
Test: atest CarVolumeGroupMutingTest
Test: Disable audio and check if things are muted
`adb shell cmd car_service define-power-policy audio_off --disable
AUDIO`
`adb shell cmd car_service define-power-policy audio_on --enable
AUDIO`
    `adb shell cmd car_service apply-power-policy audio_off`
to disable audio. Then checked `adb shell dumpsys car_service
--services
    CarAudioService` to verify muting has been restricted

Change-Id: I62fdccbea7635c7f09d763c5f81cd1d48d4bb63a
diff --git a/service/src/com/android/car/audio/CarAudioService.java b/service/src/com/android/car/audio/CarAudioService.java
index 7d13401..1e81c94 100644
--- a/service/src/com/android/car/audio/CarAudioService.java
+++ b/service/src/com/android/car/audio/CarAudioService.java
@@ -1249,6 +1249,10 @@
         }
 
         mFocusHandler.setRestrictFocus(/* isFocusRestricted= */ !isAudioEnabled);
+        if (mUseCarVolumeGroupMuting) {
+            mCarVolumeGroupMuting.setRestrictMuting(/* isMutingRestricted= */ !isAudioEnabled);
+        }
+        // TODO(b/176258537) if not using group volume, then set master mute accordingly
     }
 
     private void enforcePermission(String permissionName) {
diff --git a/service/src/com/android/car/audio/CarVolumeGroupMuting.java b/service/src/com/android/car/audio/CarVolumeGroupMuting.java
index f6d2ce9..7101127 100644
--- a/service/src/com/android/car/audio/CarVolumeGroupMuting.java
+++ b/service/src/com/android/car/audio/CarVolumeGroupMuting.java
@@ -44,6 +44,8 @@
     private final Object mLock = new Object();
     @GuardedBy("mLock")
     private List<MutingInfo> mLastMutingInformation;
+    @GuardedBy("mLock")
+    private boolean mIsMutingRestricted;
 
     CarVolumeGroupMuting(@NonNull SparseArray<CarAudioZone> carAudioZones,
             @NonNull AudioControlWrapper audioControlWrapper) {
@@ -72,11 +74,26 @@
         if (Log.isLoggable(TAG, Log.DEBUG)) {
             Slog.d(TAG, "carMuteChanged");
         }
+
         List<MutingInfo> mutingInfo = generateMutingInfo();
         setLastMutingInfo(mutingInfo);
         mAudioControlWrapper.onDevicesToMuteChange(mutingInfo);
     }
 
+    public void setRestrictMuting(boolean isMutingRestricted) {
+        synchronized (mLock) {
+            mIsMutingRestricted = isMutingRestricted;
+        }
+
+        carMuteChanged();
+    }
+
+    private boolean isMutingRestricted() {
+        synchronized (mLock) {
+            return mIsMutingRestricted;
+        }
+    }
+
     private void setLastMutingInfo(List<MutingInfo> mutingInfo) {
         synchronized (mLock) {
             mLastMutingInformation = mutingInfo;
@@ -92,8 +109,11 @@
 
     private List<MutingInfo> generateMutingInfo() {
         List<MutingInfo> mutingInformation = new ArrayList<>(mCarAudioZones.size());
+
+        boolean isMutingRestricted = isMutingRestricted();
         for (int index = 0; index < mCarAudioZones.size(); index++) {
-            mutingInformation.add(generateMutingInfoFromZone(mCarAudioZones.valueAt(index)));
+            mutingInformation.add(generateMutingInfoFromZone(mCarAudioZones.valueAt(index),
+                    isMutingRestricted));
         }
 
         return mutingInformation;
@@ -106,6 +126,7 @@
         writer.println(TAG);
         writer.increaseIndent();
         synchronized (mLock) {
+            writer.printf("Is muting restricted? %b\n", mIsMutingRestricted);
             for (int index = 0; index < mLastMutingInformation.size(); index++) {
                 dumpCarMutingInfo(writer, mLastMutingInformation.get(index));
             }
@@ -134,7 +155,8 @@
     }
 
     @VisibleForTesting
-    static MutingInfo generateMutingInfoFromZone(CarAudioZone audioZone) {
+    static MutingInfo generateMutingInfoFromZone(CarAudioZone audioZone,
+            boolean isMutingRestricted) {
         MutingInfo mutingInfo = new MutingInfo();
         mutingInfo.zoneId = audioZone.getId();
 
@@ -144,11 +166,12 @@
 
         for (int groupIndex = 0; groupIndex < groups.length; groupIndex++) {
             CarVolumeGroup group = groups[groupIndex];
-            if (group.isMuted()) {
+
+            if (group.isMuted() || (isMutingRestricted && !group.hasCriticalAudioContexts())) {
                 mutedDevices.addAll(group.getAddresses());
-                continue;
+            } else {
+                unMutedDevices.addAll(group.getAddresses());
             }
-            unMutedDevices.addAll(group.getAddresses());
         }
 
         mutingInfo.deviceAddressesToMute = mutedDevices.toArray(new String[mutedDevices.size()]);
diff --git a/tests/carservice_unit_test/src/com/android/car/audio/CarVolumeGroupMutingTest.java b/tests/carservice_unit_test/src/com/android/car/audio/CarVolumeGroupMutingTest.java
index 18bbbea..5c04921 100644
--- a/tests/carservice_unit_test/src/com/android/car/audio/CarVolumeGroupMutingTest.java
+++ b/tests/carservice_unit_test/src/com/android/car/audio/CarVolumeGroupMutingTest.java
@@ -16,8 +16,10 @@
 
 package com.android.car.audio;
 
+import static com.android.car.audio.CarAudioContext.EMERGENCY;
 import static com.android.car.audio.CarAudioContext.MUSIC;
 import static com.android.car.audio.CarAudioContext.NAVIGATION;
+import static com.android.car.audio.CarAudioContext.SAFETY;
 import static com.android.car.audio.CarAudioContext.VOICE_COMMAND;
 
 import static com.google.common.truth.Truth.assertWithMessage;
@@ -48,8 +50,10 @@
     private static final String PRIMARY_MEDIA_ADDRESS = "media";
     private static final String PRIMARY_NAVIGATION_ADDRESS = "navigation";
     private static final String PRIMARY_VOICE_ADDRESS = "voice";
-    private static final String SECONDARY_ADDRESS = "media";
-    private static final String TERTIARY_ADDRESS = "media";
+    private static final String SECONDARY_ADDRESS = "secondary";
+    private static final String TERTIARY_ADDRESS = "tertiary";
+    private static final String EMERGENCY_ADDRESS = "emergency";
+    private static final String SAFETY_ADDRESS = "safety";
     private static final int PRIMARY_ZONE_ID = CarAudioManager.PRIMARY_AUDIO_ZONE;
     private static final int SECONDARY_ZONE_ID = CarAudioManager.PRIMARY_AUDIO_ZONE + 1;
     private static final int TERTIARY_ZONE_ID = CarAudioManager.PRIMARY_AUDIO_ZONE + 2;
@@ -70,21 +74,12 @@
 
     @Before
     public void setUp() {
-        mMusicCarVolumeGroup = new VolumeGroupBuilder()
-                .addDeviceAddressAndContexts(MUSIC, PRIMARY_MEDIA_ADDRESS)
-                .build();
-        mNavigationCarVolumeGroup = new VolumeGroupBuilder()
-                .addDeviceAddressAndContexts(NAVIGATION, PRIMARY_NAVIGATION_ADDRESS)
-                .build();
-        mVoiceCarVolumeGroup = new VolumeGroupBuilder()
-                .addDeviceAddressAndContexts(VOICE_COMMAND, PRIMARY_VOICE_ADDRESS)
-                .build();
-        mSecondaryZoneVolumeGroup = new VolumeGroupBuilder()
-                .addDeviceAddressAndContexts(MUSIC, SECONDARY_ADDRESS)
-                .build();
-        mTertiaryZoneVolumeGroup = new VolumeGroupBuilder()
-                .addDeviceAddressAndContexts(MUSIC, TERTIARY_ADDRESS)
-                .build();
+        mMusicCarVolumeGroup = groupWithContextAndAddress(MUSIC, PRIMARY_MEDIA_ADDRESS);
+        mNavigationCarVolumeGroup = groupWithContextAndAddress(NAVIGATION,
+                PRIMARY_NAVIGATION_ADDRESS);
+        mVoiceCarVolumeGroup = groupWithContextAndAddress(VOICE_COMMAND, PRIMARY_VOICE_ADDRESS);
+        mSecondaryZoneVolumeGroup = groupWithContextAndAddress(MUSIC, SECONDARY_ADDRESS);
+        mTertiaryZoneVolumeGroup = groupWithContextAndAddress(MUSIC, TERTIARY_ADDRESS);
 
         mPrimaryAudioZone =
                 new TestCarAudioZoneBuilder("Primary Zone", PRIMARY_ZONE_ID)
@@ -93,20 +88,14 @@
                         .addVolumeGroup(mVoiceCarVolumeGroup)
                         .build();
 
-        mSingleDevicePrimaryZone =
-                new TestCarAudioZoneBuilder("Primary Zone", PRIMARY_ZONE_ID)
-                        .addVolumeGroup(mMusicCarVolumeGroup)
-                        .build();
+        mSingleDevicePrimaryZone = createAudioZone(mMusicCarVolumeGroup, "Primary Zone",
+                PRIMARY_ZONE_ID);
 
-        mSingleDeviceSecondaryZone =
-                new TestCarAudioZoneBuilder("Secondary Zone", SECONDARY_ZONE_ID)
-                        .addVolumeGroup(mSecondaryZoneVolumeGroup)
-                        .build();
+        mSingleDeviceSecondaryZone = createAudioZone(mSecondaryZoneVolumeGroup, "Secondary Zone",
+                SECONDARY_ZONE_ID);
 
-        mSingleDeviceTertiaryZone =
-                new TestCarAudioZoneBuilder("Tertiary Zone", TERTIARY_ZONE_ID)
-                        .addVolumeGroup(mTertiaryZoneVolumeGroup)
-                        .build();
+        mSingleDeviceTertiaryZone = createAudioZone(mTertiaryZoneVolumeGroup, "Tertiary Zone",
+                TERTIARY_ZONE_ID);
 
         when(mMockAudioControlWrapper
                 .supportsFeature(AudioControlWrapper.AUDIOCONTROL_FEATURE_AUDIO_GROUP_MUTING))
@@ -234,7 +223,7 @@
 
         carGroupMuting.carMuteChanged();
 
-        List<MutingInfo> mutingInfo =  captureMutingInfoList();
+        List<MutingInfo> mutingInfo = captureMutingInfoList();
         MutingInfo info = mutingInfo.get(mutingInfo.size() - 1);
         assertWithMessage("Device addresses to un-mute")
                 .that(info.deviceAddressesToUnmute).asList().containsExactly(
@@ -331,6 +320,49 @@
         }
     }
 
+    @Test
+    public void setRestrictMuting_isMutingRestrictedTrue_mutesNonCriticalVolumeGroups() {
+        setUpCarVolumeGroupIsMuted(mSecondaryZoneVolumeGroup, false);
+        setUpCarVolumeGroupIsMuted(mMusicCarVolumeGroup, false);
+        setUpCarVolumeGroupIsMuted(mTertiaryZoneVolumeGroup, false);
+        CarVolumeGroupMuting carGroupMuting =
+                new CarVolumeGroupMuting(getAudioZones(mSingleDevicePrimaryZone,
+                        mSingleDeviceSecondaryZone, mSingleDeviceTertiaryZone),
+                        mMockAudioControlWrapper);
+
+        carGroupMuting.setRestrictMuting(true);
+
+        for (MutingInfo info : captureMutingInfoList()) {
+            assertWithMessage("Devices addresses to mute for zone %s", info.zoneId)
+                    .that(info.deviceAddressesToMute).asList().hasSize(1);
+        }
+    }
+
+    @Test
+    public void setRestrictMuting_isMutingRestrictedTrue_leavesCriticalGroupsAsIs() {
+        setUpCarVolumeGroupIsMuted(mMusicCarVolumeGroup, false);
+        setUpCarVolumeGroupHasCriticalAudioContexts(mMusicCarVolumeGroup);
+        setUpCarVolumeGroupIsMuted(mSecondaryZoneVolumeGroup, true);
+        setUpCarVolumeGroupHasCriticalAudioContexts(mSecondaryZoneVolumeGroup);
+        CarVolumeGroupMuting carGroupMuting = new CarVolumeGroupMuting(
+                getAudioZones(mSingleDevicePrimaryZone, mSingleDeviceSecondaryZone),
+                mMockAudioControlWrapper);
+
+        carGroupMuting.setRestrictMuting(true);
+
+        for (MutingInfo info : captureMutingInfoList()) {
+            if (info.zoneId == PRIMARY_ZONE_ID) {
+                assertWithMessage("Devices addresses to unmute for zone %s", info.zoneId)
+                        .that(info.deviceAddressesToUnmute).asList().containsExactly(
+                        PRIMARY_MEDIA_ADDRESS);
+
+            } else if (info.zoneId == SECONDARY_ZONE_ID) {
+                assertWithMessage("Devices addresses to mute for zone %s", info.zoneId)
+                        .that(info.deviceAddressesToMute).asList().containsExactly(
+                                SECONDARY_ADDRESS);
+            }
+        }
+    }
 
     @Test
     public void generateMutingInfoFromZone_withNoGroupsMuted_returnsEmptyMutedList() {
@@ -338,7 +370,8 @@
         setUpCarVolumeGroupIsMuted(mNavigationCarVolumeGroup, false);
         setUpCarVolumeGroupIsMuted(mVoiceCarVolumeGroup, false);
 
-        MutingInfo info = CarVolumeGroupMuting.generateMutingInfoFromZone(mPrimaryAudioZone);
+        MutingInfo info = CarVolumeGroupMuting.generateMutingInfoFromZone(mPrimaryAudioZone,
+                /* isMutingRestricted= */ false);
 
         assertWithMessage("Device addresses to mute")
                 .that(info.deviceAddressesToMute).asList().isEmpty();
@@ -350,7 +383,8 @@
         setUpCarVolumeGroupIsMuted(mNavigationCarVolumeGroup, false);
         setUpCarVolumeGroupIsMuted(mVoiceCarVolumeGroup, false);
 
-        MutingInfo info = CarVolumeGroupMuting.generateMutingInfoFromZone(mPrimaryAudioZone);
+        MutingInfo info = CarVolumeGroupMuting.generateMutingInfoFromZone(mPrimaryAudioZone,
+                /* isMutingRestricted= */ false);
 
         assertWithMessage("Device addresses to mute")
                 .that(info.deviceAddressesToMute).asList().containsExactly(PRIMARY_MEDIA_ADDRESS);
@@ -362,7 +396,8 @@
         setUpCarVolumeGroupIsMuted(mNavigationCarVolumeGroup, true);
         setUpCarVolumeGroupIsMuted(mVoiceCarVolumeGroup, true);
 
-        MutingInfo info = CarVolumeGroupMuting.generateMutingInfoFromZone(mPrimaryAudioZone);
+        MutingInfo info = CarVolumeGroupMuting.generateMutingInfoFromZone(mPrimaryAudioZone,
+                /* isMutingRestricted= */ false);
 
         assertWithMessage("Device addresses to mute")
                 .that(info.deviceAddressesToMute).asList().containsExactly(PRIMARY_MEDIA_ADDRESS,
@@ -371,17 +406,16 @@
 
     @Test
     public void generateMutingInfoFromZone_withMutedMultiDeviceGroup_returnsAllDevicesMuted() {
-        CarAudioZone primaryZone =
-                new TestCarAudioZoneBuilder("Primary Zone", PRIMARY_ZONE_ID)
-                        .addVolumeGroup(new VolumeGroupBuilder()
-                                .addDeviceAddressAndContexts(MUSIC, PRIMARY_MEDIA_ADDRESS)
-                                .addDeviceAddressAndContexts(VOICE_COMMAND, PRIMARY_VOICE_ADDRESS)
-                                .addDeviceAddressAndContexts(NAVIGATION, PRIMARY_NAVIGATION_ADDRESS)
-                                .setIsMuted(true)
-                                .build())
-                        .build();
+        CarAudioZone primaryZone = createAudioZone(
+                new VolumeGroupBuilder()
+                        .addDeviceAddressAndContexts(MUSIC, PRIMARY_MEDIA_ADDRESS)
+                        .addDeviceAddressAndContexts(VOICE_COMMAND, PRIMARY_VOICE_ADDRESS)
+                        .addDeviceAddressAndContexts(NAVIGATION, PRIMARY_NAVIGATION_ADDRESS)
+                        .setIsMuted(true)
+                        .build(), "Primary Zone", PRIMARY_ZONE_ID);
 
-        MutingInfo info = CarVolumeGroupMuting.generateMutingInfoFromZone(primaryZone);
+        MutingInfo info = CarVolumeGroupMuting.generateMutingInfoFromZone(primaryZone,
+                /* isMutingRestricted= */ false);
 
         assertWithMessage("Device addresses to mute")
                 .that(info.deviceAddressesToMute).asList().containsExactly(PRIMARY_MEDIA_ADDRESS,
@@ -390,35 +424,95 @@
 
     @Test
     public void generateMutingInfoFromZone_withUnMutedMultiDeviceGroup_returnsAllDevicesUnMuted() {
-        CarAudioZone primaryZone =
-                new TestCarAudioZoneBuilder("Primary Zone", PRIMARY_ZONE_ID)
-                        .addVolumeGroup(new VolumeGroupBuilder()
-                                .addDeviceAddressAndContexts(MUSIC, PRIMARY_MEDIA_ADDRESS)
-                                .addDeviceAddressAndContexts(VOICE_COMMAND, PRIMARY_VOICE_ADDRESS)
-                                .addDeviceAddressAndContexts(NAVIGATION, PRIMARY_NAVIGATION_ADDRESS)
-                                .build())
-                        .build();
+        CarAudioZone primaryZone = createAudioZone(
+                new VolumeGroupBuilder()
+                        .addDeviceAddressAndContexts(MUSIC, PRIMARY_MEDIA_ADDRESS)
+                        .addDeviceAddressAndContexts(VOICE_COMMAND, PRIMARY_VOICE_ADDRESS)
+                        .addDeviceAddressAndContexts(NAVIGATION, PRIMARY_NAVIGATION_ADDRESS)
+                        .build(), "Primary Zone", PRIMARY_ZONE_ID);
 
-        MutingInfo info = CarVolumeGroupMuting.generateMutingInfoFromZone(primaryZone);
+        MutingInfo info = CarVolumeGroupMuting.generateMutingInfoFromZone(primaryZone,
+                /* isMutingRestricted= */ false);
 
         assertWithMessage("Device addresses to un-mute")
                 .that(info.deviceAddressesToUnmute).asList().containsExactly(PRIMARY_MEDIA_ADDRESS,
                 PRIMARY_NAVIGATION_ADDRESS, PRIMARY_VOICE_ADDRESS);
     }
 
+    @Test
+    public void generateMutingInfoFromZone_mutingRestricted_mutesAllNonCriticalDevices() {
+        CarAudioZone primaryZone = createAudioZone(
+                new VolumeGroupBuilder()
+                        .addDeviceAddressAndContexts(MUSIC, PRIMARY_MEDIA_ADDRESS)
+                        .addDeviceAddressAndContexts(VOICE_COMMAND, PRIMARY_VOICE_ADDRESS)
+                        .addDeviceAddressAndContexts(NAVIGATION, PRIMARY_NAVIGATION_ADDRESS)
+                        .build(), "Primary Zone", PRIMARY_ZONE_ID);
+
+        MutingInfo info = CarVolumeGroupMuting.generateMutingInfoFromZone(primaryZone,
+                /* isMutingRestricted= */ true);
+
+        assertWithMessage("Device addresses to un-mute")
+                .that(info.deviceAddressesToMute).asList().containsExactly(PRIMARY_MEDIA_ADDRESS,
+                PRIMARY_NAVIGATION_ADDRESS, PRIMARY_VOICE_ADDRESS);
+    }
+
+    @Test
+    public void generateMutingInfoFromZone_mutingRestricted_setsAllCriticalGroupsToTheirState() {
+        CarAudioZone primaryZone =
+                new TestCarAudioZoneBuilder("Primary Zone", PRIMARY_ZONE_ID)
+                        .addVolumeGroup(new VolumeGroupBuilder()
+                                .addDeviceAddressAndContexts(EMERGENCY, EMERGENCY_ADDRESS)
+                                .addDeviceAddressAndContexts(VOICE_COMMAND, PRIMARY_VOICE_ADDRESS)
+                                .build())
+                        .addVolumeGroup(new VolumeGroupBuilder()
+                                .addDeviceAddressAndContexts(SAFETY, SAFETY_ADDRESS)
+                                .addDeviceAddressAndContexts(NAVIGATION, PRIMARY_NAVIGATION_ADDRESS)
+                                .setIsMuted(true)
+                                .build()
+                        )
+                        .build();
+        setUpCarVolumeGroupHasCriticalAudioContexts(primaryZone.getVolumeGroups()[0]);
+        setUpCarVolumeGroupHasCriticalAudioContexts(primaryZone.getVolumeGroups()[1]);
+
+        MutingInfo info = CarVolumeGroupMuting.generateMutingInfoFromZone(primaryZone,
+                /* isMutingRestricted= */ true);
+
+        assertWithMessage("Device addresses to mute")
+                .that(info.deviceAddressesToMute).asList()
+                .containsExactly(SAFETY_ADDRESS, PRIMARY_NAVIGATION_ADDRESS);
+        assertWithMessage("Device addresses to un-mute")
+                .that(info.deviceAddressesToUnmute).asList()
+                .containsExactly(EMERGENCY_ADDRESS, PRIMARY_VOICE_ADDRESS);
+    }
+
+
+    private CarAudioZone createAudioZone(CarVolumeGroup volumeGroup, String name, int zoneId) {
+        return new TestCarAudioZoneBuilder(name, zoneId)
+                .addVolumeGroup(volumeGroup)
+                .build();
+    }
+
+    private CarVolumeGroup groupWithContextAndAddress(int context, String address) {
+        return new VolumeGroupBuilder().addDeviceAddressAndContexts(context, address).build();
+    }
+
     private List<MutingInfo> captureMutingInfoList() {
         ArgumentCaptor<List<MutingInfo>> captor = ArgumentCaptor.forClass(List.class);
         verify(mMockAudioControlWrapper).onDevicesToMuteChange(captor.capture());
         return captor.getValue();
     }
 
-    private void setUpCarVolumeGroupIsMuted(CarVolumeGroup musicCarVolumeGroup, boolean muted) {
-        when(musicCarVolumeGroup.isMuted()).thenReturn(muted);
+    private void setUpCarVolumeGroupIsMuted(CarVolumeGroup carVolumeGroup, boolean muted) {
+        when(carVolumeGroup.isMuted()).thenReturn(muted);
     }
 
-    private SparseArray<CarAudioZone> getAudioZones(CarAudioZone ...zones) {
+    private void setUpCarVolumeGroupHasCriticalAudioContexts(CarVolumeGroup carVolumeGroup) {
+        when(carVolumeGroup.hasCriticalAudioContexts()).thenReturn(true);
+    }
+
+    private SparseArray<CarAudioZone> getAudioZones(CarAudioZone... zones) {
         SparseArray<CarAudioZone> audioZones = new SparseArray<>();
-        for (CarAudioZone zone: zones) {
+        for (CarAudioZone zone : zones) {
             audioZones.put(zone.getId(), zone);
         }
         return audioZones;