1 /* 2 * Copyright (C) 2023 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 android.car.media.CarVolumeGroupEvent.EVENT_TYPE_ATTENUATION_CHANGED; 19 import static android.car.media.CarVolumeGroupEvent.EVENT_TYPE_MUTE_CHANGED; 20 import static android.car.media.CarVolumeGroupEvent.EVENT_TYPE_VOLUME_GAIN_INDEX_CHANGED; 21 import static android.media.AudioManager.ADJUST_MUTE; 22 import static android.media.AudioManager.ADJUST_UNMUTE; 23 24 import static com.android.car.CarLog.TAG_AUDIO; 25 import static com.android.car.audio.CoreAudioHelper.getProductStrategyForAudioAttributes; 26 import static com.android.car.internal.ExcludeFromCodeCoverageGeneratedReport.DUMP_INFO; 27 28 import android.car.builtin.util.Slogf; 29 import android.media.AudioAttributes; 30 import android.media.AudioDeviceAttributes; 31 import android.media.AudioManager; 32 import android.media.audiopolicy.AudioProductStrategy; 33 import android.util.ArraySet; 34 import android.util.SparseArray; 35 36 import com.android.car.internal.ExcludeFromCodeCoverageGeneratedReport; 37 import com.android.car.internal.util.IndentingPrintWriter; 38 import com.android.internal.annotations.GuardedBy; 39 40 import java.util.Arrays; 41 import java.util.List; 42 43 /** 44 * A class encapsulates a volume group in car. 45 * 46 * Volume in a car is controlled by group. A group holds one or more car audio contexts. 47 * Call {@link CarAudioManager#getVolumeGroupCount()} to get the count of {@link CarVolumeGroup} 48 * supported in a car. 49 */ 50 final class CoreAudioVolumeGroup extends CarVolumeGroup { 51 static final String TAG = TAG_AUDIO + ".CoreAudioVolumeGroup"; 52 public static final int EMPTY_FLAGS = 0; 53 /** 54 * For all volume operations, attributes are required 55 */ 56 private final AudioAttributes mAudioAttributes; 57 private final AudioManagerWrapper mAudioManager; 58 @GuardedBy("mLock") 59 private int mAmCurrentGainIndex; 60 private final int mAmId; 61 private final int mDefaultGainIndex; 62 private final int mMaxGainIndex; 63 private final int mMinGainIndex; 64 private boolean mAmGroupMuted; 65 private int mAmLastAudibleGainIndex; 66 CoreAudioVolumeGroup(AudioManagerWrapper audioManager, CarAudioContext carAudioContext, CarAudioSettings settingsManager, SparseArray<CarAudioDeviceInfo> contextToDevices, int zoneId, int configId, int volumeGroupId, String name, boolean useCarVolumeGroupMute, CarActivationVolumeConfig carActivationVolumeConfig)67 CoreAudioVolumeGroup(AudioManagerWrapper audioManager, CarAudioContext carAudioContext, 68 CarAudioSettings settingsManager, SparseArray<CarAudioDeviceInfo> contextToDevices, 69 int zoneId, int configId, int volumeGroupId, String name, 70 boolean useCarVolumeGroupMute, CarActivationVolumeConfig carActivationVolumeConfig) { 71 super(carAudioContext, settingsManager, contextToDevices, zoneId, configId, volumeGroupId, 72 name, useCarVolumeGroupMute, carActivationVolumeConfig); 73 mAudioManager = audioManager; 74 mAudioAttributes = CoreAudioHelper.selectAttributesForVolumeGroupName(name); 75 mAmId = CoreAudioHelper.getVolumeGroupIdForAudioAttributes(mAudioAttributes); 76 mAmCurrentGainIndex = getAmCurrentGainIndex(); 77 mMinGainIndex = mAudioManager.getMinVolumeIndexForAttributes(mAudioAttributes); 78 mMaxGainIndex = mAudioManager.getMaxVolumeIndexForAttributes(mAudioAttributes); 79 mAmGroupMuted = isAmGroupMuted(); 80 mAmLastAudibleGainIndex = getAmLastAudibleIndex(); 81 // Unfortunately core groups do not have defaults 82 mDefaultGainIndex = (mMaxGainIndex - mMinGainIndex) / 3 + mMinGainIndex; 83 mLimitedGainIndex = mMaxGainIndex; 84 } 85 86 @Override getMaxGainIndex()87 public int getMaxGainIndex() { 88 return mMaxGainIndex; 89 } 90 91 @Override getMinGainIndex()92 public int getMinGainIndex() { 93 synchronized (mLock) { 94 return mMinGainIndex; 95 } 96 } 97 getAmCurrentGainIndex()98 int getAmCurrentGainIndex() { 99 synchronized (mLock) { 100 return mAudioManager.getVolumeIndexForAttributes(mAudioAttributes); 101 } 102 } 103 isAmGroupMuted()104 boolean isAmGroupMuted() { 105 return mAudioManager.isVolumeGroupMuted(mAmId); 106 } 107 getAmLastAudibleIndex()108 int getAmLastAudibleIndex() { 109 return mAudioManager.getLastAudibleVolumeForVolumeGroup(mAmId); 110 } 111 112 @Override 113 @GuardedBy("mLock") 114 @SuppressWarnings("GuardedBy") setCurrentGainIndexLocked(int gainIndex)115 protected void setCurrentGainIndexLocked(int gainIndex) { 116 // TODO(b/260298113): wait for orthogonal mute/volume in AM to bypass check of muted state 117 if (!isBlockedLocked() && getAmLastAudibleIndex() != gainIndex) { 118 setCurrentGainIndexLocked(gainIndex, /* canChangeMuteState= */ false); 119 } 120 super.setCurrentGainIndexLocked(gainIndex); 121 } 122 123 @GuardedBy("mLock") 124 @SuppressWarnings("GuardedBy") setCurrentGainIndexLocked(int gainIndex, boolean canChangeMuteState)125 private void setCurrentGainIndexLocked(int gainIndex, boolean canChangeMuteState) { 126 int flags = 0; 127 if (canChangeMuteState || !isUserMutedLocked()) { 128 mAudioManager.setVolumeGroupVolumeIndex(mAmId, gainIndex, flags); 129 } 130 } 131 132 @Override 133 @GuardedBy("mLock") 134 @SuppressWarnings("GuardedBy") isValidGainIndexLocked(int gainIndex)135 protected boolean isValidGainIndexLocked(int gainIndex) { 136 return gainIndex >= mMinGainIndex && gainIndex <= mMaxGainIndex; 137 } 138 139 /** 140 * As per AudioService logic, a mutable group is a group which min index is zero 141 * @return true if group is mutable, false otherwise 142 */ isMutable()143 private boolean isMutable() { 144 return mMinGainIndex == 0; 145 } 146 147 @Override 148 @GuardedBy("mLock") 149 @SuppressWarnings("GuardedBy") applyMuteLocked(boolean mute)150 protected void applyMuteLocked(boolean mute) { 151 if (!isMutable()) { 152 return; 153 } 154 if (isAmGroupMuted() != mute) { 155 if (mute) { 156 mAudioManager.adjustVolumeGroupVolume(mAmId, ADJUST_MUTE, EMPTY_FLAGS); 157 } else if (!isBlockedLocked() && !isHalMutedLocked()) { 158 // Unmute shall not break any pending attenuation / limitation 159 int index = getRestrictedGainForIndexLocked(getCurrentGainIndexLocked()); 160 // Sync index if needed before unmuting 161 if (getAmLastAudibleIndex() != index) { 162 setCurrentGainIndexLocked(index, /* canChangeMuteState= */ true); 163 } 164 // TODO(b/260298113): index 0 mutes Am Group, wait for orthogonal mute/volume in AM 165 mAudioManager.adjustVolumeGroupVolume(mAmId, ADJUST_UNMUTE, EMPTY_FLAGS); 166 } 167 } 168 } 169 170 /** 171 * Allowing using {@link AudioManager#setVolumeIndexForAudioAttributes} implies that some volume 172 * change may not be originated by CarAudioManager itself. 173 * Hence, it requires synchronization of the indexes. 174 */ 175 @Override 176 @SuppressWarnings("GuardedBy") onAudioVolumeGroupChanged(int flags)177 public int onAudioVolumeGroupChanged(int flags) { 178 int returnedFlags = 0; 179 synchronized (mLock) { 180 int previousAudibleIndex = getRestrictedGainForIndexLocked(mCurrentGainIndex); 181 int previousIndex = isMutedLocked() ? getMinGainIndex() : previousAudibleIndex; 182 mAmGroupMuted = isAmGroupMuted(); 183 mAmLastAudibleGainIndex = getAmLastAudibleIndex(); 184 mAmCurrentGainIndex = getAmCurrentGainIndex(); 185 boolean volumeChanged = previousIndex != mAmCurrentGainIndex; 186 boolean muteChanged = mAmGroupMuted != isUserMutedLocked(); 187 boolean lastAudibleIndexChanged = previousAudibleIndex != mAmLastAudibleGainIndex; 188 if (isBlockedLocked()) { 189 if (!mAmGroupMuted) { 190 Slogf.i(TAG, "onAudioVolumeGroupChanged group(%s) blocked by HAL," 191 + " unmuted on AudioManager, force mute sync", getName()); 192 applyMuteLocked(/* mute= */ true); 193 } 194 Slogf.i(TAG, "onAudioVolumeGroupChanged group(%s) volume change not " 195 + "permitted while blocked changes reported by Gain callback", getName()); 196 return returnedFlags; 197 } 198 if (!muteChanged && !lastAudibleIndexChanged && !volumeChanged) { 199 Slogf.i(TAG, "onAudioVolumeGroupChanged no change for group(%s).", getName()); 200 return returnedFlags; 201 } 202 // check first if need to align mute state with AudioService 203 returnedFlags |= syncMuteState(); 204 if (returnedFlags != 0 || isUserMutedLocked()) { 205 return returnedFlags; 206 } 207 returnedFlags |= syncGainIndex(); 208 } 209 return returnedFlags; 210 } 211 212 @SuppressWarnings("GuardedBy") syncMuteState()213 int syncMuteState() { 214 int returnedFlags = 0; 215 synchronized (mLock) { 216 boolean isAmMutedByVolumeZero = mAmGroupMuted && (mAmLastAudibleGainIndex == 0); 217 if (isUserMutedLocked() != mAmGroupMuted && !isAmMutedByVolumeZero) { 218 Slogf.i(TAG, "syncMuteState group(%s) muted(%b) synced from AudioService", 219 getName(), mAmGroupMuted); 220 super.setMuteLocked(mAmGroupMuted); 221 returnedFlags |= EVENT_TYPE_MUTE_CHANGED; 222 // When unmuting, ensure not breaking restrictions 223 if (!mAmGroupMuted) { 224 // If thermal/attenuation while muted, am reports same index before restriction 225 if (mAmCurrentGainIndex == mCurrentGainIndex) { 226 if (isOverLimitLocked(mAmCurrentGainIndex)) { 227 setCurrentGainIndexLocked(mLimitedGainIndex, 228 /* canChangeMuteState= */ false); 229 } else if (isAttenuatedLocked()) { 230 setCurrentGainIndexLocked(mAttenuatedGainIndex, 231 /* canChangeMuteState= */ false); 232 } 233 } else { 234 returnedFlags |= syncGainIndex(); 235 } 236 } 237 } 238 if (isAmMutedByVolumeZero) { 239 if (getCurrentGainIndex() != 0) { 240 Slogf.i(TAG, "syncMuteState group(%s) muted at 0 on AM, sync index", getName()); 241 mCurrentGainIndex = 0; 242 returnedFlags |= 243 isFullyMutedLocked() ? 0 : EVENT_TYPE_VOLUME_GAIN_INDEX_CHANGED; 244 } 245 if (!isFullyMutedLocked()) { 246 applyMuteLocked(/* mute= */ false); 247 } 248 } 249 } 250 return returnedFlags; 251 } 252 syncGainIndex()253 int syncGainIndex() { 254 int returnedFlags = 0; 255 synchronized (mLock) { 256 // check if a limitation or ducking is active to prevent sync with am index 257 if (isOverLimitLocked(mAmCurrentGainIndex)) { 258 Slogf.i(TAG, "syncGainIndex group(%s) index(%d) over limit(%d)", getName(), 259 mAmCurrentGainIndex, 260 mLimitedGainIndex); 261 // AM Reports an overlimitation, if not already at the limit, set index as limit. 262 if (mCurrentGainIndex != mLimitedGainIndex) { 263 mCurrentGainIndex = mLimitedGainIndex; 264 returnedFlags |= EVENT_TYPE_VOLUME_GAIN_INDEX_CHANGED; 265 } 266 // Force a volume sync on AudioManager as overlimit 267 setCurrentGainIndexLocked(mLimitedGainIndex, /* canChangeMuteState= */ false); 268 } else if (isAttenuatedLocked() && mAmCurrentGainIndex != mAttenuatedGainIndex) { 269 Slogf.i(TAG, "syncGainIndex group(%s) index(%d) reset attenuation(%d)", 270 getName(), mAmCurrentGainIndex, mAttenuatedGainIndex); 271 // reset attenuation 272 resetAttenuationLocked(); 273 // Refresh the current gain with new AudioManager index 274 mCurrentGainIndex = mAmCurrentGainIndex; 275 returnedFlags |= 276 EVENT_TYPE_VOLUME_GAIN_INDEX_CHANGED | EVENT_TYPE_ATTENUATION_CHANGED; 277 } else if (getRestrictedGainForIndexLocked(mCurrentGainIndex) != mAmCurrentGainIndex) { 278 Slogf.i(TAG, "syncGainIndex group(%s) index(%d) synced from AudioService", 279 getName(), 280 mAmCurrentGainIndex); 281 // Refresh the current gain with new AudioManager index 282 mCurrentGainIndex = mAmCurrentGainIndex; 283 returnedFlags |= EVENT_TYPE_VOLUME_GAIN_INDEX_CHANGED; 284 } else { 285 Slogf.i(TAG, "syncGainIndex group(%s) index(%d) ack from AudioService", getName(), 286 mAmCurrentGainIndex); 287 } 288 } 289 return returnedFlags; 290 } 291 292 @Override updateDevices(boolean useCoreAudioRouting)293 void updateDevices(boolean useCoreAudioRouting) { 294 // If not using core audio routing, than device need to be updated to match the information 295 // for audio attributes to core volume groups. 296 if (useCoreAudioRouting) { 297 return; 298 } 299 300 int[] contexts = getContexts(); 301 for (int c = 0; c < contexts.length; c++) { 302 int context = contexts[c]; 303 AudioAttributes[] audioAttributes = getAudioAttributesForContext(context); 304 AudioDeviceAttributes device = getAudioDeviceForContext(context); 305 setPreferredDeviceForAudioAttribute(Arrays.asList(audioAttributes), device); 306 } 307 308 } 309 setPreferredDeviceForAudioAttribute(List<AudioAttributes> audioAttributes, AudioDeviceAttributes audioDeviceAttributes)310 private void setPreferredDeviceForAudioAttribute(List<AudioAttributes> audioAttributes, 311 AudioDeviceAttributes audioDeviceAttributes) { 312 ArraySet<Integer> strategiesSet = new ArraySet<>(); 313 for (int c = 0; c < audioAttributes.size(); c++) { 314 AudioProductStrategy strategy = 315 getProductStrategyForAudioAttributes(audioAttributes.get(c)); 316 if (strategy == null) { 317 continue; 318 } 319 if (!strategiesSet.add(strategy.getId())) { 320 continue; 321 } 322 mAudioManager.setPreferredDeviceForStrategy(strategy, audioDeviceAttributes); 323 } 324 } 325 326 327 @Override getDefaultGainIndex()328 protected int getDefaultGainIndex() { 329 return mDefaultGainIndex; 330 } 331 332 @Override 333 @ExcludeFromCodeCoverageGeneratedReport(reason = DUMP_INFO) 334 @GuardedBy("mLock") dumpLocked(IndentingPrintWriter writer)335 protected void dumpLocked(IndentingPrintWriter writer) { 336 writer.printf("AudioManager Gain index (current): %d\n", mAmCurrentGainIndex); 337 } 338 } 339