• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2024 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.car.media.CarAudioManager.PRIMARY_AUDIO_ZONE;
20 import static android.hardware.automotive.audiocontrol.VolumeInvocationType.ON_BOOT;
21 import static android.hardware.automotive.audiocontrol.VolumeInvocationType.ON_PLAYBACK_CHANGED;
22 import static android.hardware.automotive.audiocontrol.VolumeInvocationType.ON_SOURCE_CHANGED;
23 import static android.media.AudioManager.GET_DEVICES_INPUTS;
24 import static android.media.AudioManager.GET_DEVICES_OUTPUTS;
25 
26 import static com.android.car.audio.CarActivationVolumeConfig.ACTIVATION_VOLUME_ON_BOOT;
27 import static com.android.car.audio.CarActivationVolumeConfig.ACTIVATION_VOLUME_ON_PLAYBACK_CHANGED;
28 import static com.android.car.audio.CarActivationVolumeConfig.ACTIVATION_VOLUME_ON_SOURCE_CHANGED;
29 import static com.android.car.audio.CarAudioDeviceInfoTestUtils.MIRROR_TEST_DEVICE;
30 import static com.android.car.audio.CarAudioDeviceInfoTestUtils.OEM_TEST_DEVICE;
31 import static com.android.car.audio.CarAudioDeviceInfoTestUtils.PRIMARY_ZONE_FM_TUNER_DEVICE;
32 import static com.android.car.audio.CarAudioDeviceInfoTestUtils.PRIMARY_ZONE_MICROPHONE_DEVICE;
33 import static com.android.car.audio.CarAudioDeviceInfoTestUtils.SECONDARY_ZONE_BUS_1000_INPUT_DEVICE;
34 import static com.android.car.audio.CarAudioTestUtils.GAINS;
35 import static com.android.car.audio.CarAudioTestUtils.PRIMARY_ZONE_NAME;
36 import static com.android.car.audio.CarAudioTestUtils.TEST_CREATED_CAR_AUDIO_CONTEXT;
37 import static com.android.car.audio.CarAudioTestUtils.TEST_EMERGENCY_ATTRIBUTE;
38 import static com.android.car.audio.CarAudioTestUtils.createAudioPort;
39 import static com.android.car.audio.CarAudioTestUtils.createAudioPortDeviceExt;
40 import static com.android.car.audio.CarAudioTestUtils.createCarAudioContextNameToIdMap;
41 import static com.android.car.audio.CarAudioTestUtils.createPrimaryAudioZone;
42 import static com.android.car.audio.CarAudioTestUtils.getContextForVolumeGroupConfig;
43 import static com.android.car.audio.CarAudioTestUtils.getTestCarFadeConfiguration;
44 import static com.android.car.audio.CarAudioTestUtils.getTestDisabledCarFadeConfiguration;
45 import static com.android.car.audio.CarAudioUtils.ACTIVATION_VOLUME_INVOCATION_TYPE;
46 import static com.android.car.audio.CarAudioUtils.ACTIVATION_VOLUME_PERCENTAGE_MAX;
47 import static com.android.car.audio.CarAudioUtils.ACTIVATION_VOLUME_PERCENTAGE_MIN;
48 import static com.android.car.audio.CoreAudioRoutingUtils.CORE_PRIMARY_ZONE;
49 import static com.android.car.audio.CoreAudioRoutingUtils.getCoreAudioZone;
50 import static com.android.car.audio.CoreAudioRoutingUtils.getCoreCarAudioContext;
51 import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
52 
53 import static com.google.common.truth.Truth.assertWithMessage;
54 
55 import static org.junit.Assert.assertThrows;
56 import static org.mockito.Mockito.when;
57 
58 import android.car.test.mocks.AbstractExtendedMockitoTestCase;
59 import android.hardware.automotive.audiocontrol.AudioDeviceConfiguration;
60 import android.hardware.automotive.audiocontrol.AudioZone;
61 import android.hardware.automotive.audiocontrol.AudioZoneConfig;
62 import android.hardware.automotive.audiocontrol.AudioZoneContext;
63 import android.hardware.automotive.audiocontrol.AudioZoneContextInfo;
64 import android.hardware.automotive.audiocontrol.RoutingDeviceConfiguration;
65 import android.hardware.automotive.audiocontrol.TransientFadeConfigurationEntry;
66 import android.hardware.automotive.audiocontrol.VolumeActivationConfiguration;
67 import android.hardware.automotive.audiocontrol.VolumeGroupConfig;
68 import android.media.AudioDeviceAttributes;
69 import android.media.AudioProductStrategy;
70 import android.media.audio.common.AudioDeviceType;
71 import android.media.audio.common.AudioGain;
72 import android.media.audio.common.AudioPort;
73 import android.media.audio.common.AudioPortDeviceExt;
74 import android.util.ArrayMap;
75 import android.util.ArraySet;
76 import android.util.Log;
77 import android.util.SparseArray;
78 
79 import com.android.car.CarServiceUtils;
80 import com.android.car.internal.util.LocalLog;
81 
82 import org.junit.Before;
83 import org.junit.Test;
84 import org.mockito.Mock;
85 
86 import java.util.ArrayList;
87 import java.util.Collections;
88 import java.util.List;
89 import java.util.Set;
90 
91 public class AudioControlZoneConverterUnitTest extends AbstractExtendedMockitoTestCase {
92 
93     private static final String TAG = AudioControlZoneConverterUnitTest.class.getSimpleName();
94 
95     @Mock
96     private CarAudioSettings mCarAudioSettings;
97     @Mock
98     private AudioManagerWrapper mAudioManager;
99     @Mock
100     private LocalLog mServiceLog;
101 
102     private CarAudioDeviceInfoTestUtils mAudioDeviceInfoTestUtils =
103             new CarAudioDeviceInfoTestUtils();
104 
105     private ArrayMap<String, Integer> mCarAudioContextMap;
106     private boolean mUseFadeManagerConfiguration;
107 
108     @Override
clearInlineMocks(String when)109     protected void clearInlineMocks(String when) {
110         super.clearInlineMocks(when);
111     }
112 
113     @Override
onSessionBuilder(CustomMockitoSessionBuilder session)114     protected void onSessionBuilder(CustomMockitoSessionBuilder session) {
115         session.spyStatic(AudioManagerWrapper.class);
116     }
117 
118     @Before
setUp()119     public void setUp() {
120         doReturn(CoreAudioRoutingUtils.getProductStrategies())
121                 .when(AudioManagerWrapper::getAudioProductStrategies);
122         var outputDevice = mAudioDeviceInfoTestUtils.generateOutputDeviceInfos();
123         var inputDevices = mAudioDeviceInfoTestUtils.generateInputDeviceInfos();
124         when(mAudioManager.getDevices(GET_DEVICES_OUTPUTS)).thenReturn(outputDevice);
125         when(mAudioManager.getDevices(GET_DEVICES_INPUTS)).thenReturn(inputDevices);
126         mCarAudioContextMap = createCarAudioContextNameToIdMap(TEST_CREATED_CAR_AUDIO_CONTEXT);
127         mUseFadeManagerConfiguration = true;
128     }
129 
130     @Test
constructor_withNullAudioManager()131     public void constructor_withNullAudioManager() {
132         AudioManagerWrapper audioManager = null;
133         var serviceLog = new LocalLog(10);
134 
135         var thrown = assertThrows(NullPointerException.class, () ->
136                 new AudioControlZoneConverter(audioManager, mCarAudioSettings, serviceLog,
137                         mUseFadeManagerConfiguration));
138 
139         expectWithMessage("Constructor exception for null audio manager").that(thrown)
140                 .hasMessageThat().contains("Audio manager");
141     }
142 
143     @Test
constructor_withNullCarAudioSetting()144     public void constructor_withNullCarAudioSetting() {
145         var serviceLog = new LocalLog(10);
146         CarAudioSettings carAudioSettings = null;
147 
148         var thrown = assertThrows(NullPointerException.class, () ->
149                 new AudioControlZoneConverter(mAudioManager, carAudioSettings, serviceLog,
150                         mUseFadeManagerConfiguration));
151 
152         expectWithMessage("Constructor exception for null car audio settings").that(thrown)
153                 .hasMessageThat().contains("Car audio settings");
154     }
155 
156     @Test
constructor_withNullServiceLog()157     public void constructor_withNullServiceLog() {
158         LocalLog serviceLog = null;
159 
160         var thrown = assertThrows(NullPointerException.class, () ->
161                 new AudioControlZoneConverter(mAudioManager, mCarAudioSettings, serviceLog,
162                         mUseFadeManagerConfiguration));
163 
164         expectWithMessage("Constructor exception for null car service log").that(thrown)
165                 .hasMessageThat().contains("Local car service logs");
166     }
167 
168     @Test
convertAudioZone_withNullAudioZone()169     public void convertAudioZone_withNullAudioZone() {
170         AudioZone zone = null;
171         var audioDeviceConfig = new AudioDeviceConfiguration();
172         var audioZoneConverter = setupAudioZoneConverter();
173 
174         var thrown = assertThrows(NullPointerException.class, () ->
175                 audioZoneConverter.convertAudioZone(zone, audioDeviceConfig));
176 
177         expectWithMessage("Convert audio zone exception for null audio zone").that(thrown)
178                 .hasMessageThat().contains("Audio zone");
179     }
180 
181     @Test
convertAudioZone_withNullAudioDeviceConfiguration()182     public void convertAudioZone_withNullAudioDeviceConfiguration() {
183         var zone = new AudioZone();
184         AudioDeviceConfiguration audioDeviceConfig = null;
185         var audioZoneConverter = setupAudioZoneConverter();
186 
187         var thrown = assertThrows(NullPointerException.class, () ->
188                 audioZoneConverter.convertAudioZone(zone, audioDeviceConfig));
189 
190         expectWithMessage("Convert audio zone exception for null audio zone").that(thrown)
191                 .hasMessageThat().contains("Audio device configuration");
192     }
193 
194     @Test
convertAudioZone_withNullAudioContextInZone()195     public void convertAudioZone_withNullAudioContextInZone() {
196         var zone = new AudioZone();
197         var audioDeviceConfig = new AudioDeviceConfiguration();
198         var audioZoneConverter = setupAudioZoneConverter();
199 
200         var thrown = assertThrows(NullPointerException.class, () ->
201                 audioZoneConverter.convertAudioZone(zone, audioDeviceConfig));
202 
203         expectWithMessage("Convert audio zone exception for null context").that(thrown)
204                 .hasMessageThat().contains("Audio zone context");
205     }
206 
207     @Test
convertAudioZone_withNullAudioContextInfosInZone()208     public void convertAudioZone_withNullAudioContextInfosInZone() {
209         var zone = new AudioZone();
210         zone.audioZoneContext = new AudioZoneContext();
211         var audioDeviceConfig = new AudioDeviceConfiguration();
212         var audioZoneConverter = setupAudioZoneConverter();
213 
214         var thrown = assertThrows(NullPointerException.class, () ->
215                 audioZoneConverter.convertAudioZone(zone, audioDeviceConfig));
216 
217         expectWithMessage("Convert audio zone exception for null context infos").that(thrown)
218                 .hasMessageThat().contains("Audio zone context infos can not be null");
219     }
220 
221     @Test
convertAudioZone_withEmptyAudioContextInfosInZone()222     public void convertAudioZone_withEmptyAudioContextInfosInZone() {
223         var zone = new AudioZone();
224         var audioZoneContext = new AudioZoneContext();
225         audioZoneContext.audioContextInfos = Collections.EMPTY_LIST;
226         zone.audioZoneContext = audioZoneContext;
227         var audioDeviceConfig = new AudioDeviceConfiguration();
228         var audioZoneConverter = setupAudioZoneConverter();
229 
230         var thrown = assertThrows(IllegalArgumentException.class, () ->
231                 audioZoneConverter.convertAudioZone(zone, audioDeviceConfig));
232 
233         expectWithMessage("Convert audio zone exception for empty context infos").that(thrown)
234                 .hasMessageThat().contains("Audio zone context infos can not be empty");
235     }
236 
237     @Test
convertAudioZone_withMalformedAudioContextInZone()238     public void convertAudioZone_withMalformedAudioContextInZone() {
239         var zone = new AudioZone();
240         var audioZoneContext = new AudioZoneContext();
241         audioZoneContext.audioContextInfos = List.of(new AudioZoneContextInfo());
242         zone.audioZoneContext = audioZoneContext;
243         var audioDeviceConfig = new AudioDeviceConfiguration();
244         var audioZoneConverter = setupAudioZoneConverter();
245 
246         var carAudioZone = audioZoneConverter.convertAudioZone(zone, audioDeviceConfig);
247 
248         expectWithMessage("Converted audio zone with malformed audio context")
249                 .that(carAudioZone).isNull();
250     }
251 
252     @Test
convertAudioZone_withNonExistContextInVolumeGroup()253     public void convertAudioZone_withNonExistContextInVolumeGroup() {
254         var zone = createPrimaryAudioZone();
255         zone.audioZoneConfigs.getFirst().volumeGroups.getFirst().carAudioRoutes.getFirst()
256                 .contextNames.add("Non-existing context");
257         var audioDeviceConfig = new AudioDeviceConfiguration();
258         var audioZoneConverter = setupAudioZoneConverter();
259 
260         var carAudioZone = audioZoneConverter.convertAudioZone(zone, audioDeviceConfig);
261 
262         expectWithMessage("Converted audio zone with non existing context")
263                 .that(carAudioZone).isNull();
264     }
265 
266     @Test
convertAudioZone_withCoreVolumeAndVolumeGroupName()267     public void convertAudioZone_withCoreVolumeAndVolumeGroupName() {
268         var zone = createPrimaryAudioZone();
269         var audioDeviceConfig = new AudioDeviceConfiguration();
270         audioDeviceConfig.useCoreAudioVolume = true;
271         audioDeviceConfig.routingConfig =
272                 RoutingDeviceConfiguration.CONFIGURABLE_AUDIO_ENGINE_ROUTING;
273         var audioZoneConverter = setupAudioZoneConverter();
274 
275         var carAudioZone = audioZoneConverter.convertAudioZone(zone, audioDeviceConfig);
276 
277         expectWithMessage("Converted audio zone with for core volume and empty volme group name")
278                 .that(carAudioZone).isNull();
279     }
280 
281     @Test
convertAudioZone_withCoreRoutingAndMissingAudiProductStrategies()282     public void convertAudioZone_withCoreRoutingAndMissingAudiProductStrategies() {
283         doReturn(new ArrayList<AudioProductStrategy>())
284                 .when(AudioManagerWrapper::getAudioProductStrategies);
285         AudioZone zone = getCoreAudioZone();
286         zone.audioZoneContext.audioContextInfos.getFirst().id =
287                 AudioZoneContextInfo.UNASSIGNED_CONTEXT_ID;
288         var audioDeviceConfig = new AudioDeviceConfiguration();
289         audioDeviceConfig.useCoreAudioVolume = true;
290         audioDeviceConfig.routingConfig =
291                 RoutingDeviceConfiguration.CONFIGURABLE_AUDIO_ENGINE_ROUTING;
292         var audioZoneConverter = setupAudioZoneConverter();
293 
294         var thrown = assertThrows(IllegalArgumentException.class, () ->
295                 audioZoneConverter.convertAudioZone(zone, audioDeviceConfig));
296 
297         expectWithMessage("Convert audio zone exception for missing product strategy")
298                 .that(thrown).hasMessageThat().contains("product strategy id");
299     }
300 
301     @Test
convertAudioZone_forPrimaryZone_withCoreAudioZone()302     public void convertAudioZone_forPrimaryZone_withCoreAudioZone() {
303         var zone = getCoreAudioZone();
304         var audioDeviceConfig = new AudioDeviceConfiguration();
305         audioDeviceConfig.useCoreAudioVolume = true;
306         audioDeviceConfig.routingConfig =
307                 RoutingDeviceConfiguration.CONFIGURABLE_AUDIO_ENGINE_ROUTING;
308         var audioZoneConverter = setupAudioZoneConverter();
309 
310         var carAudioZone = audioZoneConverter.convertAudioZone(zone, audioDeviceConfig);
311 
312         var carAudioContextMap =
313                 createCarAudioContextNameToIdMap(carAudioZone.getCarAudioContext());
314         SparseArray<CarAudioZone> zones = new SparseArray<>(1);
315         boolean useCoreAudioRouting = true;
316         zones.append(PRIMARY_AUDIO_ZONE, carAudioZone);
317         CarAudioZonesValidator.validateWithoutInputDevicesCheck(zones, useCoreAudioRouting);
318         assertWithMessage("Converted primary car audio zone").that(carAudioZone)
319                 .isNotNull();
320         expectWithMessage("Converted primary car audio zone ID")
321                 .that(carAudioZone.getId()).isEqualTo(PRIMARY_AUDIO_ZONE);
322         expectWithMessage("Converted primary car audio zone name")
323                 .that(carAudioZone.getName()).isEqualTo(CORE_PRIMARY_ZONE);
324         expectWithMessage("Converted primary car audio zone context")
325                 .that(carAudioZone.getCarAudioContext()).isEqualTo(getCoreCarAudioContext());
326         var carAudioZoneConfigs = carAudioZone.getAllCarAudioZoneConfigs();
327         assertWithMessage("Converted primary car audio zone configs")
328                 .that(carAudioZoneConfigs.size()).isEqualTo(zone.audioZoneConfigs.size());
329         for (int c = 0; c < carAudioZoneConfigs.size(); c++) {
330             var zoneConfig = zone.audioZoneConfigs.get(c);
331             var zoneConfigInfo = carAudioZoneConfigs.get(c);
332             Log.i(TAG, "Testing zone config[" + c + "] " + zoneConfig.name);
333             expectWithMessage("Converted primary zone id in config %s", zoneConfig.name)
334                     .that(zoneConfigInfo.getZoneId()).isEqualTo(PRIMARY_AUDIO_ZONE);
335             validateZoneConfigInfo(carAudioZone.getName(), zoneConfigInfo, zoneConfig,
336                     PRIMARY_AUDIO_ZONE, audioDeviceConfig.useCoreAudioVolume, carAudioContextMap);
337         }
338     }
339 
340     @Test
convertAudioZone_forPrimaryZone()341     public void convertAudioZone_forPrimaryZone() {
342         var zone = createPrimaryAudioZone();
343         var audioDeviceConfig = new AudioDeviceConfiguration();
344         var audioZoneConverter = setupAudioZoneConverter();
345 
346         var carAudioZone = audioZoneConverter.convertAudioZone(zone, audioDeviceConfig);
347 
348         SparseArray<CarAudioZone> zones = new SparseArray<>(1);
349         boolean useCoreAudioRouting = false;
350         zones.append(PRIMARY_AUDIO_ZONE, carAudioZone);
351         CarAudioZonesValidator.validateWithoutInputDevicesCheck(zones, useCoreAudioRouting);
352         assertWithMessage("Converted primary car audio zone").that(carAudioZone)
353                 .isNotNull();
354         expectWithMessage("Converted primary car audio zone ID")
355                 .that(carAudioZone.getId()).isEqualTo(PRIMARY_AUDIO_ZONE);
356         expectWithMessage("Converted primary car audio zone name")
357                 .that(carAudioZone.getName()).isEqualTo(PRIMARY_ZONE_NAME);
358         expectWithMessage("Converted primary car audio zone context")
359                 .that(carAudioZone.getCarAudioContext()).isEqualTo(TEST_CREATED_CAR_AUDIO_CONTEXT);
360         var carAudioZoneConfigs = carAudioZone.getAllCarAudioZoneConfigs();
361         assertWithMessage("Converted primary car audio zone configs")
362                 .that(carAudioZoneConfigs.size()).isEqualTo(zone.audioZoneConfigs.size());
363         for (int c = 0; c < carAudioZoneConfigs.size(); c++) {
364             var zoneConfig = zone.audioZoneConfigs.get(c);
365             var zoneConfigInfo = carAudioZoneConfigs.get(c);
366             Log.i(TAG, "Testing zone config[" + c + "] " + zoneConfig.name);
367             expectWithMessage("Converted primary zone id in config %s", zoneConfig.name)
368                     .that(zoneConfigInfo.getZoneId()).isEqualTo(PRIMARY_AUDIO_ZONE);
369             validateZoneConfigInfo(carAudioZone.getName(), zoneConfigInfo, zoneConfig,
370                     PRIMARY_AUDIO_ZONE, audioDeviceConfig.useCoreAudioVolume, mCarAudioContextMap);
371         }
372     }
373 
374     @Test
convertAudioZone_forPrimaryZoneAndWithCoreAudioZoneButMissingContextID()375     public void convertAudioZone_forPrimaryZoneAndWithCoreAudioZoneButMissingContextID() {
376         var zone = getCoreAudioZone();
377         zone.audioZoneContext.audioContextInfos.getFirst().id =
378                 AudioZoneContextInfo.UNASSIGNED_CONTEXT_ID;
379         var audioDeviceConfig = new AudioDeviceConfiguration();
380         audioDeviceConfig.useCoreAudioVolume = true;
381         audioDeviceConfig.routingConfig =
382                 RoutingDeviceConfiguration.CONFIGURABLE_AUDIO_ENGINE_ROUTING;
383         var audioZoneConverter = setupAudioZoneConverter();
384 
385         var carAudioZone = audioZoneConverter.convertAudioZone(zone, audioDeviceConfig);
386 
387         SparseArray<CarAudioZone> zones = new SparseArray<>(1);
388         boolean useCoreAudioRouting = true;
389         zones.append(PRIMARY_AUDIO_ZONE, carAudioZone);
390         CarAudioZonesValidator.validateWithoutInputDevicesCheck(zones, useCoreAudioRouting);
391         assertWithMessage("Converted primary car audio zone with missing context ID")
392                 .that(carAudioZone).isNotNull();
393     }
394 
395     @Test
convertAudioZone_forPrimaryZoneAndWithFadeConfigurationEnabled()396     public void convertAudioZone_forPrimaryZoneAndWithFadeConfigurationEnabled() {
397         var zone = createPrimaryAudioZone();
398         var audioDeviceConfig = new AudioDeviceConfiguration();
399         var audioZoneConverter = setupAudioZoneConverter();
400 
401         var carAudioZone = audioZoneConverter.convertAudioZone(zone, audioDeviceConfig);
402 
403         SparseArray<CarAudioZone> zones = new SparseArray<>(1);
404         boolean useCoreAudioRouting = false;
405         zones.append(PRIMARY_AUDIO_ZONE, carAudioZone);
406         CarAudioZonesValidator.validateWithoutInputDevicesCheck(zones, useCoreAudioRouting);
407         var defaultZoneConfig = carAudioZone.getAllCarAudioZoneConfigs().stream()
408                 .filter(CarAudioZoneConfig::isDefault).findFirst().orElseThrow();
409         var defaultFadeConfig = defaultZoneConfig.getDefaultCarAudioFadeConfiguration();
410         expectWithMessage("Primary zone default config's default fade configuration")
411                 .that(defaultFadeConfig).isEqualTo(getTestCarFadeConfiguration());
412         var emergencyFadeConfig = defaultZoneConfig
413                 .getCarAudioFadeConfigurationForAudioAttributes(TEST_EMERGENCY_ATTRIBUTE);
414         // Actual car configuration is different since the default name changes for each config
415         expectWithMessage("Emergency fade configuration for default config in primary zone")
416                 .that(emergencyFadeConfig.getFadeManagerConfiguration())
417                 .isEqualTo(getTestDisabledCarFadeConfiguration().getFadeManagerConfiguration());
418     }
419 
420     @Test
convertAudioZone_forPrimaryZoneAndWithFadeConfigurationDisabled()421     public void convertAudioZone_forPrimaryZoneAndWithFadeConfigurationDisabled() {
422         mUseFadeManagerConfiguration = false;
423         var zone = createPrimaryAudioZone();
424         var audioDeviceConfig = new AudioDeviceConfiguration();
425         var audioZoneConverter = setupAudioZoneConverter();
426 
427         var carAudioZone = audioZoneConverter.convertAudioZone(zone, audioDeviceConfig);
428 
429         SparseArray<CarAudioZone> zones = new SparseArray<>(1);
430         boolean useCoreAudioRouting = false;
431         zones.append(PRIMARY_AUDIO_ZONE, carAudioZone);
432         CarAudioZonesValidator.validateWithoutInputDevicesCheck(zones, useCoreAudioRouting);
433         CarAudioZoneConfig defaultZoneConfig = carAudioZone.getAllCarAudioZoneConfigs().stream()
434                 .filter(carAudioZoneConfig -> carAudioZoneConfig.isDefault())
435                 .findFirst().orElseThrow();
436         var defaultFadeConfig = defaultZoneConfig.getDefaultCarAudioFadeConfiguration();
437         expectWithMessage("Primary zone default config's disabled default fade configuration")
438                 .that(defaultFadeConfig).isNull();
439     }
440 
441     @Test
convertAudioZone_forPrimaryZoneAndWithNullDefaultFadeConfig()442     public void convertAudioZone_forPrimaryZoneAndWithNullDefaultFadeConfig() {
443         var zone = createPrimaryAudioZone();
444         zone.audioZoneConfigs.getFirst().fadeConfiguration.defaultConfiguration = null;
445         var audioDeviceConfig = new AudioDeviceConfiguration();
446         var audioZoneConverter = setupAudioZoneConverter();
447 
448         var carAudioZone = audioZoneConverter.convertAudioZone(zone, audioDeviceConfig);
449 
450         expectWithMessage("Converted primary zone with null default fade config")
451                 .that(carAudioZone).isNull();
452     }
453 
454     @Test
convertAudioZone_forPrimaryZoneAndWithNullTransientFadeConfig()455     public void convertAudioZone_forPrimaryZoneAndWithNullTransientFadeConfig() {
456         var zone = createPrimaryAudioZone();
457         var transientFadeConfigs = new ArrayList<TransientFadeConfigurationEntry>();
458         transientFadeConfigs.add(null);
459         zone.audioZoneConfigs.getFirst().fadeConfiguration.transientConfiguration =
460                 transientFadeConfigs;
461         var audioDeviceConfig = new AudioDeviceConfiguration();
462         var audioZoneConverter = setupAudioZoneConverter();
463 
464         var carAudioZone = audioZoneConverter.convertAudioZone(zone, audioDeviceConfig);
465 
466         expectWithMessage("Converted primary zone with null transient fade config")
467                 .that(carAudioZone).isNull();
468     }
469 
470     @Test
convertZonesMirroringAudioPorts_forNullAudioPorts()471     public void convertZonesMirroringAudioPorts_forNullAudioPorts() {
472         var audioZoneConverter = setupAudioZoneConverter();
473 
474         var devices = audioZoneConverter.convertZonesMirroringAudioPorts(
475                 /* mirroringPorts= */ null);
476 
477         expectWithMessage("Converted mirroring devices for null ports").that(devices).isEmpty();
478     }
479 
480     @Test
convertZonesMirroringAudioPorts_forEmptyAudioPorts()481     public void convertZonesMirroringAudioPorts_forEmptyAudioPorts() {
482         var audioPorts = new ArrayList<AudioPort>(0);
483         var audioZoneConverter = setupAudioZoneConverter();
484 
485         var devices = audioZoneConverter.convertZonesMirroringAudioPorts(audioPorts);
486 
487         expectWithMessage("Converted mirroring devices for empty ports").that(devices).isEmpty();
488     }
489 
490     @Test
convertZonesMirroringAudioPorts_withNullDeviceInPortsList()491     public void convertZonesMirroringAudioPorts_withNullDeviceInPortsList() {
492         AudioPortDeviceExt mirrorPortDevice = createAudioPortDeviceExt(AudioDeviceType.OUT_BUS,
493                 /* connection= */ "", MIRROR_TEST_DEVICE);
494         AudioPort mirrorPort =
495                 createAudioPort(/* id= */ 10, "mirror port", GAINS, mirrorPortDevice);
496         var audioPorts = new ArrayList<AudioPort>(2);
497         audioPorts.add(mirrorPort);
498         audioPorts.add(null);
499         var audioZoneConverter = setupAudioZoneConverter();
500 
501         var devices = audioZoneConverter.convertZonesMirroringAudioPorts(audioPorts);
502 
503         expectWithMessage("Converted mirroring devices for null device in ports")
504                 .that(devices).isEmpty();
505 
506     }
507 
508     @Test
convertZonesMirroringAudioPorts_forAudioPorts()509     public void convertZonesMirroringAudioPorts_forAudioPorts() {
510         AudioPortDeviceExt mirrorPortDevice = createAudioPortDeviceExt(AudioDeviceType.OUT_BUS,
511                 /* connection= */ "", MIRROR_TEST_DEVICE);
512         AudioPort mirrorPort =
513                 createAudioPort(/* id= */ 10, "mirror port", GAINS, mirrorPortDevice);
514         AudioPortDeviceExt oemPortDevice = createAudioPortDeviceExt(AudioDeviceType.OUT_BUS,
515                 /* connection= */ "", OEM_TEST_DEVICE);
516         AudioPort oemPort = createAudioPort(/* id= */ 11, "oem port", GAINS, oemPortDevice);
517         var audioPorts = List.of(mirrorPort, oemPort);
518         var audioZoneConverter = setupAudioZoneConverter();
519 
520         var devices = audioZoneConverter.convertZonesMirroringAudioPorts(audioPorts);
521 
522         var addresses = devices.stream().map(CarAudioDeviceInfo::getAddress).toList();
523         expectWithMessage("Converted mirroring devices for empty ports")
524                 .that(addresses).containsExactly(MIRROR_TEST_DEVICE, OEM_TEST_DEVICE);
525 
526     }
527 
528     @Test
convertAudioZone_forPrimaryZoneAndWithEmptyListOfInputDevices()529     public void convertAudioZone_forPrimaryZoneAndWithEmptyListOfInputDevices() {
530         var zone = createPrimaryAudioZone();
531         zone.inputAudioDevices = List.of();
532         var audioDeviceConfig = new AudioDeviceConfiguration();
533         var audioZoneConverter = setupAudioZoneConverter();
534 
535         var carAudioZone = audioZoneConverter.convertAudioZone(zone, audioDeviceConfig);
536 
537         expectWithMessage("Converted primary zone empty list of input devices")
538                 .that(carAudioZone.getInputAudioDevices()).isEmpty();
539     }
540 
541     @Test
convertAudioZone_forPrimaryZoneAndWithNullDeviceInputDevices()542     public void convertAudioZone_forPrimaryZoneAndWithNullDeviceInputDevices() {
543         var zone = createPrimaryAudioZone();
544         var inputDevices = new ArrayList<AudioPort>(1);
545         inputDevices.add(null);
546         zone.inputAudioDevices = inputDevices;
547         var audioDeviceConfig = new AudioDeviceConfiguration();
548         var audioZoneConverter = setupAudioZoneConverter();
549 
550         var carAudioZone = audioZoneConverter.convertAudioZone(zone, audioDeviceConfig);
551 
552         expectWithMessage("Converted primary zone with invalid null input device zone")
553                 .that(carAudioZone).isNull();
554     }
555 
556     @Test
convertAudioZone_forPrimaryZoneAndWithNullExtDeviceInputDevices()557     public void convertAudioZone_forPrimaryZoneAndWithNullExtDeviceInputDevices() {
558         var zone = createPrimaryAudioZone();
559         var portDeviceExt = createAudioPortDeviceExt(AudioDeviceType.IN_BUS, /* connection= */ "",
560                 /* address= */ "invalid address");
561         var audioPort = createAudioPort(10, "input bus", new AudioGain[0], portDeviceExt);
562         zone.inputAudioDevices = List.of(audioPort);
563         var audioDeviceConfig = new AudioDeviceConfiguration();
564         var audioZoneConverter = setupAudioZoneConverter();
565 
566         var carAudioZone = audioZoneConverter.convertAudioZone(zone, audioDeviceConfig);
567 
568         expectWithMessage("Converted primary zone with invalid null input ext device")
569                 .that(carAudioZone).isNull();
570     }
571 
572     @Test
convertAudioZone_forPrimaryZoneAndWithInputDevices()573     public void convertAudioZone_forPrimaryZoneAndWithInputDevices() {
574         var zone = createPrimaryAudioZone();
575         zone.inputAudioDevices = getInputDevicePorts();
576         var audioDeviceConfig = new AudioDeviceConfiguration();
577         var audioZoneConverter = setupAudioZoneConverter();
578 
579         var carAudioZone = audioZoneConverter.convertAudioZone(zone, audioDeviceConfig);
580 
581         var zones = new SparseArray<CarAudioZone>(1);
582         boolean useCoreAudioRouting = false;
583         zones.append(PRIMARY_AUDIO_ZONE, carAudioZone);
584         CarAudioZonesValidator.validate(zones, useCoreAudioRouting);
585         var inputDevices = carAudioZone.getInputAudioDevices().stream()
586                 .map(AudioDeviceAttributes::getAddress).toList();
587         expectWithMessage("Converted input devices").that(inputDevices)
588                 .containsExactly(SECONDARY_ZONE_BUS_1000_INPUT_DEVICE, PRIMARY_ZONE_FM_TUNER_DEVICE,
589                         PRIMARY_ZONE_MICROPHONE_DEVICE);
590     }
591 
getInputDevicePorts()592     private List<AudioPort> getInputDevicePorts() {
593         var portBusDeviceExt = createAudioPortDeviceExt(AudioDeviceType.IN_BUS,
594                 /* connection= */ "", SECONDARY_ZONE_BUS_1000_INPUT_DEVICE);
595         var busPort = createAudioPort(11, "bus", new AudioGain[0], portBusDeviceExt);
596         var portFMDeviceExt = createAudioPortDeviceExt(AudioDeviceType.IN_FM_TUNER,
597                 /* connection= */ "", PRIMARY_ZONE_FM_TUNER_DEVICE);
598         var fmTunerPort = createAudioPort(10, "fm tuner", new AudioGain[0], portFMDeviceExt);
599         var portMicDeviceExt = createAudioPortDeviceExt(AudioDeviceType.IN_MICROPHONE,
600                 /* connection= */ "", PRIMARY_ZONE_MICROPHONE_DEVICE);
601         var micPort = createAudioPort(11, "mic", new AudioGain[0], portMicDeviceExt);
602         return List.of(busPort, fmTunerPort, micPort);
603     }
604 
validateZoneConfigInfo(String zoneName, CarAudioZoneConfig carZoneConfig, AudioZoneConfig zoneConfig, int zoneId, boolean useCoreVolume, ArrayMap<String, Integer> carAudioContextMap)605     private void validateZoneConfigInfo(String zoneName, CarAudioZoneConfig carZoneConfig,
606             AudioZoneConfig zoneConfig, int zoneId, boolean useCoreVolume,
607             ArrayMap<String, Integer> carAudioContextMap) {
608         expectWithMessage("Name for converted audio zone %s config %s", zoneName, zoneConfig.name)
609                 .that(carZoneConfig.getName()).isEqualTo(zoneConfig.name);
610         expectWithMessage("Primary status for converted audio zone %s config %s", zoneName,
611                 zoneConfig.name).that(carZoneConfig.isDefault())
612                 .isEqualTo(zoneConfig.isDefault);
613         var carVolumeGroups = carZoneConfig.getVolumeGroups();
614         var volumeGroupConfigs = zoneConfig.volumeGroups;
615         assertWithMessage("Volume groups for converted audio zone %s config %s", zoneName,
616                 zoneConfig.name).that(carVolumeGroups.length).isEqualTo(volumeGroupConfigs.size());
617         String configName = zoneName + " config id " + carZoneConfig.getName();
618         Set<Integer> contexts = new ArraySet<>();
619         for (int c = 0; c < carVolumeGroups.length; c++) {
620             var carVolumeGroup = carVolumeGroups[c];
621             var volumeGroupConfig = volumeGroupConfigs.get(c);
622             Log.i(TAG, "Testing volume group [" + c + "] " + volumeGroupConfig.name + " in "
623                     + configName);
624             expectWithMessage("Volume group zone id for %s", configName)
625                     .that(carVolumeGroup.mZoneId).isEqualTo(zoneId);
626             expectWithMessage("Volume group config id for %s", configName)
627                     .that(carVolumeGroup.mConfigId).isEqualTo(carZoneConfig.getZoneConfigId());
628             validateCarVolumeGroup(configName, carVolumeGroup, volumeGroupConfig, useCoreVolume,
629                     contexts, carAudioContextMap);
630         }
631         expectWithMessage("Total context count %s", configName).that(contexts.size())
632                 .isEqualTo(carAudioContextMap.size());
633     }
634 
validateCarVolumeGroup(String configInfo, CarVolumeGroup carVolumeGroup, VolumeGroupConfig groupConfig, boolean useCoreVolume, Set<Integer> contexts, ArrayMap<String, Integer> carAudioContextMap)635     private void validateCarVolumeGroup(String configInfo, CarVolumeGroup carVolumeGroup,
636             VolumeGroupConfig groupConfig, boolean useCoreVolume, Set<Integer> contexts,
637             ArrayMap<String, Integer> carAudioContextMap) {
638         if (useCoreVolume || (groupConfig.name != null && !groupConfig.name.isEmpty())) {
639             expectWithMessage("Volume group name for %s", configInfo)
640                     .that(carVolumeGroup.getName()).isEqualTo(groupConfig.name);
641         }
642         var groupContexts = CarServiceUtils.asList(carVolumeGroup.getContexts());
643         expectWithMessage("Volume group ID group %s for %s", carVolumeGroup.getName(),
644                 configInfo).that(carVolumeGroup.mId).isEqualTo(groupConfig.id);
645         expectWithMessage("Volume group contexts group %s for %s", carVolumeGroup.getName(),
646                 configInfo).that(groupContexts).containsExactlyElementsIn(
647                         getContextForVolumeGroupConfig(groupConfig, carAudioContextMap));
648         String groupInfo = "Volume group " + carVolumeGroup.getName() + " for " + configInfo;
649         validateMinMaxActivation(groupInfo, carVolumeGroup.getCarActivationVolumeConfig(),
650                 groupConfig.activationConfiguration);
651         for (int c = 0; c < groupContexts.size(); c++) {
652             int context = groupContexts.get(c);
653             expectWithMessage("Volume group context %s for %s", context, groupInfo)
654                     .that(contexts.add(context)).isTrue();
655         }
656     }
657 
validateMinMaxActivation(String groupInfo, CarActivationVolumeConfig config, VolumeActivationConfiguration activationConfiguration)658     private void validateMinMaxActivation(String groupInfo, CarActivationVolumeConfig config,
659             VolumeActivationConfiguration activationConfiguration) {
660         int activation = ACTIVATION_VOLUME_INVOCATION_TYPE;
661         int min = ACTIVATION_VOLUME_PERCENTAGE_MIN;
662         int max = ACTIVATION_VOLUME_PERCENTAGE_MAX;
663         if (activationConfiguration != null
664                 && activationConfiguration.volumeActivationEntries != null
665                 && !activationConfiguration.volumeActivationEntries.isEmpty()) {
666             var entry = activationConfiguration.volumeActivationEntries.getFirst();
667             activation = getActivationFromHalActivation(entry.type);
668             min = entry.minActivationVolumePercentage;
669             max = entry.maxActivationVolumePercentage;
670         }
671         expectWithMessage("Volume group activation for %s", groupInfo)
672                 .that(config.getInvocationType()).isEqualTo(activation);
673         expectWithMessage("Volume group min activation value for %s", groupInfo)
674                 .that(config.getMinActivationVolumePercentage()).isEqualTo(min);
675         expectWithMessage("Volume group max activation value for %s", groupInfo)
676                 .that(config.getMaxActivationVolumePercentage()).isEqualTo(max);
677     }
678 
getActivationFromHalActivation(int halActivationType)679     private int getActivationFromHalActivation(int halActivationType) {
680         return switch (halActivationType) {
681             case ON_PLAYBACK_CHANGED -> ACTIVATION_VOLUME_ON_PLAYBACK_CHANGED;
682             case ON_SOURCE_CHANGED -> ACTIVATION_VOLUME_ON_SOURCE_CHANGED;
683             case ON_BOOT -> ACTIVATION_VOLUME_ON_BOOT;
684             default -> ACTIVATION_VOLUME_INVOCATION_TYPE;
685         };
686     }
687 
setupAudioZoneConverter()688     private AudioControlZoneConverter setupAudioZoneConverter() {
689         return new AudioControlZoneConverter(mAudioManager, mCarAudioSettings, mServiceLog,
690                 mUseFadeManagerConfiguration);
691     }
692 }
693