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 21 import static com.android.car.audio.CarAudioUtils.isMicrophoneInputDevice; 22 import static com.android.car.audio.hal.AudioControlWrapper.AUDIOCONTROL_FEATURE_AUDIO_CONFIGURATION; 23 24 import android.hardware.automotive.audiocontrol.AudioDeviceConfiguration; 25 import android.hardware.automotive.audiocontrol.AudioZone; 26 import android.hardware.automotive.audiocontrol.RoutingDeviceConfiguration; 27 import android.media.AudioDeviceAttributes; 28 import android.media.AudioDeviceInfo; 29 import android.media.AudioManager; 30 import android.util.ArraySet; 31 import android.util.Slog; 32 import android.util.SparseArray; 33 import android.util.SparseIntArray; 34 35 import com.android.car.CarLog; 36 import com.android.car.audio.hal.AudioControlWrapper; 37 import com.android.car.internal.util.LocalLog; 38 import com.android.internal.annotations.GuardedBy; 39 40 import java.util.ArrayList; 41 import java.util.List; 42 import java.util.Objects; 43 44 /** 45 * Class to get car audio service configuration from audio control HAL 46 */ 47 final class CarAudioZonesHelperAudioControlHAL implements CarAudioZonesHelper { 48 49 private static final String TAG = CarAudioZonesHelperAudioControlHAL.class.getSimpleName(); 50 51 private final LocalLog mCarAudioLog; 52 private final AudioControlWrapper mAudioControl; 53 private final AudioControlZoneConverter mZoneConverter; 54 private final AudioManagerWrapper mAudioManager; 55 56 private final Object mLock = new Object(); 57 @GuardedBy("mLock") //Use to guard access 58 private final SparseIntArray mAudioZoneIdToOccupantZoneId = new SparseIntArray(); 59 @GuardedBy("mLock") 60 private CarAudioContext mAudioContext; 61 @GuardedBy("mLock") 62 private AudioDeviceConfiguration mAudioDeviceConfiguration; 63 CarAudioZonesHelperAudioControlHAL(AudioControlWrapper wrapper, AudioManagerWrapper audioManager, CarAudioSettings settings, LocalLog serviceLog, boolean useFadeManagerConfiguration)64 CarAudioZonesHelperAudioControlHAL(AudioControlWrapper wrapper, 65 AudioManagerWrapper audioManager, CarAudioSettings settings, LocalLog serviceLog, 66 boolean useFadeManagerConfiguration) { 67 mAudioControl = Objects.requireNonNull(wrapper, "Audio control HAL can not be null"); 68 mAudioManager = Objects.requireNonNull(audioManager, "Audio manager can not be null"); 69 Objects.requireNonNull(settings, "Car audio settings can not be null"); 70 mCarAudioLog = Objects.requireNonNull(serviceLog, "Car audio log can not be null"); 71 mZoneConverter = new AudioControlZoneConverter(audioManager, settings, serviceLog, 72 useFadeManagerConfiguration); 73 mAudioDeviceConfiguration = new AudioDeviceConfiguration(); 74 } 75 76 @Override loadAudioZones()77 public SparseArray<CarAudioZone> loadAudioZones() { 78 var deviceConfigs = getAudioDeviceConfiguration(); 79 if (deviceConfigs.routingConfig == RoutingDeviceConfiguration.DEFAULT_AUDIO_ROUTING) { 80 return new SparseArray<>(); 81 } 82 return initCarAudioZones(deviceConfigs); 83 } 84 getAudioDeviceConfiguration()85 private AudioDeviceConfiguration getAudioDeviceConfiguration() { 86 if (!mAudioControl.supportsFeature(AUDIOCONTROL_FEATURE_AUDIO_CONFIGURATION)) { 87 return getDefaultAudioDeviceConfiguration(); 88 } 89 var deviceConfigs = mAudioControl.getAudioDeviceConfiguration(); 90 if (deviceConfigs == null) { 91 return getDefaultAudioDeviceConfiguration(); 92 } 93 return deviceConfigs; 94 } 95 getDefaultAudioDeviceConfiguration()96 private static AudioDeviceConfiguration getDefaultAudioDeviceConfiguration() { 97 AudioDeviceConfiguration deviceConfiguration = new AudioDeviceConfiguration(); 98 deviceConfiguration.routingConfig = RoutingDeviceConfiguration.DEFAULT_AUDIO_ROUTING; 99 return deviceConfiguration; 100 } 101 initCarAudioZones(AudioDeviceConfiguration deviceConfigs)102 private SparseArray<CarAudioZone> initCarAudioZones(AudioDeviceConfiguration deviceConfigs) { 103 var halAudioZones = mAudioControl.getCarAudioZones(); 104 if (halAudioZones == null || halAudioZones.isEmpty()) { 105 logParsingError("Audio control HAL returned invalid zones"); 106 return new SparseArray<>(); 107 } 108 var zoneIdToZone = new SparseArray<CarAudioZone>(halAudioZones.size()); 109 boolean foundErrors = false; 110 var zoneIdToOccupantZoneId = new SparseIntArray(); 111 var usedInputAddresses = new ArraySet<String>(); 112 for (int c = 0; c < halAudioZones.size(); c++) { 113 var halZone = halAudioZones.get(c); 114 if (halZone == null) { 115 logParsingError("Audio control HAL zones helper found null audio zone"); 116 foundErrors = true; 117 continue; 118 } 119 var carAudioZone = mZoneConverter.convertAudioZone(halZone, deviceConfigs); 120 if (carAudioZone == null) { 121 logParsingError("Audio control HAL zones helper failed to parse audio zone " 122 + halZone.id); 123 foundErrors = true; 124 continue; 125 } 126 usedInputAddresses.addAll(getInputDeviceAddresses(carAudioZone)); 127 if (zoneIdToZone.indexOfKey(carAudioZone.getId()) >= 0) { 128 logParsingError("Audio control HAL zones helper found a repeating audio zone," 129 + " zone id " + halZone.id); 130 foundErrors = true; 131 continue; 132 } 133 // If found errors continue to parse other zones to get errors 134 if (foundErrors) { 135 continue; 136 } 137 zoneIdToZone.put(carAudioZone.getId(), carAudioZone); 138 if (halZone.occupantZoneId != AudioZone.UNASSIGNED_OCCUPANT) { 139 zoneIdToOccupantZoneId.put(carAudioZone.getId(), halZone.occupantZoneId); 140 } 141 } 142 if (foundErrors) { 143 zoneIdToZone.clear(); 144 zoneIdToOccupantZoneId.clear(); 145 } 146 CarAudioZone primaryZone = zoneIdToZone.get(PRIMARY_AUDIO_ZONE); 147 if (primaryZone != null) { 148 synchronized (mLock) { 149 mAudioContext = primaryZone.getCarAudioContext(); 150 mAudioDeviceConfiguration = deviceConfigs; 151 mAudioZoneIdToOccupantZoneId.clear(); 152 for (int c = 0; c < zoneIdToOccupantZoneId.size(); c++) { 153 int zoneId = zoneIdToOccupantZoneId.keyAt(c); 154 int occupantId = zoneIdToOccupantZoneId.valueAt(c); 155 mAudioZoneIdToOccupantZoneId.put(zoneId, occupantId); 156 } 157 } 158 addRemainingInputDevicesToZone(primaryZone, usedInputAddresses); 159 } else { 160 logParsingError("Audio control HAL zones helper could not find primary zone"); 161 zoneIdToZone.clear(); 162 zoneIdToOccupantZoneId.clear(); 163 } 164 165 return zoneIdToZone; 166 } 167 addRemainingInputDevicesToZone(CarAudioZone zone, ArraySet<String> usedAddresses)168 private void addRemainingInputDevicesToZone(CarAudioZone zone, 169 ArraySet<String> usedAddresses) { 170 AudioDeviceInfo[] inputDevices = mAudioManager.getDevices(AudioManager.GET_DEVICES_INPUTS); 171 for (var device : inputDevices) { 172 if (usedAddresses.contains(device.getAddress()) || !isMicrophoneInputDevice(device)) { 173 continue; 174 } 175 zone.addInputAudioDevice(new AudioDeviceAttributes(device)); 176 } 177 } 178 getInputDeviceAddresses(CarAudioZone carAudioZone)179 private static List<String> getInputDeviceAddresses(CarAudioZone carAudioZone) { 180 var inputDevices = carAudioZone.getInputAudioDevices(); 181 var addresses = new ArrayList<String>(inputDevices.size()); 182 for (int c = 0; c < inputDevices.size(); c++) { 183 addresses.add(inputDevices.get(c).getAddress()); 184 } 185 return addresses; 186 } 187 logParsingError(String message)188 private void logParsingError(String message) { 189 Slog.e(CarLog.TAG_AUDIO, message); 190 mCarAudioLog.log(message); 191 } 192 193 @Override getCarAudioContext()194 public CarAudioContext getCarAudioContext() { 195 synchronized (mLock) { 196 return mAudioContext; 197 } 198 } 199 200 @Override getCarAudioZoneIdToOccupantZoneIdMapping()201 public SparseIntArray getCarAudioZoneIdToOccupantZoneIdMapping() { 202 synchronized (mLock) { 203 return mAudioZoneIdToOccupantZoneId; 204 } 205 } 206 207 @Override getMirrorDeviceInfos()208 public List<CarAudioDeviceInfo> getMirrorDeviceInfos() { 209 return mZoneConverter.convertZonesMirroringAudioPorts( 210 mAudioControl.getOutputMirroringDevices()); 211 } 212 213 @Override useCoreAudioRouting()214 public boolean useCoreAudioRouting() { 215 synchronized (mLock) { 216 return mAudioDeviceConfiguration.routingConfig 217 == RoutingDeviceConfiguration.CONFIGURABLE_AUDIO_ENGINE_ROUTING; 218 } 219 } 220 221 @Override useCoreAudioVolume()222 public boolean useCoreAudioVolume() { 223 synchronized (mLock) { 224 return mAudioDeviceConfiguration.useCoreAudioVolume; 225 } 226 } 227 228 @Override useHalDuckingSignalOrDefault(boolean unusedDefaultUseHalDuckingSignal)229 public boolean useHalDuckingSignalOrDefault(boolean unusedDefaultUseHalDuckingSignal) { 230 // Prefer information from HAL over RRO since vendor freeze requires it and this API 231 // enables information directly from vendor 232 synchronized (mLock) { 233 return mAudioDeviceConfiguration.useHalDuckingSignals; 234 } 235 } 236 237 @Override useVolumeGroupMuting()238 public boolean useVolumeGroupMuting() { 239 synchronized (mLock) { 240 return mAudioDeviceConfiguration.useCarVolumeGroupMuting; 241 } 242 } 243 } 244