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