1 /* 2 * Copyright (C) 2018 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 package com.android.car.audio; 17 18 import static com.android.car.internal.ExcludeFromCodeCoverageGeneratedReport.DUMP_INFO; 19 20 import android.annotation.Nullable; 21 import android.car.builtin.util.Slogf; 22 import android.car.media.CarAudioManager; 23 import android.car.media.CarAudioZoneConfigInfo; 24 import android.car.media.CarVolumeGroupEvent; 25 import android.car.media.CarVolumeGroupInfo; 26 import android.media.AudioAttributes; 27 import android.media.AudioDeviceAttributes; 28 import android.media.AudioDeviceInfo; 29 import android.media.AudioPlaybackConfiguration; 30 import android.util.SparseArray; 31 32 import com.android.car.CarLog; 33 import com.android.car.audio.hal.HalAudioDeviceInfo; 34 import com.android.car.internal.ExcludeFromCodeCoverageGeneratedReport; 35 import com.android.car.internal.util.IndentingPrintWriter; 36 import com.android.internal.annotations.GuardedBy; 37 38 import java.util.ArrayList; 39 import java.util.List; 40 import java.util.Objects; 41 42 /** 43 * A class encapsulates an audio zone in car. 44 * 45 * An audio zone can contain multiple {@link CarAudioZoneConfig}s, and each zone has its own 46 * {@link CarAudioFocus} instance. Additionally, there may be dedicated hardware volume keys 47 * attached to each zone. 48 * 49 * See also the unified car_audio_configuration.xml 50 */ 51 public class CarAudioZone { 52 53 private final int mId; 54 private final String mName; 55 private final CarAudioContext mCarAudioContext; 56 private final List<AudioDeviceAttributes> mInputAudioDevice; 57 // zone configuration id to zone configuration mapping 58 // We don't protect mCarAudioZoneConfigs by a lock because it's only written at XML parsing. 59 private final SparseArray<CarAudioZoneConfig> mCarAudioZoneConfigs; 60 private final Object mLock = new Object(); 61 62 @GuardedBy("mLock") 63 private int mCurrentConfigId; 64 CarAudioZone(CarAudioContext carAudioContext, String name, int id)65 CarAudioZone(CarAudioContext carAudioContext, String name, int id) { 66 mCarAudioContext = Objects.requireNonNull(carAudioContext, 67 "Car audio context can not be null"); 68 mName = name; 69 mId = id; 70 mCurrentConfigId = 0; 71 mInputAudioDevice = new ArrayList<>(); 72 mCarAudioZoneConfigs = new SparseArray<>(); 73 } 74 getCurrentConfigId()75 private int getCurrentConfigId() { 76 synchronized (mLock) { 77 return mCurrentConfigId; 78 } 79 } 80 getId()81 int getId() { 82 return mId; 83 } 84 getName()85 String getName() { 86 return mName; 87 } 88 isPrimaryZone()89 boolean isPrimaryZone() { 90 return mId == CarAudioManager.PRIMARY_AUDIO_ZONE; 91 } 92 getCurrentCarAudioZoneConfig()93 CarAudioZoneConfig getCurrentCarAudioZoneConfig() { 94 synchronized (mLock) { 95 return mCarAudioZoneConfigs.get(mCurrentConfigId); 96 } 97 } 98 getAllCarAudioZoneConfigs()99 List<CarAudioZoneConfig> getAllCarAudioZoneConfigs() { 100 List<CarAudioZoneConfig> zoneConfigList = new ArrayList<>(mCarAudioZoneConfigs.size()); 101 for (int index = 0; index < mCarAudioZoneConfigs.size(); index++) { 102 zoneConfigList.add(mCarAudioZoneConfigs.valueAt(index)); 103 } 104 return zoneConfigList; 105 } 106 107 @Nullable getCurrentVolumeGroup(String groupName)108 CarVolumeGroup getCurrentVolumeGroup(String groupName) { 109 return getCurrentCarAudioZoneConfig().getVolumeGroup(groupName); 110 } 111 getCurrentVolumeGroup(int groupId)112 CarVolumeGroup getCurrentVolumeGroup(int groupId) { 113 return getCurrentCarAudioZoneConfig().getVolumeGroup(groupId); 114 } 115 116 /** 117 * @return Snapshot of available {@link AudioDeviceInfo}s in List. 118 */ getCurrentAudioDeviceInfos()119 List<AudioDeviceInfo> getCurrentAudioDeviceInfos() { 120 return getCurrentCarAudioZoneConfig().getAudioDeviceInfos(); 121 } 122 getCurrentAudioDeviceInfosSupportingDynamicMix()123 List<AudioDeviceInfo> getCurrentAudioDeviceInfosSupportingDynamicMix() { 124 return getCurrentCarAudioZoneConfig().getAudioDeviceInfosSupportingDynamicMix(); 125 } 126 getCurrentVolumeGroupCount()127 int getCurrentVolumeGroupCount() { 128 return getCurrentCarAudioZoneConfig().getVolumeGroupCount(); 129 } 130 131 /** 132 * @return Snapshot of available {@link CarVolumeGroup}s in array. 133 */ getCurrentVolumeGroups()134 CarVolumeGroup[] getCurrentVolumeGroups() { 135 return getCurrentCarAudioZoneConfig().getVolumeGroups(); 136 } 137 validateCanUseDynamicMixRouting(boolean useCoreAudioRouting)138 boolean validateCanUseDynamicMixRouting(boolean useCoreAudioRouting) { 139 return getCurrentCarAudioZoneConfig().validateCanUseDynamicMixRouting(useCoreAudioRouting); 140 } 141 142 /** 143 * Constraints applied here: 144 * 145 * <ul> 146 * <li>At least one zone configuration exists. 147 * <li>Current zone configuration exists. 148 * <li>The zone id of all zone configurations matches zone id of the zone. 149 * <li>Exactly one zone configuration is default. 150 * <li>Volume groups for each zone configuration is valid (see 151 * {@link CarAudioZoneConfig#validateVolumeGroups(CarAudioContext, boolean)}). 152 * </ul> 153 */ validateZoneConfigs(boolean useCoreAudioRouting)154 boolean validateZoneConfigs(boolean useCoreAudioRouting) { 155 if (mCarAudioZoneConfigs.size() == 0) { 156 Slogf.w(CarLog.TAG_AUDIO, "No zone configurations for zone %d", mId); 157 return false; 158 } 159 int currentConfigId = getCurrentConfigId(); 160 if (!mCarAudioZoneConfigs.contains(currentConfigId)) { 161 Slogf.w(CarLog.TAG_AUDIO, "Current zone configuration %d for zone %d does not exist", 162 currentConfigId, mId); 163 return false; 164 } 165 boolean isDefaultConfigFound = false; 166 for (int index = 0; index < mCarAudioZoneConfigs.size(); index++) { 167 CarAudioZoneConfig zoneConfig = mCarAudioZoneConfigs.valueAt(index); 168 if (zoneConfig.getZoneId() != mId) { 169 Slogf.w(CarLog.TAG_AUDIO, 170 "Zone id %d of zone configuration %d does not match zone id %d", 171 zoneConfig.getZoneId(), 172 mCarAudioZoneConfigs.keyAt(index), mId); 173 return false; 174 } 175 if (zoneConfig.isDefault()) { 176 if (isDefaultConfigFound) { 177 Slogf.w(CarLog.TAG_AUDIO, 178 "Multiple default zone configurations exist in zone %d", mId); 179 return false; 180 } 181 isDefaultConfigFound = true; 182 } 183 if (!zoneConfig.validateVolumeGroups(mCarAudioContext, 184 useCoreAudioRouting)) { 185 return false; 186 } 187 } 188 if (!isDefaultConfigFound) { 189 Slogf.w(CarLog.TAG_AUDIO, "No default zone configuration exists in zone %d", mId); 190 return false; 191 } 192 return true; 193 } 194 isCurrentZoneConfig(CarAudioZoneConfigInfo configInfoSwitchedTo)195 boolean isCurrentZoneConfig(CarAudioZoneConfigInfo configInfoSwitchedTo) { 196 synchronized (mLock) { 197 return configInfoSwitchedTo.equals(mCarAudioZoneConfigs.get(mCurrentConfigId) 198 .getCarAudioZoneConfigInfo()); 199 } 200 } 201 setCurrentCarZoneConfig(CarAudioZoneConfigInfo configInfoSwitchedTo)202 void setCurrentCarZoneConfig(CarAudioZoneConfigInfo configInfoSwitchedTo) { 203 synchronized (mLock) { 204 mCurrentConfigId = configInfoSwitchedTo.getConfigId(); 205 } 206 } 207 init()208 void init() { 209 for (int index = 0; index < mCarAudioZoneConfigs.size(); index++) { 210 mCarAudioZoneConfigs.valueAt(index).synchronizeCurrentGainIndex(); 211 } 212 } 213 214 @ExcludeFromCodeCoverageGeneratedReport(reason = DUMP_INFO) dump(IndentingPrintWriter writer)215 void dump(IndentingPrintWriter writer) { 216 writer.printf("CarAudioZone(%s:%d) isPrimary? %b\n", mName, mId, 217 isPrimaryZone()); 218 writer.increaseIndent(); 219 writer.printf("Current Config Id: %d\n", getCurrentConfigId()); 220 writer.printf("Input Audio Device Addresses\n"); 221 writer.increaseIndent(); 222 for (int index = 0; index < mInputAudioDevice.size(); index++) { 223 writer.printf("Device Address(%s)\n", mInputAudioDevice.get(index).getAddress()); 224 } 225 writer.decreaseIndent(); 226 writer.println(); 227 writer.printf("Audio Zone Configurations\n"); 228 writer.increaseIndent(); 229 for (int i = 0; i < mCarAudioZoneConfigs.size(); i++) { 230 mCarAudioZoneConfigs.valueAt(i).dump(writer); 231 } 232 writer.decreaseIndent(); 233 writer.println(); 234 writer.decreaseIndent(); 235 } 236 237 /** 238 * Return the audio device address mapping to a car audio context 239 */ getAddressForContext(int audioContext)240 public String getAddressForContext(int audioContext) { 241 mCarAudioContext.preconditionCheckAudioContext(audioContext); 242 String deviceAddress = null; 243 for (CarVolumeGroup volumeGroup : getCurrentVolumeGroups()) { 244 deviceAddress = volumeGroup.getAddressForContext(audioContext); 245 if (deviceAddress != null) { 246 return deviceAddress; 247 } 248 } 249 // This should not happen unless something went wrong. 250 // Device address are unique per zone and all contexts are assigned in a zone. 251 throw new IllegalStateException("Could not find output device in zone " + mId 252 + " for audio context " + audioContext); 253 } 254 getAudioDeviceForContext(int audioContext)255 public AudioDeviceInfo getAudioDeviceForContext(int audioContext) { 256 mCarAudioContext.preconditionCheckAudioContext(audioContext); 257 for (CarVolumeGroup volumeGroup : getCurrentVolumeGroups()) { 258 AudioDeviceInfo deviceInfo = volumeGroup.getAudioDeviceForContext(audioContext); 259 if (deviceInfo != null) { 260 return deviceInfo; 261 } 262 } 263 // This should not happen unless something went wrong. 264 // Device address are unique per zone and all contexts are assigned in a zone. 265 throw new IllegalStateException("Could not find output device in zone " + mId 266 + " for audio context " + audioContext); 267 } 268 269 /** 270 * Update the volume groups for the new user 271 * @param userId user id to update to 272 */ updateVolumeGroupsSettingsForUser(int userId)273 public void updateVolumeGroupsSettingsForUser(int userId) { 274 for (int index = 0; index < mCarAudioZoneConfigs.size(); index++) { 275 mCarAudioZoneConfigs.valueAt(index).updateVolumeGroupsSettingsForUser(userId); 276 } 277 } 278 addInputAudioDevice(AudioDeviceAttributes device)279 void addInputAudioDevice(AudioDeviceAttributes device) { 280 mInputAudioDevice.add(device); 281 } 282 getInputAudioDevices()283 List<AudioDeviceAttributes> getInputAudioDevices() { 284 return mInputAudioDevice; 285 } 286 addZoneConfig(CarAudioZoneConfig zoneConfig)287 void addZoneConfig(CarAudioZoneConfig zoneConfig) { 288 mCarAudioZoneConfigs.put(zoneConfig.getZoneConfigId(), zoneConfig); 289 if (zoneConfig.isDefault()) { 290 synchronized (mLock) { 291 mCurrentConfigId = zoneConfig.getZoneConfigId(); 292 } 293 } 294 } 295 findActiveAudioAttributesFromPlaybackConfigurations( List<AudioPlaybackConfiguration> configurations)296 public List<AudioAttributes> findActiveAudioAttributesFromPlaybackConfigurations( 297 List<AudioPlaybackConfiguration> configurations) { 298 Objects.requireNonNull(configurations, "Audio playback configurations can not be null"); 299 List<AudioAttributes> audioAttributes = new ArrayList<>(); 300 for (int index = 0; index < configurations.size(); index++) { 301 AudioPlaybackConfiguration configuration = configurations.get(index); 302 if (configuration.isActive()) { 303 if (isAudioDeviceInfoValidForZone(configuration.getAudioDeviceInfo())) { 304 // Note that address's context and the context actually supplied could be 305 // different 306 audioAttributes.add(configuration.getAudioAttributes()); 307 } 308 } 309 } 310 return audioAttributes; 311 } 312 isAudioDeviceInfoValidForZone(AudioDeviceInfo info)313 boolean isAudioDeviceInfoValidForZone(AudioDeviceInfo info) { 314 return getCurrentCarAudioZoneConfig().isAudioDeviceInfoValidForZone(info); 315 } 316 onAudioGainChanged(List<Integer> halReasons, List<CarAudioGainConfigInfo> gainInfos)317 List<CarVolumeGroupEvent> onAudioGainChanged(List<Integer> halReasons, 318 List<CarAudioGainConfigInfo> gainInfos) { 319 List<CarVolumeGroupEvent> events = new ArrayList<>(); 320 for (int index = 0; index < mCarAudioZoneConfigs.size(); index++) { 321 List<CarVolumeGroupEvent> eventsForZoneConfig = mCarAudioZoneConfigs.valueAt(index) 322 .onAudioGainChanged(halReasons, gainInfos); 323 // use events for callback only if current zone configuration 324 if (mCarAudioZoneConfigs.keyAt(index) == getCurrentConfigId()) { 325 events.addAll(eventsForZoneConfig); 326 } 327 } 328 return events; 329 } 330 onAudioPortsChanged(List<HalAudioDeviceInfo> deviceInfos)331 List<CarVolumeGroupEvent> onAudioPortsChanged(List<HalAudioDeviceInfo> deviceInfos) { 332 List<CarVolumeGroupEvent> events = new ArrayList<>(); 333 for (int index = 0; index < mCarAudioZoneConfigs.size(); index++) { 334 List<CarVolumeGroupEvent> eventsForZoneConfig = mCarAudioZoneConfigs.valueAt(index) 335 .onAudioPortsChanged(deviceInfos); 336 // Use events for callback only if current zone configuration 337 if (mCarAudioZoneConfigs.keyAt(index) == getCurrentConfigId()) { 338 events.addAll(eventsForZoneConfig); 339 } 340 } 341 return events; 342 } 343 344 /** 345 * Returns the car audio context set for the car audio zone 346 */ getCarAudioContext()347 public CarAudioContext getCarAudioContext() { 348 return mCarAudioContext; 349 } 350 351 /** 352 * Returns the car volume infos for all the volume groups in the audio zone 353 */ getCurrentVolumeGroupInfos()354 List<CarVolumeGroupInfo> getCurrentVolumeGroupInfos() { 355 return getCurrentCarAudioZoneConfig().getVolumeGroupInfos(); 356 } 357 358 /** 359 * Returns all audio zone config info in the audio zone 360 */ getCarAudioZoneConfigInfos()361 List<CarAudioZoneConfigInfo> getCarAudioZoneConfigInfos() { 362 List<CarAudioZoneConfigInfo> zoneConfigInfos = new ArrayList<>(mCarAudioZoneConfigs.size()); 363 for (int index = 0; index < mCarAudioZoneConfigs.size(); index++) { 364 zoneConfigInfos.add(mCarAudioZoneConfigs.valueAt(index).getCarAudioZoneConfigInfo()); 365 } 366 367 return zoneConfigInfos; 368 } 369 } 370