• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2018 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.feature.Flags.carAudioDynamicDevices;
19 import static android.car.feature.Flags.carAudioMinMaxActivationVolume;
20 import static android.car.feature.Flags.carAudioMuteAmbiguity;
21 import static android.car.media.CarVolumeGroupEvent.EVENT_TYPE_ATTENUATION_CHANGED;
22 import static android.car.media.CarVolumeGroupEvent.EVENT_TYPE_MUTE_CHANGED;
23 import static android.car.media.CarVolumeGroupEvent.EVENT_TYPE_VOLUME_BLOCKED_CHANGED;
24 import static android.car.media.CarVolumeGroupEvent.EVENT_TYPE_VOLUME_GAIN_INDEX_CHANGED;
25 import static android.media.AudioDeviceInfo.TYPE_AUX_LINE;
26 import static android.media.AudioDeviceInfo.TYPE_BLE_BROADCAST;
27 import static android.media.AudioDeviceInfo.TYPE_BLE_HEADSET;
28 import static android.media.AudioDeviceInfo.TYPE_BLE_SPEAKER;
29 import static android.media.AudioDeviceInfo.TYPE_BLUETOOTH_A2DP;
30 import static android.media.AudioDeviceInfo.TYPE_BUILTIN_SPEAKER;
31 import static android.media.AudioDeviceInfo.TYPE_BUS;
32 import static android.media.AudioDeviceInfo.TYPE_HDMI;
33 import static android.media.AudioDeviceInfo.TYPE_USB_ACCESSORY;
34 import static android.media.AudioDeviceInfo.TYPE_USB_DEVICE;
35 import static android.media.AudioDeviceInfo.TYPE_USB_HEADSET;
36 import static android.media.AudioDeviceInfo.TYPE_WIRED_HEADPHONES;
37 import static android.media.AudioDeviceInfo.TYPE_WIRED_HEADSET;
38 
39 import static com.android.car.audio.CarActivationVolumeConfig.ActivationVolumeInvocationType;
40 import static com.android.car.audio.hal.HalAudioGainCallback.reasonToString;
41 import static com.android.car.internal.ExcludeFromCodeCoverageGeneratedReport.BOILERPLATE_CODE;
42 import static com.android.car.internal.ExcludeFromCodeCoverageGeneratedReport.DUMP_INFO;
43 
44 import android.annotation.NonNull;
45 import android.annotation.Nullable;
46 import android.annotation.UserIdInt;
47 import android.car.builtin.util.Slogf;
48 import android.car.media.CarVolumeGroupInfo;
49 import android.media.AudioAttributes;
50 import android.media.AudioDeviceAttributes;
51 import android.media.AudioDeviceInfo;
52 import android.media.AudioManager;
53 import android.os.UserHandle;
54 import android.util.ArrayMap;
55 import android.util.ArraySet;
56 import android.util.SparseArray;
57 import android.util.proto.ProtoOutputStream;
58 
59 import com.android.car.CarLog;
60 import com.android.car.audio.CarAudioContext.AudioContext;
61 import com.android.car.audio.CarAudioDumpProto.CarAudioZoneConfigProto;
62 import com.android.car.audio.CarAudioDumpProto.CarVolumeGroupProto;
63 import com.android.car.audio.CarAudioDumpProto.CarVolumeGroupProto.ContextToAddress;
64 import com.android.car.audio.CarAudioDumpProto.CarVolumeGroupProto.GainInfo;
65 import com.android.car.audio.hal.HalAudioDeviceInfo;
66 import com.android.car.internal.ExcludeFromCodeCoverageGeneratedReport;
67 import com.android.car.internal.util.DebugUtils;
68 import com.android.car.internal.util.IndentingPrintWriter;
69 import com.android.car.internal.util.LocalLog;
70 import com.android.internal.annotations.GuardedBy;
71 import com.android.internal.annotations.VisibleForTesting;
72 import com.android.internal.util.Preconditions;
73 
74 import java.util.ArrayList;
75 import java.util.Arrays;
76 import java.util.List;
77 import java.util.Objects;
78 import java.util.Set;
79 
80 /**
81  * A class encapsulates a volume group in car.
82  *
83  * Interface holding volume interface APIs and also common code for:
84  *
85  * -volume groups using {@link AudioManager#setAudioPortGain} to control the volume
86  * while the audioserver resource config_useFixedVolume is set.
87  *
88  * -volume groups relying on audioserver to control the volume and access using
89  * {@link AudioManager#setVolumeIndexForAttributes(AudioAttributes, int, int)} and all other
90  * related volume APIs.
91  * Gain may either be controlled on hardware amplifier using Audio HAL setaudioPortConfig if the
92  * correlated audio device port defines a gain controller with attribute name="useForVolume" set
93  * or in software using the port id in Audio flinger.
94  * Gains are set only when activity is detected on the given audio device port (Mixer thread, or
95  * {@link android.media.HwAudioSource} realized through a software bridge or hardware bridge.
96  *
97  */
98 /* package */ abstract class CarVolumeGroup {
99     public static final int UNINITIALIZED = -1;
100     private static final int EVENT_LOGGER_QUEUE_SIZE = 50;
101     private static final String TAG = CarLog.tagFor(CarVolumeGroup.class);
102 
103     private final boolean mUseCarVolumeGroupMute;
104     private final boolean mHasCriticalAudioContexts;
105     private final CarAudioSettings mSettingsManager;
106     protected final int mId;
107     private final String mName;
108     protected final int mZoneId;
109     protected final int mConfigId;
110     protected final SparseArray<CarAudioDeviceInfo> mContextToDevices;
111 
112     protected final Object mLock = new Object();
113     private final CarAudioContext mCarAudioContext;
114 
115     private final CarActivationVolumeConfig mCarActivationVolumeConfig;
116 
117     @GuardedBy("mLock")
118     protected final SparseArray<String> mContextToAddress;
119     @GuardedBy("mLock")
120     protected final ArrayMap<String, CarAudioDeviceInfo> mAddressToCarAudioDeviceInfo;
121     @GuardedBy("mLock")
122     protected int mStoredGainIndex;
123 
124     @GuardedBy("mLock")
125     protected int mCurrentGainIndex = UNINITIALIZED;
126 
127     /**
128      * Mute state for requests coming from clients. See {@link #mIsHalMuted} for state of requests
129      * coming from HAL.
130      */
131     @GuardedBy("mLock")
132     protected boolean mIsMuted;
133     @GuardedBy("mLock")
134     protected @UserIdInt int mUserId = UserHandle.CURRENT.getIdentifier();
135 
136     /**
137      * Attenuated gain is set to {@link #UNINITIALIZED} till attenuation explicitly reported by
138      * {@link com.android.car.audio.hal.HalAudioGainCallback#onAudioDeviceGainsChanged} for one or
139      * more {@link android.hardware.automotive.audiocontrol.Reasons}. When the reason is cleared,
140      * it returns back to {@link #UNINITIALIZED}.
141      */
142     @GuardedBy("mLock")
143     protected int mAttenuatedGainIndex = UNINITIALIZED;
144 
145     /**
146      * Limitation gain is set to max gain value till limitation explicitly reported by {@link
147      * com.android.car.audio.hal.HalAudioGainCallback#onAudioDeviceGainsChanged} for one or more
148      * {@link android.hardware.automotive.audiocontrol.Reasons}. When the reason is cleared, it
149      * returns back to max.
150      */
151     @GuardedBy("mLock")
152     protected int mLimitedGainIndex;
153 
154     /**
155      * Blocked gain is set to {@link #UNINITIALIZED} till blocking case explicitly reported by
156      * {@link com.android.car.audio.hal.HalAudioGainCallback#onAudioDeviceGainsChanged} for one or
157      * more {@link android.hardware.automotive.audiocontrol.Reasons}. When the reason is cleared,
158      * it returns back to {@link #UNINITIALIZED}.
159      */
160     @GuardedBy("mLock")
161     protected int mBlockedGainIndex = UNINITIALIZED;
162 
163     /**
164      * The default state of HAL mute is {@code false} until HAL explicitly reports through
165      * {@link com.android.car.audio.hal.HalAudioGainCallback#onAudioDeviceGainsChanged} for one or
166      * more {@link android.hardware.automotive.audiocontrol.Reasons}. When the reason
167      * is cleared, it is reset. See {@link #mIsMuted} for state of requests coming from clients.
168      */
169     @GuardedBy("mLock")
170     private boolean mIsHalMuted = false;
171 
172     /**
173      * Reasons list currently reported for this port by {@link
174      * com.android.car.audio.hal.HalAudioGainCallback#onAudioDeviceGainsChanged}.
175      */
176     protected List<Integer> mReasons = new ArrayList<>();
177 
178     /**
179      * Event logger for volume group changes such as activation volume invocation
180      */
181     protected final LocalLog mEventLogger = new LocalLog(EVENT_LOGGER_QUEUE_SIZE);
182 
CarVolumeGroup(CarAudioContext carAudioContext, CarAudioSettings settingsManager, SparseArray<CarAudioDeviceInfo> contextToDevices, int zoneId, int configId, int volumeGroupId, String name, boolean useCarVolumeGroupMute, CarActivationVolumeConfig carActivationVolumeConfig)183     protected CarVolumeGroup(CarAudioContext carAudioContext, CarAudioSettings settingsManager,
184             SparseArray<CarAudioDeviceInfo> contextToDevices, int zoneId, int configId,
185             int volumeGroupId, String name, boolean useCarVolumeGroupMute,
186             CarActivationVolumeConfig carActivationVolumeConfig) {
187         mSettingsManager = settingsManager;
188         mCarAudioContext = carAudioContext;
189         mContextToDevices = contextToDevices;
190         mZoneId = zoneId;
191         mConfigId = configId;
192         mId = volumeGroupId;
193         mName = Objects.requireNonNull(name, "Volume group name cannot be null");
194         mUseCarVolumeGroupMute = useCarVolumeGroupMute;
195         mContextToAddress = new SparseArray<>(contextToDevices.size());
196         mAddressToCarAudioDeviceInfo = new ArrayMap<>(contextToDevices.size());
197         List<AudioAttributes> volumeAttributes = new ArrayList<>();
198         for (int index = 0; index <  contextToDevices.size(); index++) {
199             int context = contextToDevices.keyAt(index);
200             CarAudioDeviceInfo info = contextToDevices.valueAt(index);
201             List<AudioAttributes> audioAttributes =
202                     Arrays.asList(mCarAudioContext.getAudioAttributesForContext(context));
203             volumeAttributes.addAll(audioAttributes);
204             mContextToAddress.put(context, info.getAddress());
205             mAddressToCarAudioDeviceInfo.put(info.getAddress(), info);
206         }
207 
208         mHasCriticalAudioContexts = containsCriticalAttributes(volumeAttributes);
209         mCarActivationVolumeConfig = Objects.requireNonNull(carActivationVolumeConfig,
210                 "Activation volume config can not be null");
211     }
212 
init()213     void init() {
214         synchronized (mLock) {
215             mStoredGainIndex = mSettingsManager.getStoredVolumeGainIndexForUser(
216                     mUserId, mZoneId, mConfigId, mId);
217             updateCurrentGainIndexLocked();
218         }
219     }
220 
221     @GuardedBy("mLock")
setBlockedLocked(int blockedIndex)222     protected void setBlockedLocked(int blockedIndex) {
223         mBlockedGainIndex = blockedIndex;
224     }
225 
226     @GuardedBy("mLock")
resetBlockedLocked()227     protected void resetBlockedLocked() {
228         setBlockedLocked(UNINITIALIZED);
229     }
230 
231     @GuardedBy("mLock")
isBlockedLocked()232     protected boolean isBlockedLocked() {
233         return mBlockedGainIndex != UNINITIALIZED;
234     }
235 
236     @GuardedBy("mLock")
setLimitLocked(int limitIndex)237     protected void setLimitLocked(int limitIndex) {
238         int minActivationGainIndex = getMinActivationGainIndex();
239         if (limitIndex < minActivationGainIndex) {
240             Slogf.w(CarLog.TAG_AUDIO, "Limit cannot be set lower than min activation volume index",
241                     minActivationGainIndex);
242         }
243         mLimitedGainIndex = limitIndex;
244     }
245 
246     @GuardedBy("mLock")
resetLimitLocked()247     protected void resetLimitLocked() {
248         setLimitLocked(getMaxGainIndex());
249     }
250 
251     @GuardedBy("mLock")
isLimitedLocked()252     protected boolean isLimitedLocked() {
253         return mLimitedGainIndex != getMaxGainIndex();
254     }
255 
256     @GuardedBy("mLock")
isOverLimitLocked()257     protected boolean isOverLimitLocked() {
258         return isOverLimitLocked(mCurrentGainIndex);
259     }
260 
261     @GuardedBy("mLock")
isOverLimitLocked(int index)262     protected boolean isOverLimitLocked(int index) {
263         return isLimitedLocked() && (index > mLimitedGainIndex);
264     }
265 
266     @GuardedBy("mLock")
setAttenuatedGainLocked(int attenuatedGainIndex)267     protected void setAttenuatedGainLocked(int attenuatedGainIndex) {
268         mAttenuatedGainIndex = attenuatedGainIndex;
269     }
270 
271     @GuardedBy("mLock")
resetAttenuationLocked()272     protected void resetAttenuationLocked() {
273         setAttenuatedGainLocked(UNINITIALIZED);
274     }
275 
276     @GuardedBy("mLock")
isAttenuatedLocked()277     protected boolean isAttenuatedLocked() {
278         return mAttenuatedGainIndex != UNINITIALIZED;
279     }
280 
281     @GuardedBy("mLock")
setHalMuteLocked(boolean mute)282     private void setHalMuteLocked(boolean mute) {
283         mIsHalMuted = mute;
284     }
285 
286     @GuardedBy("mLock")
isHalMutedLocked()287     protected boolean isHalMutedLocked() {
288         return mIsHalMuted;
289     }
290 
isHalMuted()291     boolean isHalMuted() {
292         synchronized (mLock) {
293             return isHalMutedLocked();
294         }
295     }
296 
setBlocked(int blockedIndex)297     void setBlocked(int blockedIndex) {
298         synchronized (mLock) {
299             setBlockedLocked(blockedIndex);
300         }
301     }
302 
resetBlocked()303     void resetBlocked() {
304         synchronized (mLock) {
305             resetBlockedLocked();
306         }
307     }
308 
isBlocked()309     boolean isBlocked() {
310         synchronized (mLock) {
311             return isBlockedLocked();
312         }
313     }
314 
setLimit(int limitIndex)315     void setLimit(int limitIndex) {
316         synchronized (mLock) {
317             setLimitLocked(limitIndex);
318         }
319     }
320 
resetLimit()321     void resetLimit() {
322         synchronized (mLock) {
323             resetLimitLocked();
324         }
325     }
326 
isLimited()327     boolean isLimited() {
328         synchronized (mLock) {
329             return isLimitedLocked();
330         }
331     }
332 
isOverLimit()333     boolean isOverLimit() {
334         synchronized (mLock) {
335             return isOverLimitLocked();
336         }
337     }
338 
setAttenuatedGain(int attenuatedGainIndex)339     void setAttenuatedGain(int attenuatedGainIndex) {
340         synchronized (mLock) {
341             setAttenuatedGainLocked(attenuatedGainIndex);
342         }
343     }
344 
resetAttenuation()345     void resetAttenuation() {
346         synchronized (mLock) {
347             resetAttenuationLocked();
348         }
349     }
350 
isAttenuated()351     boolean isAttenuated() {
352         synchronized (mLock) {
353             return isAttenuatedLocked();
354         }
355     }
356 
357     @Nullable
getCarAudioDeviceInfoForAddress(String address)358     CarAudioDeviceInfo getCarAudioDeviceInfoForAddress(String address) {
359         synchronized (mLock) {
360             return mAddressToCarAudioDeviceInfo.get(address);
361         }
362     }
363 
getContexts()364     int[] getContexts() {
365         int[] carAudioContexts = new int[mContextToDevices.size()];
366         for (int i = 0; i < mContextToDevices.size(); i++) {
367             carAudioContexts[i] = mContextToDevices.keyAt(i);
368         }
369         return carAudioContexts;
370     }
371 
getAudioAttributesForContext(int context)372     protected AudioAttributes[] getAudioAttributesForContext(int context) {
373         return mCarAudioContext.getAudioAttributesForContext(context);
374     }
375 
376     /**
377      * Returns the id of the volume group.
378      * <p> Note that all clients are already developed in the way that when they get the number of
379      * volume group, they will then address a given volume group using its id as if the id was the
380      * index of the array of group (aka 0 to length - 1).
381      */
getId()382     int getId() {
383         return mId;
384     }
385 
getName()386     String getName() {
387         return mName;
388     }
389 
390     /**
391      * Returns the devices address for the given context
392      * or {@code null} if the context does not exist in the volume group
393      */
394     @Nullable
getAddressForContext(int audioContext)395     String getAddressForContext(int audioContext) {
396         synchronized (mLock) {
397             return mContextToAddress.get(audioContext);
398         }
399     }
400 
401     /**
402      * Returns the audio devices for the given context
403      * or {@code null} if the context does not exist in the volume group
404      */
405     @Nullable
getAudioDeviceForContext(int audioContext)406     AudioDeviceAttributes getAudioDeviceForContext(int audioContext) {
407         String address = getAddressForContext(audioContext);
408         if (address == null) {
409             return null;
410         }
411 
412         CarAudioDeviceInfo info;
413         synchronized (mLock) {
414             info = mAddressToCarAudioDeviceInfo.get(address);
415         }
416         if (info == null) {
417             return null;
418         }
419 
420         return info.getAudioDevice();
421     }
422 
423     @AudioContext
getContextsForAddress(@onNull String address)424     List<Integer> getContextsForAddress(@NonNull String address) {
425         List<Integer> carAudioContexts = new ArrayList<>();
426         synchronized (mLock) {
427             for (int i = 0; i < mContextToAddress.size(); i++) {
428                 String value = mContextToAddress.valueAt(i);
429                 if (address.equals(value)) {
430                     carAudioContexts.add(mContextToAddress.keyAt(i));
431                 }
432             }
433         }
434         return carAudioContexts;
435     }
436 
getAddresses()437     List<String> getAddresses() {
438         synchronized (mLock) {
439             return new ArrayList<>(mAddressToCarAudioDeviceInfo.keySet());
440         }
441     }
442 
getAllSupportedUsagesForAddress(@onNull String address)443     List<Integer> getAllSupportedUsagesForAddress(@NonNull String address) {
444         List<Integer> supportedUsagesForAddress = new ArrayList<>();
445         List<Integer> contextsForAddress = getContextsForAddress(address);
446         for (int contextIndex = 0; contextIndex < contextsForAddress.size(); contextIndex++) {
447             int contextId = contextsForAddress.get(contextIndex);
448             AudioAttributes[] attributes =
449                     mCarAudioContext.getAudioAttributesForContext(contextId);
450             for (int attrIndex = 0; attrIndex < attributes.length; attrIndex++) {
451                 int usage = attributes[attrIndex].getSystemUsage();
452                 if (!supportedUsagesForAddress.contains(usage)) {
453                     supportedUsagesForAddress.add(usage);
454                 }
455             }
456         }
457         return supportedUsagesForAddress;
458     }
459 
getMaxGainIndex()460     abstract int getMaxGainIndex();
461 
getMinGainIndex()462     abstract int getMinGainIndex();
463 
getMaxActivationGainIndex()464     int getMaxActivationGainIndex() {
465         int maxGainIndex = getMaxGainIndex();
466         int minGainIndex = getMinGainIndex();
467         return minGainIndex + (int) Math.round(
468                 mCarActivationVolumeConfig.getMaxActivationVolumePercentage() / 100.0
469                 * (maxGainIndex - minGainIndex));
470     }
471 
getMinActivationGainIndex()472     int getMinActivationGainIndex() {
473         int maxGainIndex = getMaxGainIndex();
474         int minGainIndex = getMinGainIndex();
475         return minGainIndex + (int) Math.round(
476                 mCarActivationVolumeConfig.getMinActivationVolumePercentage() / 100.0
477                 * (maxGainIndex - minGainIndex));
478     }
479 
getActivationVolumeInvocationType()480     int getActivationVolumeInvocationType() {
481         return mCarActivationVolumeConfig.getInvocationType();
482     }
483 
getCurrentGainIndex()484     int getCurrentGainIndex() {
485         synchronized (mLock) {
486             if (isMutedLocked()) {
487                 return getMinGainIndex();
488             }
489 
490             return getRestrictedGainForIndexLocked(getCurrentGainIndexLocked());
491         }
492     }
493 
494     @GuardedBy("mLock")
getCurrentGainIndexLocked()495     protected int getCurrentGainIndexLocked() {
496         return mCurrentGainIndex;
497     }
498 
499     @GuardedBy("mLock")
getRestrictedGainForIndexLocked(int index)500     protected int getRestrictedGainForIndexLocked(int index) {
501         if (isBlockedLocked()) {
502             return mBlockedGainIndex;
503         }
504         if (isOverLimitLocked()) {
505             return mLimitedGainIndex;
506         }
507         if (isAttenuatedLocked()) {
508             // Need to figure out if attenuation shall be hidden to end user
509             // as while ducked from IAudioControl
510             // TODO(b/) clarify in case of volume adjustment if the reference index is the
511             // ducked index or the current index. Taking current may lead to gap of index > 1.
512             return mAttenuatedGainIndex;
513         }
514         return index;
515     }
516 
517     /**
518      * Sets the gain on this group, gain will be set on all devices within volume group.
519      */
setCurrentGainIndex(int gainIndex)520     void setCurrentGainIndex(int gainIndex) {
521         synchronized (mLock) {
522             int currentgainIndex = gainIndex;
523             Preconditions.checkArgument(isValidGainIndexLocked(gainIndex),
524                     "Gain out of range (%d:%d) index %d", getMinGainIndex(), getMaxGainIndex(),
525                     gainIndex);
526             if (isBlockedLocked()) {
527                 // prevent any volume change while {@link IAudioGainCallback} reported block event.
528                 return;
529             }
530             if (isOverLimitLocked(currentgainIndex)) {
531                 currentgainIndex = mLimitedGainIndex;
532             }
533             if (isAttenuatedLocked()) {
534                 resetAttenuationLocked();
535             }
536             // In case of attenuation/Limitation, requested index is now the new reference for
537             // cached current index.
538             mCurrentGainIndex = currentgainIndex;
539 
540             if (mIsMuted) {
541                 setMuteLocked(false);
542             }
543             setCurrentGainIndexLocked(mCurrentGainIndex);
544         }
545     }
546 
547     @GuardedBy("mLock")
setCurrentGainIndexLocked(int gainIndex)548     protected void setCurrentGainIndexLocked(int gainIndex) {
549         storeGainIndexForUserLocked(gainIndex, mUserId);
550     }
551 
handleActivationVolume( @ctivationVolumeInvocationType int activationVolumeInvocationType)552     boolean handleActivationVolume(
553             @ActivationVolumeInvocationType int activationVolumeInvocationType) {
554         if (!carAudioMinMaxActivationVolume()
555                 || (getActivationVolumeInvocationType() & activationVolumeInvocationType) == 0) {
556             // Min/max activation volume is not invoked if the given invocation type is not allowed
557             // for the volume group.
558             return false;
559         }
560         boolean invokeVolumeGainIndexChanged = true;
561         synchronized (mLock) {
562             int minActivationGainIndex = getMinActivationGainIndex();
563             int maxActivationGainIndex = getMaxActivationGainIndex();
564             int curGainIndex = getCurrentGainIndexLocked();
565             int activationVolume;
566             if (curGainIndex > maxActivationGainIndex) {
567                 activationVolume = maxActivationGainIndex;
568             } else if (curGainIndex < minActivationGainIndex) {
569                 activationVolume = minActivationGainIndex;
570             } else {
571                 return false;
572             }
573             if (isMutedLocked() || isBlockedLocked()) {
574                 invokeVolumeGainIndexChanged = false;
575             } else {
576                 if (isOverLimitLocked(activationVolume)) {
577                     // Limit index is used as min activation gain index if limit is lower than min
578                     // activation gain index.
579                     invokeVolumeGainIndexChanged = !isOverLimitLocked(curGainIndex);
580                 }
581                 if (isAttenuatedLocked()) {
582                     // Attenuation state should be maintained and not reset for min/max activation.
583                     invokeVolumeGainIndexChanged = false;
584                 }
585             }
586             mCurrentGainIndex = activationVolume;
587             setCurrentGainIndexLocked(mCurrentGainIndex);
588             if (invokeVolumeGainIndexChanged) {
589                 String activationVolumeMsg = "Change gain index " + curGainIndex + " to "
590                         + mCurrentGainIndex + " due to min/max activation type "
591                         + activationVolumeInvocationType;
592                 logEvent(activationVolumeMsg);
593             }
594         }
595         return invokeVolumeGainIndexChanged;
596     }
597 
logEvent(String message)598     protected void logEvent(String message) {
599         mEventLogger.log(message);
600         Slogf.d(TAG, message);
601     }
602 
hasCriticalAudioContexts()603     boolean hasCriticalAudioContexts() {
604         return mHasCriticalAudioContexts;
605     }
606 
607     @Override
608     @ExcludeFromCodeCoverageGeneratedReport(reason = BOILERPLATE_CODE)
toString()609     public String toString() {
610         synchronized (mLock) {
611             return "CarVolumeGroup id: " + mId
612                     + " currentGainIndex: " + mCurrentGainIndex
613                     + " contexts: " + Arrays.toString(getContexts())
614                     + " addresses: " + String.join(", ", getAddresses());
615         }
616     }
617 
618     @ExcludeFromCodeCoverageGeneratedReport(reason = DUMP_INFO)
dumpLocked(IndentingPrintWriter writer)619     protected abstract void dumpLocked(IndentingPrintWriter writer);
620 
621     @ExcludeFromCodeCoverageGeneratedReport(reason = DUMP_INFO)
dump(IndentingPrintWriter writer)622     void dump(IndentingPrintWriter writer) {
623         synchronized (mLock) {
624             writer.printf("CarVolumeGroup(%d)\n", mId);
625             writer.increaseIndent();
626             writer.printf("Name(%s)\n", mName);
627             writer.printf("Zone Id(%d)\n", mZoneId);
628             writer.printf("Configuration Id(%d)\n", mConfigId);
629             writer.printf("Is Muted(%b)\n", isMutedLocked());
630             writer.printf("UserId(%d)\n", mUserId);
631             writer.printf("Persist Volume Group Mute(%b)\n",
632                     mSettingsManager.isPersistVolumeGroupMuteEnabled(mUserId));
633             dumpLocked(writer);
634             writer.printf("Gain indexes (min / max / default / current): %d %d %d %d\n",
635                     getMinGainIndex(), getMaxGainIndex(), getDefaultGainIndex(),
636                     mCurrentGainIndex);
637             writer.printf("Activation gain (min index / max index / invocation type): %d %d %d\n",
638                     getMinActivationGainIndex(), getMaxActivationGainIndex(),
639                     getActivationVolumeInvocationType());
640             for (int i = 0; i < mContextToAddress.size(); i++) {
641                 writer.printf("Context: %s -> Address: %s\n",
642                         mCarAudioContext.toString(mContextToAddress.keyAt(i)),
643                         mContextToAddress.valueAt(i));
644             }
645             for (int i = 0; i < mContextToDevices.size(); i++) {
646                 CarAudioDeviceInfo info = mContextToDevices.valueAt(i);
647                 info.dump(writer);
648             }
649             writer.printf("Reported reasons:\n");
650             writer.increaseIndent();
651             for (int index = 0; index < mReasons.size(); index++) {
652                 int reason = mReasons.get(index);
653                 writer.printf("%s\n", reasonToString(reason));
654             }
655             writer.decreaseIndent();
656             writer.printf("Gain infos:\n");
657             writer.increaseIndent();
658             writer.printf(
659                     "Blocked: %b%s\n",
660                     isBlockedLocked(),
661                     (isBlockedLocked() ? " (at: " + mBlockedGainIndex + ")" : ""));
662             writer.printf(
663                     "Limited: %b%s\n",
664                     isLimitedLocked(),
665                     (isLimitedLocked() ? " (at: " + mLimitedGainIndex + ")" : ""));
666             writer.printf(
667                     "Attenuated: %b%s\n",
668                     isAttenuatedLocked(),
669                     (isAttenuatedLocked() ? " (at: " + mAttenuatedGainIndex + ")" : ""));
670             writer.printf("Muted by HAL: %b\n", isHalMutedLocked());
671             writer.decreaseIndent();
672             writer.println("Events:");
673             writer.increaseIndent();
674             mEventLogger.dump(writer);
675             writer.decreaseIndent();
676             // Empty line for comfortable reading
677             writer.println();
678             writer.decreaseIndent();
679         }
680     }
681 
682     @ExcludeFromCodeCoverageGeneratedReport(reason = DUMP_INFO)
dumpProto(ProtoOutputStream proto)683     void dumpProto(ProtoOutputStream proto) {
684         long volumeGroupToken = proto.start(CarAudioZoneConfigProto.VOLUME_GROUPS);
685         synchronized (mLock) {
686             proto.write(CarVolumeGroupProto.ID, mId);
687             proto.write(CarVolumeGroupProto.NAME, mName);
688             proto.write(CarVolumeGroupProto.ZONE_ID, mZoneId);
689             proto.write(CarVolumeGroupProto.CONFIG_ID, mConfigId);
690             proto.write(CarVolumeGroupProto.MUTED, isMutedLocked());
691             proto.write(CarVolumeGroupProto.USER_ID, mUserId);
692             proto.write(CarVolumeGroupProto.PERSIST_VOLUME_GROUP_MUTE_ENABLED,
693                     mSettingsManager.isPersistVolumeGroupMuteEnabled(mUserId));
694 
695             long volumeGainToken = proto.start(CarVolumeGroupProto.VOLUME_GAIN);
696             proto.write(CarAudioDumpProto.CarVolumeGain.MIN_GAIN_INDEX, getMinGainIndex());
697             proto.write(CarAudioDumpProto.CarVolumeGain.MAX_GAIN_INDEX, getMaxGainIndex());
698             proto.write(CarAudioDumpProto.CarVolumeGain.DEFAULT_GAIN_INDEX, getDefaultGainIndex());
699             proto.write(CarAudioDumpProto.CarVolumeGain.CURRENT_GAIN_INDEX, mCurrentGainIndex);
700             proto.write(CarAudioDumpProto.CarVolumeGain.MIN_ACTIVATION_GAIN_INDEX,
701                     getMinActivationGainIndex());
702             proto.write(CarAudioDumpProto.CarVolumeGain.MAX_ACTIVATION_GAIN_INDEX,
703                     getMaxActivationGainIndex());
704             proto.write(CarAudioDumpProto.CarVolumeGain.ACTIVATION_INVOCATION_TYPE,
705                     getActivationVolumeInvocationType());
706             proto.end(volumeGainToken);
707 
708             for (int i = 0; i < mContextToAddress.size(); i++) {
709                 long contextToAddressMappingToken = proto.start(CarVolumeGroupProto
710                         .CONTEXT_TO_ADDRESS_MAPPINGS);
711                 proto.write(ContextToAddress.CONTEXT,
712                         mCarAudioContext.toString(mContextToAddress.keyAt(i)));
713                 proto.write(ContextToAddress.ADDRESS, mContextToAddress.valueAt(i));
714                 proto.end(contextToAddressMappingToken);
715             }
716 
717             for (int i = 0; i < mContextToDevices.size(); i++) {
718                 CarAudioDeviceInfo info = mContextToDevices.valueAt(i);
719                 info.dumpProto(CarVolumeGroupProto.CAR_AUDIO_DEVICE_INFOS, proto);
720             }
721 
722             for (int index = 0; index < mReasons.size(); index++) {
723                 int reason = mReasons.get(index);
724                 proto.write(CarVolumeGroupProto.REPORTED_REASONS, reasonToString(reason));
725             }
726 
727             long gainInfoToken = proto.start(CarVolumeGroupProto.GAIN_INFOS);
728             proto.write(GainInfo.BLOCKED, isBlockedLocked());
729             if (isBlockedLocked()) {
730                 proto.write(GainInfo.BLOCKED_GAIN_INDEX, mBlockedGainIndex);
731             }
732             proto.write(GainInfo.LIMITED, isLimitedLocked());
733             if (isLimitedLocked()) {
734                 proto.write(GainInfo.LIMITED_GAIN_INDEX, mLimitedGainIndex);
735             }
736             proto.write(GainInfo.ATTENUATED, isAttenuatedLocked());
737             if (isAttenuatedLocked()) {
738                 proto.write(GainInfo.ATTENUATED_GAIN_INDEX, mAttenuatedGainIndex);
739             }
740             proto.write(GainInfo.HAL_MUTED, isHalMutedLocked());
741             proto.write(GainInfo.IS_ACTIVE, isActive());
742             proto.end(gainInfoToken);
743 
744         }
745         proto.end(volumeGroupToken);
746     }
747 
loadVolumesSettingsForUser(@serIdInt int userId)748     void loadVolumesSettingsForUser(@UserIdInt int userId) {
749         synchronized (mLock) {
750             //Update the volume for the new user
751             updateUserIdLocked(userId);
752             //Update the current gain index
753             updateCurrentGainIndexLocked();
754             setCurrentGainIndexLocked(getCurrentGainIndexLocked());
755             //Reset devices with current gain index
756             updateGroupMuteLocked();
757         }
758     }
759 
760     /**
761      * Set the mute state of the Volume Group
762      *
763      * @param mute state requested
764      * @return true if mute state has changed, false otherwiser (already set or change not allowed)
765      */
setMute(boolean mute)766     boolean setMute(boolean mute) {
767         synchronized (mLock) {
768             return setMuteLocked(mute);
769         }
770     }
771 
772     @VisibleForTesting
getCarActivationVolumeConfig()773     CarActivationVolumeConfig getCarActivationVolumeConfig() {
774         return mCarActivationVolumeConfig;
775     }
776 
777     @GuardedBy("mLock")
setMuteLocked(boolean mute)778     boolean setMuteLocked(boolean mute) {
779         // If hal mutes the audio devices, then do not allow other incoming requests to unmute.
780         if (!mute && isHalMutedLocked()) {
781             Slogf.e(CarLog.TAG_AUDIO, "Un-mute request cannot be processed due to active "
782                     + "hal mute restriction!");
783             return false;
784         }
785         applyMuteLocked(mute);
786         return saveMuteStateToSettingsLocked(mute);
787     }
788 
789     @GuardedBy("mLock")
saveMuteStateToSettingsLocked(boolean mute)790     protected boolean saveMuteStateToSettingsLocked(boolean mute) {
791         boolean hasChanged = mIsMuted != mute;
792         mIsMuted = mute;
793         if (mSettingsManager.isPersistVolumeGroupMuteEnabled(mUserId)) {
794             mSettingsManager.storeVolumeGroupMuteForUser(mUserId, mZoneId, mConfigId, mId, mute);
795         }
796         return hasChanged;
797     }
798 
799     @GuardedBy("mLock")
applyMuteLocked(boolean mute)800     protected void applyMuteLocked(boolean mute) {
801     }
802 
isMuted()803     boolean isMuted() {
804         synchronized (mLock) {
805             return isMutedLocked();
806         }
807     }
808 
809     @GuardedBy("mLock")
isMutedLocked()810     protected boolean isMutedLocked() {
811         // if either of the mute states is set, it results in group being muted.
812         return isUserMutedLocked() || isHalMutedLocked();
813     }
814 
815     @GuardedBy("mLock")
isUserMutedLocked()816     protected boolean isUserMutedLocked() {
817         return mIsMuted;
818     }
819 
820     @GuardedBy("mLock")
isFullyMutedLocked()821     protected boolean isFullyMutedLocked() {
822         return isUserMutedLocked() || isHalMutedLocked() || isBlockedLocked();
823     }
824 
containsCriticalAttributes(List<AudioAttributes> volumeAttributes)825     private static boolean containsCriticalAttributes(List<AudioAttributes> volumeAttributes) {
826         for (int index = 0; index < volumeAttributes.size(); index++) {
827             if (CarAudioContext.isCriticalAudioAudioAttribute(volumeAttributes.get(index))) {
828                 return true;
829             }
830         }
831         return false;
832     }
833 
834     @GuardedBy("mLock")
updateUserIdLocked(@serIdInt int userId)835     private void updateUserIdLocked(@UserIdInt int userId) {
836         mUserId = userId;
837         mStoredGainIndex = getCurrentGainIndexForUserLocked();
838     }
839 
840     @GuardedBy("mLock")
getCurrentGainIndexForUserLocked()841     private int getCurrentGainIndexForUserLocked() {
842         int gainIndexForUser = mSettingsManager.getStoredVolumeGainIndexForUser(mUserId, mZoneId,
843                 mConfigId, mId);
844         Slogf.i(CarLog.TAG_AUDIO, "updateUserId userId " + mUserId
845                 + " gainIndexForUser " + gainIndexForUser);
846         return gainIndexForUser;
847     }
848 
849     /**
850      * Update the current gain index based on the stored gain index
851      */
852     @GuardedBy("mLock")
updateCurrentGainIndexLocked()853     private void updateCurrentGainIndexLocked() {
854         if (isValidGainIndexLocked(mStoredGainIndex)) {
855             mCurrentGainIndex = mStoredGainIndex;
856         } else {
857             mCurrentGainIndex = getDefaultGainIndex();
858         }
859     }
860 
isValidGainIndex(int gainIndex)861     protected boolean isValidGainIndex(int gainIndex) {
862         synchronized (mLock) {
863             return isValidGainIndexLocked(gainIndex);
864         }
865     }
isValidGainIndexLocked(int gainIndex)866     protected abstract boolean isValidGainIndexLocked(int gainIndex);
867 
getDefaultGainIndex()868     protected abstract int getDefaultGainIndex();
869 
870     @GuardedBy("mLock")
storeGainIndexForUserLocked(int gainIndex, @UserIdInt int userId)871     private void storeGainIndexForUserLocked(int gainIndex, @UserIdInt int userId) {
872         mSettingsManager.storeVolumeGainIndexForUser(userId,
873                 mZoneId, mConfigId, mId, gainIndex);
874     }
875 
876     @GuardedBy("mLock")
updateGroupMuteLocked()877     private void updateGroupMuteLocked() {
878         if (!mUseCarVolumeGroupMute) {
879             return;
880         }
881         if (!mSettingsManager.isPersistVolumeGroupMuteEnabled(mUserId)) {
882             mIsMuted = false;
883             return;
884         }
885         mIsMuted = mSettingsManager.getVolumeGroupMuteForUser(mUserId, mZoneId, mConfigId, mId);
886         applyMuteLocked(isFullyMutedLocked());
887     }
888 
889     /**
890      * Updates volume group states (index, mute, blocked etc) on callback from audio control hal.
891      *
892      * <p>If gain config info carries duplicate info, do not generate events (i.e. eventType = 0)
893      * @param halReasons reasons for change to gain config info
894      * @param gain updated gain config info
895      * @return one or more of {@link android.car.media.CarVolumeGroupEvent.EventTypeEnum} or 0 for
896      * duplicate gain config info
897      */
onAudioGainChanged(List<Integer> halReasons, CarAudioGainConfigInfo gain)898     int onAudioGainChanged(List<Integer> halReasons, CarAudioGainConfigInfo gain) {
899         int eventType = 0;
900         int halIndex = gain.getVolumeIndex();
901         if (getCarAudioDeviceInfoForAddress(gain.getDeviceAddress()) == null
902                 || !isValidGainIndex(halIndex)) {
903             Slogf.e(CarLog.TAG_AUDIO,
904                     "onAudioGainChanged invalid CarAudioGainConfigInfo: " + gain
905                     + " for group id: " + mId);
906             return eventType;
907         }
908         synchronized (mLock) {
909             int previousRestrictedIndex = getRestrictedGainForIndexLocked(mCurrentGainIndex);
910             mReasons = new ArrayList<>(halReasons);
911 
912             boolean shouldBlock = CarAudioGainMonitor.shouldBlockVolumeRequest(halReasons);
913             if ((shouldBlock != isBlockedLocked())
914                     || (shouldBlock && (halIndex != mBlockedGainIndex))) {
915                 setBlockedLocked(shouldBlock ? halIndex : UNINITIALIZED);
916                 eventType |= EVENT_TYPE_VOLUME_BLOCKED_CHANGED;
917             }
918 
919             boolean shouldLimit = CarAudioGainMonitor.shouldLimitVolume(halReasons);
920             if ((shouldLimit != isLimitedLocked())
921                     || (shouldLimit && (halIndex != mLimitedGainIndex))) {
922                 setLimitLocked(shouldLimit ? halIndex : getMaxGainIndex());
923                 eventType |= EVENT_TYPE_ATTENUATION_CHANGED;
924             }
925 
926             boolean shouldDuck = CarAudioGainMonitor.shouldDuckGain(halReasons);
927             if ((shouldDuck != isAttenuatedLocked())
928                     || (shouldDuck && (halIndex != mAttenuatedGainIndex))) {
929                 setAttenuatedGainLocked(shouldDuck ? halIndex : UNINITIALIZED);
930                 eventType |= EVENT_TYPE_ATTENUATION_CHANGED;
931             }
932 
933             // Accept mute callbacks from hal only if group mute is enabled.
934             // If disabled, such callbacks will be considered as blocking restriction only.
935             boolean shouldMute = CarAudioGainMonitor.shouldMuteVolumeGroup(halReasons);
936             if (mUseCarVolumeGroupMute && (shouldMute != isHalMutedLocked())) {
937                 setHalMuteLocked(shouldMute);
938                 eventType |= EVENT_TYPE_MUTE_CHANGED;
939             }
940 
941             if (CarAudioGainMonitor.shouldUpdateVolumeIndex(halReasons)
942                     && (halIndex != getRestrictedGainForIndexLocked(mCurrentGainIndex))) {
943                 mCurrentGainIndex = halIndex;
944                 eventType |= EVENT_TYPE_VOLUME_GAIN_INDEX_CHANGED;
945             }
946 
947             // Blocked/Attenuated index shall have been already apply by Audio HAL on HW.
948             // However, keep in sync & broadcast to all ports this volume group deals with.
949             //
950             // Do not update current gain cache, keep it for restoring rather using reported index
951             // when the event is cleared.
952             int newRestrictedIndex = getRestrictedGainForIndexLocked(mCurrentGainIndex);
953             setCurrentGainIndexLocked(newRestrictedIndex);
954             // Hal or user mute state can change (only user mute enabled while hal muted allowed).
955             // Force a sync of mute application.
956             applyMuteLocked(isFullyMutedLocked());
957 
958             if (newRestrictedIndex != previousRestrictedIndex) {
959                 eventType |= EVENT_TYPE_VOLUME_GAIN_INDEX_CHANGED;
960             }
961         }
962         return eventType;
963     }
964 
getCarVolumeGroupInfo()965     CarVolumeGroupInfo getCarVolumeGroupInfo() {
966         int gainIndex;
967         boolean isMuted;
968         boolean isHalMuted;
969         boolean isBlocked;
970         boolean isAttenuated;
971         synchronized (mLock) {
972             gainIndex = getRestrictedGainForIndexLocked(mCurrentGainIndex);
973             isMuted = isMutedLocked();
974             isHalMuted = isHalMutedLocked();
975             isBlocked = isBlockedLocked();
976             isAttenuated = isAttenuatedLocked() || isLimitedLocked();
977         }
978 
979         String name = mName.isEmpty() ? "group id " + mId : mName;
980 
981         CarVolumeGroupInfo.Builder builder = new CarVolumeGroupInfo.Builder(name, mZoneId, mId)
982                 .setVolumeGainIndex(gainIndex).setMaxVolumeGainIndex(getMaxGainIndex())
983                 .setMinVolumeGainIndex(getMinGainIndex()).setMuted(isMuted).setBlocked(isBlocked)
984                 .setAttenuated(isAttenuated).setAudioAttributes(getAudioAttributes());
985 
986         if (carAudioDynamicDevices()) {
987             builder.setAudioDeviceAttributes(getAudioDeviceAttributes());
988         }
989 
990         if (carAudioMinMaxActivationVolume()) {
991             builder.setMaxActivationVolumeGainIndex(getMaxActivationGainIndex())
992                     .setMinActivationVolumeGainIndex(getMinActivationGainIndex());
993         }
994 
995         if (carAudioMuteAmbiguity()) {
996             builder.setMutedBySystem(isHalMuted);
997         }
998 
999         return builder.build();
1000     }
1001 
getAudioDeviceAttributes()1002     private List<AudioDeviceAttributes> getAudioDeviceAttributes() {
1003         ArraySet<AudioDeviceAttributes> set = new ArraySet<>();
1004         int[] contexts = getContexts();
1005         for (int index = 0; index < contexts.length; index++) {
1006             AudioDeviceAttributes device = getAudioDeviceForContext(contexts[index]);
1007             if (device == null) {
1008                 Slogf.w(CarLog.TAG_AUDIO,
1009                         "getAudioDeviceAttributes: Could not find audio device for context "
1010                                 + mCarAudioContext.toString(contexts[index]));
1011                 continue;
1012             }
1013             set.add(device);
1014         }
1015         return new ArrayList<>(set);
1016     }
1017 
hasAudioAttributes(AudioAttributes audioAttributes)1018     boolean hasAudioAttributes(AudioAttributes audioAttributes) {
1019         synchronized (mLock) {
1020             return mContextToAddress.contains(mCarAudioContext.getContextForAttributes(
1021                     audioAttributes));
1022         }
1023     }
1024 
getAudioAttributes()1025     List<AudioAttributes> getAudioAttributes() {
1026         List<AudioAttributes> audioAttributes = new ArrayList<>();
1027         synchronized (mLock) {
1028             for (int index = 0; index < mContextToAddress.size(); index++) {
1029                 int context = mContextToAddress.keyAt(index);
1030                 AudioAttributes[] contextAttributes =
1031                         mCarAudioContext.getAudioAttributesForContext(context);
1032                 for (int attrIndex = 0; attrIndex < contextAttributes.length; attrIndex++) {
1033                     audioAttributes.add(contextAttributes[attrIndex]);
1034                 }
1035             }
1036         }
1037         return audioAttributes;
1038     }
1039 
1040     /**
1041      * @return one or more {@link android.car.media.CarVolumeGroupEvent.EventTypeEnum}
1042      */
onAudioVolumeGroupChanged(int flags)1043     public int onAudioVolumeGroupChanged(int flags) {
1044         return 0;
1045     }
1046 
1047     /**
1048      * Updates car audio device info with the hal audio device info
1049      */
updateAudioDeviceInfo(HalAudioDeviceInfo halDeviceInfo)1050     void updateAudioDeviceInfo(HalAudioDeviceInfo halDeviceInfo) {
1051         synchronized (mLock) {
1052             CarAudioDeviceInfo info = mAddressToCarAudioDeviceInfo.get(halDeviceInfo.getAddress());
1053             if (info == null) {
1054                 Slogf.w(CarLog.TAG_AUDIO, "No matching car audio device info found for address: %s",
1055                         halDeviceInfo.getAddress());
1056                 return;
1057             }
1058             info.updateAudioDeviceInfo(halDeviceInfo);
1059         }
1060     }
1061 
updateDevices()1062     void updateDevices() {
1063     }
1064 
1065     /**
1066      * Calculates the new gain stages from list of assigned audio device infos
1067      *
1068      * <p>Used to update audio device gain stages dynamically.
1069      *
1070      * @return  one or more of {@link android.car.media.CarVolumeGroupEvent.EventTypeEnum}, or 0 if
1071      * dynamic updates are not supported
1072      */
calculateNewGainStageFromDeviceInfos()1073     int calculateNewGainStageFromDeviceInfos() {
1074         return 0;
1075     }
1076 
isActive()1077     boolean isActive() {
1078         synchronized (mLock) {
1079             for (int c = 0; c < mAddressToCarAudioDeviceInfo.size(); c++) {
1080                 CarAudioDeviceInfo info = mAddressToCarAudioDeviceInfo.valueAt(c);
1081                 if (info.isActive()) {
1082                     continue;
1083                 }
1084                 return false;
1085             }
1086         }
1087         return true;
1088     }
1089 
audioDevicesAdded(List<AudioDeviceInfo> devices)1090     public boolean audioDevicesAdded(List<AudioDeviceInfo> devices) {
1091         Objects.requireNonNull(devices, "Audio devices can not be null");
1092         if (isActive()) {
1093             return false;
1094         }
1095 
1096         boolean updated = false;
1097         for (int c = 0; c < mContextToDevices.size(); c++) {
1098             if (!mContextToDevices.valueAt(c).audioDevicesAdded(devices)) {
1099                 continue;
1100             }
1101             updated = true;
1102         }
1103         if (!updated) {
1104             return false;
1105         }
1106         synchronized (mLock) {
1107             updateAudioDevicesMappingLocked();
1108         }
1109         return true;
1110     }
1111 
audioDevicesRemoved(List<AudioDeviceInfo> devices)1112     public boolean audioDevicesRemoved(List<AudioDeviceInfo> devices) {
1113         Objects.requireNonNull(devices, "Audio devices can not be null");
1114         boolean updated = false;
1115         for (int c = 0; c < mContextToDevices.size(); c++) {
1116             if (!mContextToDevices.valueAt(c).audioDevicesRemoved(devices)) {
1117                 continue;
1118             }
1119             updated = true;
1120         }
1121         if (!updated) {
1122             return false;
1123         }
1124         synchronized (mLock) {
1125             updateAudioDevicesMappingLocked();
1126         }
1127         return true;
1128     }
1129 
1130     @GuardedBy("mLock")
updateAudioDevicesMappingLocked()1131     private void updateAudioDevicesMappingLocked() {
1132         mAddressToCarAudioDeviceInfo.clear();
1133         mContextToAddress.clear();
1134         for (int c = 0; c < mContextToDevices.size(); c++) {
1135             CarAudioDeviceInfo info = mContextToDevices.valueAt(c);
1136             int audioContext = mContextToDevices.keyAt(c);
1137             mAddressToCarAudioDeviceInfo.put(info.getAddress(), info);
1138             mContextToAddress.put(audioContext, info.getAddress());
1139         }
1140     }
1141 
1142     /**
1143      * Determines if device types assign to volume groups are valid based on the following rules:
1144      * <ul>
1145      * <li>Dynamic device types (non BUS) for this group should not appear in the
1146      * {@code dynamicDeviceTypesInConfig} passed in parameter</li>
1147      * <li>Dynamic device types should appear alone in volume group</li>
1148      * </ul>
1149      *
1150      * @param dynamicDeviceTypesInConfig Devices already seen in other volume groups for the same
1151      * configuration, groups checks if the device types for the volume group already exists here
1152      * and return {@code false} if so. Also adds any non-existing device types for the group.
1153      * @return {@code true} if the rules defined above are valid for the group, {@code false}
1154      * otherwise
1155      */
validateDeviceTypes(Set<Integer> dynamicDeviceTypesInConfig)1156     boolean validateDeviceTypes(Set<Integer> dynamicDeviceTypesInConfig) {
1157         List<AudioDeviceAttributes> devices = getAudioDeviceAttributes();
1158         boolean hasNonBusDevice = false;
1159         for (int c = 0; c < devices.size(); c++) {
1160             int deviceType = devices.get(c).getType();
1161             // BUS devices are handled by address name check
1162             if (deviceType == TYPE_BUS) {
1163                 continue;
1164             }
1165             hasNonBusDevice = true;
1166             int convertedType = convertDeviceType(deviceType);
1167             if (dynamicDeviceTypesInConfig.add(convertedType)) {
1168                 continue;
1169             }
1170             Slogf.e(CarLog.TAG_AUDIO, "Car volume groups defined in"
1171                     + " car_audio_configuration.xml shared the dynamic device type "
1172                     + DebugUtils.constantToString(AudioDeviceInfo.class, /* prefix= */ "TYPE_",
1173                     deviceType) + " in multiple volume groups in the same configuration");
1174             return false;
1175         }
1176         if (!hasNonBusDevice || devices.size() == 1) {
1177             return true;
1178         }
1179         Slogf.e(CarLog.TAG_AUDIO, "Car volume group " + getName()
1180                 + " defined in car_audio_configuration.xml"
1181                 + " has multiple devices for a dynamic device group."
1182                 + " Groups with dynamic devices can only have a single device.");
1183         return false;
1184     }
1185 
1186     // Given the current limitation in BT stack where there can only be one BT device available
1187     // of any type, we need to consider all BT types as the same, we are picking TYPE_BLUETOOTH_A2DP
1188     // for verification purposes, could pick any of them.
convertDeviceType(int type)1189     private static int convertDeviceType(int type) {
1190         switch (type) {
1191             case TYPE_BLUETOOTH_A2DP: // fall through
1192             case TYPE_BLE_HEADSET: // fall through
1193             case TYPE_BLE_SPEAKER: // fall through
1194             case TYPE_BLE_BROADCAST:
1195                 return TYPE_BLUETOOTH_A2DP;
1196             case TYPE_BUILTIN_SPEAKER: // fall through
1197             case TYPE_WIRED_HEADSET: // fall through
1198             case TYPE_WIRED_HEADPHONES: // fall through
1199             case TYPE_HDMI: // fall through
1200             case TYPE_USB_ACCESSORY: // fall through
1201             case TYPE_USB_DEVICE: // fall through
1202             case TYPE_USB_HEADSET: // fall through
1203             case TYPE_AUX_LINE: // fall through
1204             case TYPE_BUS:
1205             default:
1206                 return type;
1207         }
1208     }
1209 }
1210