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_VOLUME_GAIN_INDEX_CHANGED; 19 import static android.car.media.CarVolumeGroupEvent.EVENT_TYPE_VOLUME_MAX_INDEX_CHANGED; 20 import static android.car.media.CarVolumeGroupEvent.EVENT_TYPE_VOLUME_MIN_INDEX_CHANGED; 21 22 import static com.android.car.internal.ExcludeFromCodeCoverageGeneratedReport.DUMP_INFO; 23 24 import android.util.SparseArray; 25 26 import com.android.car.internal.ExcludeFromCodeCoverageGeneratedReport; 27 import com.android.car.internal.util.IndentingPrintWriter; 28 import com.android.internal.annotations.GuardedBy; 29 import com.android.internal.util.Preconditions; 30 31 /** 32 * A class encapsulates a volume group in car. 33 * 34 * Volume in a car is controlled by group. A group holds one or more car audio contexts. 35 * Call {@link CarAudioManager#getVolumeGroupCount()} to get the count of {@link CarVolumeGroup} 36 * supported in a car. 37 */ 38 final class CarAudioVolumeGroup extends CarVolumeGroup { 39 private static final int UNSET_STEP_SIZE = -1; 40 private static final int EVENT_TYPE_NONE = 0; 41 @GuardedBy("mLock") 42 private int mDefaultGain = Integer.MAX_VALUE; 43 @GuardedBy("mLock") 44 private int mMaxGain = Integer.MIN_VALUE; 45 @GuardedBy("mLock") 46 private int mMinGain = Integer.MAX_VALUE; 47 @GuardedBy("mLock") 48 private int mStepSize = UNSET_STEP_SIZE; 49 CarAudioVolumeGroup(CarAudioContext carAudioContext, CarAudioSettings settingsManager, SparseArray<CarAudioDeviceInfo> contextToDeviceInfo, int zoneId, int configId, int volumeGroupId, String name, int stepSize, int defaultGain, int minGain, int maxGain, boolean useCarVolumeGroupMute, CarActivationVolumeConfig carActivationVolumeConfig)50 CarAudioVolumeGroup(CarAudioContext carAudioContext, 51 CarAudioSettings settingsManager, 52 SparseArray<CarAudioDeviceInfo> contextToDeviceInfo, int zoneId, int configId, 53 int volumeGroupId, String name, int stepSize, int defaultGain, int minGain, int maxGain, 54 boolean useCarVolumeGroupMute, CarActivationVolumeConfig carActivationVolumeConfig) { 55 super(carAudioContext, settingsManager, contextToDeviceInfo, zoneId, configId, 56 volumeGroupId, name, useCarVolumeGroupMute, carActivationVolumeConfig); 57 Preconditions.checkArgument(stepSize != 0, "Step Size must not be zero"); 58 mStepSize = stepSize; 59 mDefaultGain = defaultGain; 60 mMinGain = minGain; 61 mMaxGain = maxGain; 62 mLimitedGainIndex = getIndexForGainLocked(mMaxGain); 63 } 64 65 @Override getMaxGainIndex()66 public int getMaxGainIndex() { 67 synchronized (mLock) { 68 return getIndexForGainLocked(mMaxGain); 69 } 70 } 71 72 @Override getMinGainIndex()73 public int getMinGainIndex() { 74 synchronized (mLock) { 75 return getIndexForGainLocked(mMinGain); 76 } 77 } 78 79 @Override 80 @GuardedBy("mLock") 81 @SuppressWarnings("GuardedBy") setCurrentGainIndexLocked(int gainIndex)82 protected void setCurrentGainIndexLocked(int gainIndex) { 83 int gainInMillibels = getGainForIndexLocked(gainIndex); 84 for (int index = 0; index < mAddressToCarAudioDeviceInfo.size(); index++) { 85 CarAudioDeviceInfo info = mAddressToCarAudioDeviceInfo.valueAt(index); 86 info.setCurrentGain(gainInMillibels); 87 } 88 super.setCurrentGainIndexLocked(gainIndex); 89 } 90 91 @Override 92 @GuardedBy("mLock") isValidGainIndexLocked(int gainIndex)93 protected boolean isValidGainIndexLocked(int gainIndex) { 94 return gainIndex >= getIndexForGainLocked(mMinGain) 95 && gainIndex <= getIndexForGainLocked(mMaxGain); 96 } 97 98 @Override 99 @ExcludeFromCodeCoverageGeneratedReport(reason = DUMP_INFO) 100 @GuardedBy("mLock") dumpLocked(IndentingPrintWriter writer)101 protected void dumpLocked(IndentingPrintWriter writer) { 102 writer.printf("Step size: %d\n", mStepSize); 103 writer.printf("Gain values (min / max / default/ current): %d %d %d %d\n", mMinGain, 104 mMaxGain, mDefaultGain, getGainForIndexLocked(mCurrentGainIndex)); 105 } 106 107 @Override getDefaultGainIndex()108 protected int getDefaultGainIndex() { 109 synchronized (mLock) { 110 return getIndexForGainLocked(mDefaultGain); 111 } 112 } 113 114 @GuardedBy("mLock") getGainForIndexLocked(int gainIndex)115 private int getGainForIndexLocked(int gainIndex) { 116 return mMinGain + gainIndex * mStepSize; 117 } 118 119 @GuardedBy("mLock") getIndexForGainLocked(int gainInMillibel)120 private int getIndexForGainLocked(int gainInMillibel) { 121 return (gainInMillibel - mMinGain) / mStepSize; 122 } 123 124 @Override calculateNewGainStageFromDeviceInfos()125 int calculateNewGainStageFromDeviceInfos() { 126 int minGain = Integer.MAX_VALUE; 127 int maxGain = Integer.MIN_VALUE; 128 int defaultGain = Integer.MAX_VALUE; 129 int stepSize = UNSET_STEP_SIZE; 130 131 int eventType = EVENT_TYPE_NONE; 132 synchronized (mLock) { 133 // compute the new volume group gain stage from scratch 134 for (int index = 0; index < mAddressToCarAudioDeviceInfo.size(); index++) { 135 CarAudioDeviceInfo info = mAddressToCarAudioDeviceInfo.valueAt(index); 136 if (stepSize == UNSET_STEP_SIZE) { 137 stepSize = info.getStepValue(); 138 } else { 139 Preconditions.checkArgument( 140 info.getStepValue() == stepSize, 141 "Gain stages within one group must have same step value"); 142 } 143 if (info.getDefaultGain() < defaultGain) { 144 // We're arbitrarily selecting the lowest 145 // device default gain as the group's default. 146 defaultGain = info.getDefaultGain(); 147 } 148 if (info.getMaxGain() > maxGain) { 149 maxGain = info.getMaxGain(); 150 } 151 if (info.getMinGain() < minGain) { 152 minGain = info.getMinGain(); 153 } 154 } 155 156 // update the new gain stage and return event types so that callback can be triggered. 157 // get the current and restricted gains in mb before updating the volume bounds. These 158 // will be used for extrapolating to new volume ranges 159 int epCurrentGainInMb = getGainForIndexLocked(getCurrentGainIndexLocked()); 160 int epLimitedGainInMb = getGainForIndexLocked(mLimitedGainIndex); 161 int epBlockedGainInMb = getGainForIndexLocked(mBlockedGainIndex); 162 int epAttenuatedGainInMb = getGainForIndexLocked(mAttenuatedGainIndex); 163 boolean isLimited = isLimitedLocked(); 164 165 // update the volume ranges 166 if (minGain != mMinGain) { 167 mMinGain = minGain; 168 eventType |= (EVENT_TYPE_VOLUME_GAIN_INDEX_CHANGED 169 | EVENT_TYPE_VOLUME_MIN_INDEX_CHANGED); 170 } 171 if (maxGain != mMaxGain) { 172 mMaxGain = maxGain; 173 eventType |= (EVENT_TYPE_VOLUME_GAIN_INDEX_CHANGED 174 | EVENT_TYPE_VOLUME_MAX_INDEX_CHANGED); 175 } 176 // if only default gain changes, no impact to volume gain stages (i.e. no event). 177 if (defaultGain != mDefaultGain) { 178 mDefaultGain = defaultGain; 179 } 180 if (stepSize != mStepSize) { 181 mStepSize = stepSize; 182 eventType |= (EVENT_TYPE_VOLUME_GAIN_INDEX_CHANGED 183 | EVENT_TYPE_VOLUME_MAX_INDEX_CHANGED); 184 } 185 // if no change to indexes, return 186 if (eventType == EVENT_TYPE_NONE) { 187 return eventType; 188 } 189 190 // if min/max/step values change, we shall try to maintain the same volume level 191 // through simple extrapolation, i.e., the new gain index is calculated from the 192 // previous {@code mCurrentGainIndex} in millibels. 193 // caution: before updating, check if the previous gain is out of bound in the 194 // new gain stage. If yes, use the safe value (i.e default gain) 195 // provided by the hal implementations. 196 mCurrentGainIndex = getIndexForGainLocked(epCurrentGainInMb); 197 if (!isValidGainIndexLocked(mCurrentGainIndex)) { 198 mCurrentGainIndex = getIndexForGainLocked(mDefaultGain); 199 } 200 201 // similar extrapolation is tried for restricted gains: limited, blocked, attenuated. 202 // Note: Even after best effort, it is possible that some or all of the old restriction 203 // indexes are invalid and therefore reset. 204 int newLimitedGainIndex = getIndexForGainLocked(epLimitedGainInMb); 205 if (isLimited && isValidGainIndexLocked(newLimitedGainIndex)) { 206 setLimitLocked(newLimitedGainIndex); 207 } else { 208 resetLimitLocked(); 209 } 210 211 int newBlockedGainIndex = getIndexForGainLocked(epBlockedGainInMb); 212 if (isBlockedLocked() && isValidGainIndexLocked(newBlockedGainIndex)) { 213 setBlockedLocked(newBlockedGainIndex); 214 } else { 215 resetBlockedLocked(); 216 } 217 218 int newAttenuatedGainIndex = getIndexForGainLocked(epAttenuatedGainInMb); 219 if (isAttenuatedLocked() && isValidGainIndexLocked(newAttenuatedGainIndex)) { 220 setAttenuatedGainLocked(newAttenuatedGainIndex); 221 } else { 222 resetAttenuationLocked(); 223 } 224 225 // Notes: 226 // (1) Setting current gain index will trigger Audio HAL. If restrictions are still 227 // valid but were reset above, we expect AudioControl HAL to resend the restrictions 228 // in a callback. 229 // (2) Audio HAL will be responsible to ensure consistent speaker output during this 230 // transition. 231 setCurrentGainIndexLocked(getRestrictedGainForIndexLocked(mCurrentGainIndex)); 232 } 233 return eventType; 234 } 235 } 236