1 /*
2  * Copyright (C) 2022 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.car.audio;
18 
19 import static android.media.AudioAttributes.USAGE_ASSISTANCE_NAVIGATION_GUIDANCE;
20 import static android.media.AudioAttributes.USAGE_MEDIA;
21 
22 import static com.android.car.audio.GainBuilder.DEFAULT_GAIN;
23 import static com.android.car.audio.GainBuilder.MAX_GAIN;
24 import static com.android.car.audio.GainBuilder.STEP_SIZE;
25 import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
26 
27 import static com.google.common.truth.Truth.assertWithMessage;
28 
29 import static org.junit.Assert.assertThrows;
30 import static org.mockito.ArgumentMatchers.any;
31 import static org.mockito.ArgumentMatchers.anyString;
32 import static org.mockito.ArgumentMatchers.eq;
33 import static org.mockito.Mockito.RETURNS_DEEP_STUBS;
34 import static org.mockito.Mockito.mock;
35 import static org.mockito.Mockito.never;
36 import static org.mockito.Mockito.spy;
37 import static org.mockito.Mockito.times;
38 import static org.mockito.Mockito.verify;
39 import static org.mockito.Mockito.when;
40 
41 import android.car.media.CarVolumeGroupEvent;
42 import android.car.media.CarVolumeGroupInfo;
43 import android.car.test.mocks.AbstractExtendedMockitoTestCase;
44 import android.hardware.automotive.audiocontrol.AudioGainConfigInfo;
45 import android.hardware.automotive.audiocontrol.IAudioControl;
46 import android.hardware.automotive.audiocontrol.Reasons;
47 import android.os.IBinder;
48 import android.util.SparseArray;
49 
50 import androidx.test.ext.junit.runners.AndroidJUnit4;
51 
52 import com.android.car.audio.hal.AudioControlWrapperAidl;
53 import com.android.car.audio.hal.HalAudioGainCallback;
54 
55 import org.junit.Before;
56 import org.junit.Test;
57 import org.junit.runner.RunWith;
58 import org.mockito.Mock;
59 
60 import java.util.Arrays;
61 import java.util.List;
62 
63 @RunWith(AndroidJUnit4.class)
64 public final class CarAudioGainMonitorTest extends AbstractExtendedMockitoTestCase {
65     private static final int PRIMARY_ZONE_ID = 0;
66     private static final int PASSENGER_ZONE_ID = 1;
67     private static final int UNKNOWN_ZONE_ID = 50;
68     private static final int REAR_ZONE_ID = 2;
69     private static final String PRIMARY_MEDIA_ADDRESS = "primary_media";
70     private static final String PRIMARY_NAVIGATION_ADDRESS = "primary_navigation_address";
71     private static final String PRIMARY_CALL_ADDRESS = "primary_call_address";
72     private static final String REAR_MEDIA_ADDRESS = "rear_media";
73     private static final int TEST_GROUP_ID = 0;
74 
75     private static final CarAudioContext TEST_CAR_AUDIO_CONTEXT =
76             new CarAudioContext(CarAudioContext.getAllContextsInfo(),
77                     /* useCoreAudioRouting= */ false);
78 
79     private static final @CarAudioContext.AudioContext int TEST_MEDIA_AUDIO_CONTEXT =
80             TEST_CAR_AUDIO_CONTEXT.getContextForAudioAttribute(
81                     CarAudioContext.getAudioAttributeFromUsage(USAGE_MEDIA));
82     private static final @CarAudioContext.AudioContext int TEST_NAVIGATION_AUDIO_CONTEXT =
83             TEST_CAR_AUDIO_CONTEXT.getContextForAudioAttribute(CarAudioContext
84                     .getAudioAttributeFromUsage(USAGE_ASSISTANCE_NAVIGATION_GUIDANCE));
85 
86     private static final CarVolumeGroupInfo TEST_PRIMARY_VOLUME_INFO =
87             new CarVolumeGroupInfo.Builder("group id " + TEST_GROUP_ID, PRIMARY_ZONE_ID,
88                     TEST_GROUP_ID).setMinVolumeGainIndex(0)
89                     .setMaxVolumeGainIndex(MAX_GAIN / STEP_SIZE)
90                     .setVolumeGainIndex(DEFAULT_GAIN / STEP_SIZE)
91                     .setMinActivationVolumeGainIndex(0)
92                     .setMaxActivationVolumeGainIndex(MAX_GAIN / STEP_SIZE).build();
93 
94     private static final CarVolumeGroupInfo TEST_PASSENGER_VOLUME_INFO =
95             new CarVolumeGroupInfo.Builder("group id " + TEST_GROUP_ID, PASSENGER_ZONE_ID,
96                     TEST_GROUP_ID).setMinVolumeGainIndex(0)
97                     .setMaxVolumeGainIndex(MAX_GAIN / STEP_SIZE)
98                     .setVolumeGainIndex(DEFAULT_GAIN / STEP_SIZE)
99                     .setMinActivationVolumeGainIndex(0)
100                     .setMaxActivationVolumeGainIndex(MAX_GAIN / STEP_SIZE).build();
101 
102     private static final CarVolumeGroupInfo TEST_REAR_VOLUME_INFO =
103             new CarVolumeGroupInfo.Builder("group id " + TEST_GROUP_ID, REAR_ZONE_ID,
104                     TEST_GROUP_ID).setMinVolumeGainIndex(0)
105                     .setMaxVolumeGainIndex(MAX_GAIN / STEP_SIZE)
106                     .setVolumeGainIndex(DEFAULT_GAIN / STEP_SIZE)
107                     .setMinActivationVolumeGainIndex(0)
108                     .setMaxActivationVolumeGainIndex(MAX_GAIN / STEP_SIZE).build();
109 
110     private static final CarVolumeGroupEvent TEST_PRIMARY_VOLUME_GROUP_EVENT =
111             new CarVolumeGroupEvent.Builder(List.of(TEST_PRIMARY_VOLUME_INFO),
112                     CarVolumeGroupEvent.EVENT_TYPE_VOLUME_GAIN_INDEX_CHANGED).build();
113 
114     private static final CarVolumeGroupEvent TEST_PASSENGER_VOLUME_GROUP_EVENT =
115             new CarVolumeGroupEvent.Builder(List.of(TEST_PASSENGER_VOLUME_INFO),
116                     CarVolumeGroupEvent.EVENT_TYPE_VOLUME_GAIN_INDEX_CHANGED).build();
117 
118     private static final CarVolumeGroupEvent TEST_REAR_VOLUME_GROUP_EVENT =
119             new CarVolumeGroupEvent.Builder(List.of(TEST_REAR_VOLUME_INFO),
120                     CarVolumeGroupEvent.EVENT_TYPE_VOLUME_GAIN_INDEX_CHANGED).build();
121 
122     private final SparseArray<CarAudioZone> mCarAudioZones = generateZoneMocks();
123 
124     @Mock private IBinder mBinder;
125 
126     @Mock private IAudioControl mAudioControl;
127 
128     @Mock
129     CarVolumeInfoWrapper mMockVolumeInfoWrapper;
130 
131     private AudioControlWrapperAidl mAudioControlWrapperAidl;
132 
133     @Override
onSessionBuilder(CustomMockitoSessionBuilder session)134     protected void onSessionBuilder(CustomMockitoSessionBuilder session) {
135         session.spyStatic(AudioControlWrapperAidl.class);
136     }
137 
138     @Before
setUp()139     public void setUp() {
140         when(mBinder.queryLocalInterface(anyString())).thenReturn(mAudioControl);
141         doReturn(mBinder).when(AudioControlWrapperAidl::getService);
142         mAudioControlWrapperAidl = spy(new AudioControlWrapperAidl(mBinder));
143     }
144 
145     @Test
constructor_fails()146     public void constructor_fails() {
147         assertThrows(
148                 NullPointerException.class,
149                 () ->
150                         new CarAudioGainMonitor(
151                                 /* audioControlWrapper= */ null, mMockVolumeInfoWrapper,
152                                 /* carAudioZones= */ null));
153 
154         assertThrows(
155                 NullPointerException.class,
156                 () ->
157                         new CarAudioGainMonitor(
158                                 mAudioControlWrapperAidl, mMockVolumeInfoWrapper,
159                                 /* carAudioZones= */ null));
160 
161         assertThrows(
162                 NullPointerException.class,
163                 () -> new CarAudioGainMonitor(/* audioControlWrapper= */ null,
164                         mMockVolumeInfoWrapper, mCarAudioZones));
165     }
166 
167     @Test
constructor_nullCarVolumeInfoWrapper_fails()168     public void constructor_nullCarVolumeInfoWrapper_fails() {
169         Throwable thrown = assertThrows(NullPointerException.class, () -> new CarAudioGainMonitor(
170                 mAudioControlWrapperAidl, /* carVolumeInfoWrapper= */ null, mCarAudioZones));
171 
172         expectWithMessage("Constructor exception")
173                 .that(thrown).hasMessageThat().contains("Car volume info wrapper can not be null");
174     }
175 
176     @Test
constructor_succeeds()177     public void constructor_succeeds() {
178         CarAudioGainMonitor carAudioGainMonitor =
179                 new CarAudioGainMonitor(mAudioControlWrapperAidl, mMockVolumeInfoWrapper,
180                         mCarAudioZones);
181 
182         expectWithMessage("carAudioGainMonitor").that(carAudioGainMonitor).isNotNull();
183     }
184 
185     @Test
registercallback_succeeds()186     public void registercallback_succeeds() {
187         CarAudioGainMonitor carAudioGainMonitor =
188                 new CarAudioGainMonitor(mAudioControlWrapperAidl, mMockVolumeInfoWrapper,
189                         mCarAudioZones);
190 
191         HalAudioGainCallback callback = mock(HalAudioGainCallback.class);
192         carAudioGainMonitor.registerAudioGainListener(callback);
193         verify(mAudioControlWrapperAidl).registerAudioGainCallback(eq(callback));
194 
195         carAudioGainMonitor.unregisterAudioGainListener();
196         verify(mAudioControlWrapperAidl).unregisterAudioGainCallback();
197     }
198 
199     @Test
registercallback_multipleTimes()200     public void registercallback_multipleTimes() {
201         CarAudioGainMonitor carAudioGainMonitor =
202                 new CarAudioGainMonitor(mAudioControlWrapperAidl, mMockVolumeInfoWrapper,
203                         mCarAudioZones);
204         HalAudioGainCallback callback = mock(HalAudioGainCallback.class);
205         carAudioGainMonitor.registerAudioGainListener(callback);
206         verify(mAudioControlWrapperAidl).registerAudioGainCallback(eq(callback));
207 
208         carAudioGainMonitor.registerAudioGainListener(callback);
209         verify(mAudioControlWrapperAidl, times(2)).registerAudioGainCallback(eq(callback));
210 
211         HalAudioGainCallback callback2 = mock(HalAudioGainCallback.class);
212         carAudioGainMonitor.registerAudioGainListener(callback2);
213         verify(mAudioControlWrapperAidl).registerAudioGainCallback(eq(callback2));
214     }
215 
216     @Test
handleAudioDeviceGainsChanged_validZones()217     public void handleAudioDeviceGainsChanged_validZones() {
218         List<CarVolumeGroupEvent> events =
219                 List.of(
220                         TEST_PRIMARY_VOLUME_GROUP_EVENT,
221                         TEST_PASSENGER_VOLUME_GROUP_EVENT,
222                         TEST_REAR_VOLUME_GROUP_EVENT
223                 );
224         CarAudioGainMonitor carAudioGainMonitor =
225                 new CarAudioGainMonitor(mAudioControlWrapperAidl, mMockVolumeInfoWrapper,
226                         mCarAudioZones);
227         List<Integer> reasons = List.of(Reasons.REMOTE_MUTE, Reasons.NAV_DUCKING);
228         AudioGainConfigInfo primaryZoneGain = new AudioGainConfigInfo();
229         primaryZoneGain.zoneId = PRIMARY_ZONE_ID;
230         primaryZoneGain.devicePortAddress = PRIMARY_MEDIA_ADDRESS;
231         CarAudioGainConfigInfo primaryZoneCarGain = new CarAudioGainConfigInfo(primaryZoneGain);
232         AudioGainConfigInfo primaryZoneGain2 = new AudioGainConfigInfo();
233         primaryZoneGain2.zoneId = PRIMARY_ZONE_ID;
234         primaryZoneGain2.devicePortAddress = PRIMARY_NAVIGATION_ADDRESS;
235         CarAudioGainConfigInfo primaryZoneCarGain2 = new CarAudioGainConfigInfo(primaryZoneGain2);
236         AudioGainConfigInfo primaryZoneGain3 = new AudioGainConfigInfo();
237         primaryZoneGain3.zoneId = PRIMARY_ZONE_ID;
238         primaryZoneGain3.devicePortAddress = PRIMARY_CALL_ADDRESS;
239         CarAudioGainConfigInfo primaryZoneCarGain3 = new CarAudioGainConfigInfo(primaryZoneGain3);
240         AudioGainConfigInfo passengerZoneGain = new AudioGainConfigInfo();
241         passengerZoneGain.zoneId = PASSENGER_ZONE_ID;
242         CarAudioGainConfigInfo passengerZoneCarGain = new CarAudioGainConfigInfo(passengerZoneGain);
243         AudioGainConfigInfo rearZoneGain = new AudioGainConfigInfo();
244         rearZoneGain.zoneId = REAR_ZONE_ID;
245         CarAudioGainConfigInfo rearZoneCarGain = new CarAudioGainConfigInfo(rearZoneGain);
246 
247         SparseArray<List<CarAudioGainConfigInfo>> gainsForZones = new SparseArray<>();
248         gainsForZones.put(
249                 PRIMARY_ZONE_ID,
250                 Arrays.asList(primaryZoneCarGain, primaryZoneCarGain2, primaryZoneCarGain3));
251         gainsForZones.put(PASSENGER_ZONE_ID, Arrays.asList(passengerZoneCarGain));
252         gainsForZones.put(REAR_ZONE_ID, Arrays.asList(rearZoneCarGain));
253 
254         List<CarAudioGainConfigInfo> gains =
255                 List.of(
256                         primaryZoneCarGain,
257                         primaryZoneCarGain2,
258                         primaryZoneCarGain3,
259                         passengerZoneCarGain,
260                         rearZoneCarGain);
261         carAudioGainMonitor.handleAudioDeviceGainsChanged(reasons, gains);
262 
263         for (int index = 0; index < mCarAudioZones.size(); index++) {
264             CarAudioZone carAudioZone = mCarAudioZones.valueAt(index);
265             List<CarAudioGainConfigInfo> gainsForZone = gainsForZones.get(carAudioZone.getId());
266             verify(carAudioZone).onAudioGainChanged(eq(reasons), eq(gainsForZone));
267         }
268         verify(mMockVolumeInfoWrapper).onVolumeGroupEvent(eq(events));
269     }
270 
271     @Test
handleAudioDeviceGainsChanged_validAndUnknownZones()272     public void handleAudioDeviceGainsChanged_validAndUnknownZones() {
273         List<CarVolumeGroupEvent> events =
274                 List.of(
275                         TEST_PRIMARY_VOLUME_GROUP_EVENT,
276                         TEST_REAR_VOLUME_GROUP_EVENT
277                 );
278         CarAudioGainMonitor carAudioGainMonitor =
279                 new CarAudioGainMonitor(mAudioControlWrapperAidl, mMockVolumeInfoWrapper,
280                         mCarAudioZones);
281         List<Integer> reasons = List.of(Reasons.REMOTE_MUTE, Reasons.NAV_DUCKING);
282         AudioGainConfigInfo primaryZoneGain = new AudioGainConfigInfo();
283         primaryZoneGain.zoneId = PRIMARY_ZONE_ID;
284         CarAudioGainConfigInfo primaryZoneCarGain = new CarAudioGainConfigInfo(primaryZoneGain);
285         AudioGainConfigInfo rearZoneGain = new AudioGainConfigInfo();
286         rearZoneGain.zoneId = REAR_ZONE_ID;
287         CarAudioGainConfigInfo rearZoneCarGain = new CarAudioGainConfigInfo(rearZoneGain);
288         AudioGainConfigInfo unknownGain = new AudioGainConfigInfo();
289         unknownGain.zoneId = UNKNOWN_ZONE_ID;
290         CarAudioGainConfigInfo unknownCarGain = new CarAudioGainConfigInfo(unknownGain);
291 
292         SparseArray<List<CarAudioGainConfigInfo>> gainsForZones = new SparseArray<>();
293         gainsForZones.put(PRIMARY_ZONE_ID, Arrays.asList(primaryZoneCarGain));
294         gainsForZones.put(REAR_ZONE_ID, Arrays.asList(rearZoneCarGain));
295         gainsForZones.put(UNKNOWN_ZONE_ID, Arrays.asList(unknownCarGain));
296 
297         List<CarAudioGainConfigInfo> gains =
298                 List.of(primaryZoneCarGain, unknownCarGain, rearZoneCarGain);
299         carAudioGainMonitor.handleAudioDeviceGainsChanged(reasons, gains);
300 
301         for (int index = 0; index < mCarAudioZones.size(); index++) {
302             CarAudioZone carAudioZone = mCarAudioZones.valueAt(index);
303             Integer zoneId = carAudioZone.getId();
304             if (gainsForZones.contains(zoneId)) {
305                 List<CarAudioGainConfigInfo> gainsForZone = gainsForZones.get(zoneId);
306                 verify(carAudioZone).onAudioGainChanged(eq(reasons), eq(gainsForZone));
307                 continue;
308             }
309             verify(carAudioZone, never()).onAudioGainChanged(any(), any());
310         }
311         verify(mMockVolumeInfoWrapper).onVolumeGroupEvent(eq(events));
312     }
313 
314     @Test
handleAudioDeviceGainsChanged_unknownZones()315     public void handleAudioDeviceGainsChanged_unknownZones() {
316         CarAudioGainMonitor carAudioGainMonitor =
317                 new CarAudioGainMonitor(mAudioControlWrapperAidl, mMockVolumeInfoWrapper,
318                         mCarAudioZones);
319         List<Integer> reasons = List.of(Reasons.REMOTE_MUTE, Reasons.NAV_DUCKING);
320 
321         AudioGainConfigInfo unknownGain = new AudioGainConfigInfo();
322         unknownGain.zoneId = UNKNOWN_ZONE_ID;
323         CarAudioGainConfigInfo unknownCarGain = new CarAudioGainConfigInfo(unknownGain);
324         AudioGainConfigInfo unknownGain2 = new AudioGainConfigInfo();
325         unknownGain2.zoneId = REAR_ZONE_ID + 1;
326         CarAudioGainConfigInfo unknownCarGain2 = new CarAudioGainConfigInfo(unknownGain2);
327 
328         List<CarAudioGainConfigInfo> gains = List.of(unknownCarGain, unknownCarGain2);
329         carAudioGainMonitor.handleAudioDeviceGainsChanged(reasons, gains);
330 
331         for (int index = 0; index < mCarAudioZones.size(); index++) {
332             CarAudioZone carAudioZone = mCarAudioZones.valueAt(index);
333             verify(carAudioZone, never()).onAudioGainChanged(any(), any());
334         }
335         verify(mMockVolumeInfoWrapper, never()).onVolumeGroupEvent(any());
336     }
337 
338     @Test
shouldBlockVolumeRequest_returnsTrue()339     public void shouldBlockVolumeRequest_returnsTrue() {
340         List<Integer> blockingReasons =
341                 List.of(Reasons.FORCED_MASTER_MUTE, Reasons.TCU_MUTE, Reasons.REMOTE_MUTE);
342 
343         // One by one
344         for (int index = 0; index < blockingReasons.size(); index++) {
345             List<Integer> reasons = Arrays.asList(blockingReasons.get(index));
346             assertWithMessage("Volume Requests Blocked")
347                     .that(CarAudioGainMonitor.shouldBlockVolumeRequest(reasons))
348                     .isTrue();
349         }
350         // All
351         assertWithMessage("Volume Requests Blocked")
352                 .that(CarAudioGainMonitor.shouldBlockVolumeRequest(blockingReasons))
353                 .isTrue();
354 
355         List<Integer> mixedReasons =
356                 List.of(
357                         Reasons.FORCED_MASTER_MUTE,
358                         Reasons.NAV_DUCKING,
359                         Reasons.THERMAL_LIMITATION);
360 
361         assertWithMessage("Volume Requests Blocked")
362                 .that(CarAudioGainMonitor.shouldBlockVolumeRequest(mixedReasons))
363                 .isTrue();
364     }
365 
366     @Test
shouldBlockVolumeRequest_returnsFalse()367     public void shouldBlockVolumeRequest_returnsFalse() {
368         List<Integer> nonBlockingReasons =
369                 List.of(
370                         Reasons.NAV_DUCKING,
371                         Reasons.ADAS_DUCKING,
372                         Reasons.THERMAL_LIMITATION,
373                         Reasons.SUSPEND_EXIT_VOL_LIMITATION);
374 
375         // One by one
376         for (int index = 0; index < nonBlockingReasons.size(); index++) {
377             List<Integer> reasons = Arrays.asList(nonBlockingReasons.get(index));
378             assertWithMessage("Volume Requests Blocked")
379                     .that(CarAudioGainMonitor.shouldBlockVolumeRequest(reasons))
380                     .isFalse();
381         }
382         // All
383         assertWithMessage("Volume Requests Blocked")
384                 .that(CarAudioGainMonitor.shouldBlockVolumeRequest(nonBlockingReasons))
385                 .isFalse();
386     }
387 
388     @Test
shouldLimitVolume_returnsTrue()389     public void shouldLimitVolume_returnsTrue() {
390         List<Integer> limitReasons =
391                 List.of(Reasons.THERMAL_LIMITATION, Reasons.SUSPEND_EXIT_VOL_LIMITATION);
392 
393         // One by one
394         for (int index = 0; index < limitReasons.size(); index++) {
395             List<Integer> reasons = Arrays.asList(limitReasons.get(index));
396             assertWithMessage("Volume Limited")
397                     .that(CarAudioGainMonitor.shouldLimitVolume(reasons))
398                     .isTrue();
399         }
400         // All
401         assertWithMessage("Volume Limited")
402                 .that(CarAudioGainMonitor.shouldLimitVolume(limitReasons))
403                 .isTrue();
404 
405         List<Integer> mixedReasons =
406                 List.of(
407                         Reasons.FORCED_MASTER_MUTE,
408                         Reasons.NAV_DUCKING,
409                         Reasons.THERMAL_LIMITATION);
410 
411         assertWithMessage("Volume Limited")
412                 .that(CarAudioGainMonitor.shouldLimitVolume(mixedReasons))
413                 .isTrue();
414     }
415 
416     @Test
shouldLimitVolume_returnsFalse()417     public void shouldLimitVolume_returnsFalse() {
418         List<Integer> nonLimitReasons =
419                 List.of(
420                         Reasons.NAV_DUCKING,
421                         Reasons.ADAS_DUCKING,
422                         Reasons.FORCED_MASTER_MUTE,
423                         Reasons.TCU_MUTE,
424                         Reasons.REMOTE_MUTE);
425 
426         // One by one
427         for (int index = 0; index < nonLimitReasons.size(); index++) {
428             List<Integer> reasons = Arrays.asList(nonLimitReasons.get(index));
429             assertWithMessage("Volume Limited")
430                     .that(CarAudioGainMonitor.shouldLimitVolume(reasons))
431                     .isFalse();
432         }
433         // All
434         assertWithMessage("Volume Limited")
435                 .that(CarAudioGainMonitor.shouldLimitVolume(nonLimitReasons))
436                 .isFalse();
437     }
438 
439     @Test
shouldDuckGain_returnsTrue()440     public void shouldDuckGain_returnsTrue() {
441         List<Integer> limitReasons = List.of(Reasons.ADAS_DUCKING, Reasons.NAV_DUCKING);
442 
443         // One by one
444         for (int index = 0; index < limitReasons.size(); index++) {
445             List<Integer> reasons = Arrays.asList(limitReasons.get(index));
446             assertWithMessage("Volume Requests Blocked")
447                     .that(CarAudioGainMonitor.shouldDuckGain(reasons))
448                     .isTrue();
449         }
450         // All
451         assertWithMessage("Volume Attenuated")
452                 .that(CarAudioGainMonitor.shouldDuckGain(limitReasons))
453                 .isTrue();
454 
455         List<Integer> mixedReasons =
456                 List.of(
457                         Reasons.FORCED_MASTER_MUTE,
458                         Reasons.NAV_DUCKING,
459                         Reasons.THERMAL_LIMITATION);
460 
461         assertWithMessage("Volume Attenuated")
462                 .that(CarAudioGainMonitor.shouldDuckGain(mixedReasons))
463                 .isTrue();
464     }
465 
466     @Test
shouldDuckGain_returnsFalse()467     public void shouldDuckGain_returnsFalse() {
468         List<Integer> nonDuckingReasons =
469                 List.of(
470                         Reasons.THERMAL_LIMITATION,
471                         Reasons.SUSPEND_EXIT_VOL_LIMITATION,
472                         Reasons.FORCED_MASTER_MUTE,
473                         Reasons.TCU_MUTE,
474                         Reasons.REMOTE_MUTE);
475 
476         // One by one
477         for (int index = 0; index < nonDuckingReasons.size(); index++) {
478             List<Integer> reasons = Arrays.asList(nonDuckingReasons.get(index));
479             assertWithMessage("Volume Attenuated")
480                     .that(CarAudioGainMonitor.shouldDuckGain(reasons))
481                     .isFalse();
482         }
483         // All
484         assertWithMessage("Volume Attenuated")
485                 .that(CarAudioGainMonitor.shouldDuckGain(nonDuckingReasons))
486                 .isFalse();
487     }
488 
489     @Test
shouldMuteVolumeGroup_forEach_returnsTrue()490     public void shouldMuteVolumeGroup_forEach_returnsTrue() {
491         List<Integer> muteReasons = List.of(Reasons.TCU_MUTE, Reasons.REMOTE_MUTE);
492 
493         for (int index = 0; index < muteReasons.size(); index++) {
494             List<Integer> reasons = Arrays.asList(muteReasons.get(index));
495             assertWithMessage("Mute volume group for reason")
496                     .that(CarAudioGainMonitor.shouldMuteVolumeGroup(reasons)).isTrue();
497         }
498     }
499 
500     @Test
shouldMuteVolumeGroup_returnsTrue()501     public void shouldMuteVolumeGroup_returnsTrue() {
502         List<Integer> muteReasons = List.of(Reasons.TCU_MUTE, Reasons.REMOTE_MUTE);
503 
504         assertWithMessage("Mute volume group for multiple reasons")
505                 .that(CarAudioGainMonitor.shouldMuteVolumeGroup(muteReasons)).isTrue();
506     }
507 
508     @Test
shouldMuteVolumeGroup_forEach_returnsFalse()509     public void shouldMuteVolumeGroup_forEach_returnsFalse() {
510         List<Integer> nonMuteReasons = List.of(Reasons.FORCED_MASTER_MUTE, Reasons.ADAS_DUCKING);
511 
512         for (int index = 0; index < nonMuteReasons.size(); index++) {
513             List<Integer> reasons = Arrays.asList(nonMuteReasons.get(index));
514             assertWithMessage("Mute volume group for reason")
515                     .that(CarAudioGainMonitor.shouldMuteVolumeGroup(reasons)).isFalse();
516         }
517     }
518 
519     @Test
shouldMuteVolumeGroup_returnsFalse()520     public void shouldMuteVolumeGroup_returnsFalse() {
521         List<Integer> nonMuteReasons = List.of(Reasons.FORCED_MASTER_MUTE, Reasons.ADAS_DUCKING);
522 
523         assertWithMessage("Mute volume group for multiple reasons")
524                 .that(CarAudioGainMonitor.shouldMuteVolumeGroup(nonMuteReasons)).isFalse();
525     }
526 
527     @Test
shouldUpdateVolumeIndex_returnsTrue()528     public void shouldUpdateVolumeIndex_returnsTrue() {
529         List<Integer> reasons = List.of(Reasons.EXTERNAL_AMP_VOL_FEEDBACK, Reasons.TCU_MUTE);
530 
531         assertWithMessage("Update volume index for reasons")
532                 .that(CarAudioGainMonitor.shouldUpdateVolumeIndex(reasons)).isTrue();
533     }
534 
535     @Test
shouldUpdateVolumeIndex_returnsFalse()536     public void shouldUpdateVolumeIndex_returnsFalse() {
537         List<Integer> reasons =
538                 List.of(
539                         Reasons.THERMAL_LIMITATION,
540                         Reasons.SUSPEND_EXIT_VOL_LIMITATION,
541                         Reasons.FORCED_MASTER_MUTE,
542                         Reasons.TCU_MUTE,
543                         Reasons.REMOTE_MUTE);
544 
545         assertWithMessage("Update volume index for reasons")
546                 .that(CarAudioGainMonitor.shouldUpdateVolumeIndex(reasons)).isFalse();
547     }
548 
549     @Test
convertReasonsToExtraInfo_supportedReasons_isEqualTo()550     public void convertReasonsToExtraInfo_supportedReasons_isEqualTo() {
551         List<Integer> reasons =
552                 List.of(
553                         Reasons.REMOTE_MUTE,
554                         Reasons.THERMAL_LIMITATION,
555                         Reasons.SUSPEND_EXIT_VOL_LIMITATION,
556                         Reasons.TCU_MUTE,
557                         Reasons.FORCED_MASTER_MUTE,
558                         Reasons.ADAS_DUCKING,
559                         Reasons.NAV_DUCKING,
560                         Reasons.EXTERNAL_AMP_VOL_FEEDBACK,
561                         Reasons.PROJECTION_DUCKING);
562         List<Integer> extraInfos =
563                 List.of(
564                         CarVolumeGroupEvent.EXTRA_INFO_MUTE_TOGGLED_BY_AUDIO_SYSTEM,
565                         CarVolumeGroupEvent.EXTRA_INFO_TRANSIENT_ATTENUATION_THERMAL,
566                         CarVolumeGroupEvent.EXTRA_INFO_ATTENUATION_ACTIVATION,
567                         CarVolumeGroupEvent.EXTRA_INFO_MUTE_TOGGLED_BY_EMERGENCY,
568                         CarVolumeGroupEvent.EXTRA_INFO_TRANSIENT_ATTENUATION_EXTERNAL,
569                         CarVolumeGroupEvent.EXTRA_INFO_TRANSIENT_ATTENUATION_NAVIGATION,
570                         CarVolumeGroupEvent.EXTRA_INFO_VOLUME_INDEX_CHANGED_BY_AUDIO_SYSTEM,
571                         CarVolumeGroupEvent.EXTRA_INFO_TRANSIENT_ATTENUATION_PROJECTION);
572 
573         assertWithMessage("Convert reasons to extra infos")
574                 .that(CarAudioGainMonitor.convertReasonsToExtraInfo(reasons)).isEqualTo(extraInfos);
575     }
576 
577     @Test
convertReasonsToExtraInfo_unSupportedReasons_isEmpty()578     public void convertReasonsToExtraInfo_unSupportedReasons_isEmpty() {
579         List<Integer> reasons =
580                 List.of(
581                         Reasons.FORCED_MASTER_MUTE,
582                         Reasons.OTHER);
583 
584         assertWithMessage("Convert reasons to extra infos")
585                 .that(CarAudioGainMonitor.convertReasonsToExtraInfo(reasons).isEmpty()).isTrue();
586     }
587 
generateZoneMocks()588     private static SparseArray<CarAudioZone> generateZoneMocks() {
589         SparseArray<CarAudioZone> zones = new SparseArray<>();
590         CarAudioZone primaryZone = mock(CarAudioZone.class, RETURNS_DEEP_STUBS);
591         when(primaryZone.getId()).thenReturn(PRIMARY_ZONE_ID);
592         when(primaryZone.getAddressForContext(TEST_MEDIA_AUDIO_CONTEXT))
593                 .thenReturn(PRIMARY_MEDIA_ADDRESS);
594         when(primaryZone.getAddressForContext(TEST_NAVIGATION_AUDIO_CONTEXT))
595                 .thenReturn(PRIMARY_NAVIGATION_ADDRESS);
596         when(primaryZone.onAudioGainChanged(any(), any()))
597                 .thenReturn(List.of(TEST_PRIMARY_VOLUME_GROUP_EVENT));
598         zones.append(PRIMARY_ZONE_ID, primaryZone);
599 
600         CarAudioZone passengerZone = mock(CarAudioZone.class, RETURNS_DEEP_STUBS);
601         when(passengerZone.getId()).thenReturn(PASSENGER_ZONE_ID);
602         when(passengerZone.onAudioGainChanged(any(), any()))
603                 .thenReturn(List.of(TEST_PASSENGER_VOLUME_GROUP_EVENT));
604         zones.append(PASSENGER_ZONE_ID, passengerZone);
605 
606         CarAudioZone rearZone = mock(CarAudioZone.class, RETURNS_DEEP_STUBS);
607         when(rearZone.getId()).thenReturn(REAR_ZONE_ID);
608         when(rearZone.getAddressForContext(TEST_MEDIA_AUDIO_CONTEXT))
609                 .thenReturn(REAR_MEDIA_ADDRESS);
610         when(rearZone.onAudioGainChanged(any(), any()))
611                 .thenReturn(List.of(TEST_REAR_VOLUME_GROUP_EVENT));
612         zones.append(REAR_ZONE_ID, rearZone);
613 
614         return zones;
615     }
616 }
617