• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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