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