• 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 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