• 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 com.android.car.audio.hal.HalAudioGainCallback.reasonToString;
19 import static com.android.car.internal.ExcludeFromCodeCoverageGeneratedReport.BOILERPLATE_CODE;
20 import static com.android.car.internal.ExcludeFromCodeCoverageGeneratedReport.DUMP_INFO;
21 
22 import android.annotation.NonNull;
23 import android.annotation.Nullable;
24 import android.annotation.UserIdInt;
25 import android.car.builtin.util.Slogf;
26 import android.car.media.CarAudioManager;
27 import android.car.media.CarVolumeGroupInfo;
28 import android.media.AudioAttributes;
29 import android.media.AudioDeviceInfo;
30 import android.os.UserHandle;
31 import android.util.ArrayMap;
32 import android.util.SparseArray;
33 
34 import com.android.car.CarLog;
35 import com.android.car.audio.CarAudioContext.AudioContext;
36 import com.android.car.internal.ExcludeFromCodeCoverageGeneratedReport;
37 import com.android.car.internal.util.IndentingPrintWriter;
38 import com.android.internal.annotations.GuardedBy;
39 import com.android.internal.annotations.VisibleForTesting;
40 import com.android.internal.util.Preconditions;
41 
42 import java.util.ArrayList;
43 import java.util.Arrays;
44 import java.util.List;
45 import java.util.Objects;
46 
47 /**
48  * A class encapsulates a volume group in car.
49  *
50  * Volume in a car is controlled by group. A group holds one or more car audio contexts.
51  * Call {@link CarAudioManager#getVolumeGroupCount()} to get the count of {@link CarVolumeGroup}
52  * supported in a car.
53  */
54 /* package */ final class CarVolumeGroup {
55 
56     public static final int UNINITIALIZED = -1;
57 
58     private final boolean mUseCarVolumeGroupMute;
59     private final boolean mHasCriticalAudioContexts;
60     private final CarAudioSettings mSettingsManager;
61     private final int mDefaultGain;
62     private final int mId;
63     private final int mMaxGain;
64     private final int mMinGain;
65     private final int mStepSize;
66     private final int mZoneId;
67     private final SparseArray<String> mContextToAddress;
68     private final ArrayMap<String, CarAudioDeviceInfo> mAddressToCarAudioDeviceInfo;
69 
70     private final Object mLock = new Object();
71     private final CarAudioContext mCarAudioContext;
72 
73     @GuardedBy("mLock")
74     private int mStoredGainIndex;
75 
76     @GuardedBy("mLock")
77     private int mCurrentGainIndex = UNINITIALIZED;
78 
79     @GuardedBy("mLock")
80     private boolean mIsMuted;
81     @GuardedBy("mLock")
82     private @UserIdInt int mUserId = UserHandle.CURRENT.getIdentifier();
83 
84     /**
85      * Attenuated gain is set to {@see CarAudioDeviceInfo#UNINITIALIZED} till attenuation explicitly
86      * reported by {@see HalAudioGainCallback#onAudioDeviceGainsChanged} for one or more {@see
87      * android.hardware.automotive.audiocontrol#Reasons}. When the reason is cleared, it returns
88      * back to {@see CarAudioDeviceInfo#UNINITIALIZED}.
89      */
90     @GuardedBy("mLock")
91     private int mAttenuatedGainIndex = UNINITIALIZED;
92 
93     /**
94      * Limitation gain is set to max gain value till limitation explicitly reported by {@see
95      * HalAudioGainCallback#onAudioDeviceGainsChanged} for one or more {@see
96      * android.hardware.automotive.audiocontrol#Reasons}. When the reason is cleared, it returns
97      * back to max.
98      */
99     @GuardedBy("mLock")
100     private int mLimitedGainIndex;
101 
102     /**
103      * Blocked gain is set to {@see CarAudioDeviceInfo#UNINITIALIZED} till blocking case explicitly
104      * reported by {@see HalAudioGainCallback#onAudioDeviceGainsChanged} for one or more {@see
105      * android.hardware.automotive.audiocontrol#Reasons}. When the reason is cleared, it returns
106      * back to {@see CarAudioDeviceInfo#UNINITIALIZED}.
107      */
108     @GuardedBy("mLock")
109     private int mBlockedGainIndex = UNINITIALIZED;
110 
111     /**
112      * Reasons list currently reported for this port by {@see
113      * HalAudioGainCallback#onAudioDeviceGainsChanged}.
114      */
115     private List<Integer> mReasons = new ArrayList<>();
116 
CarVolumeGroup(CarAudioContext carAudioContext, CarAudioSettings settingsManager, SparseArray<String> contextToAddress, ArrayMap<String, CarAudioDeviceInfo> addressToCarAudioDeviceInfo, int zoneId, int id, int stepSize, int defaultGain, int minGain, int maxGain, boolean useCarVolumeGroupMute)117     private CarVolumeGroup(CarAudioContext carAudioContext, CarAudioSettings settingsManager,
118             SparseArray<String> contextToAddress, ArrayMap<String,
119             CarAudioDeviceInfo> addressToCarAudioDeviceInfo, int zoneId, int id, int stepSize,
120             int defaultGain, int minGain, int maxGain, boolean useCarVolumeGroupMute) {
121 
122         mSettingsManager = settingsManager;
123         mContextToAddress = contextToAddress;
124         mAddressToCarAudioDeviceInfo = addressToCarAudioDeviceInfo;
125         mCarAudioContext = carAudioContext;
126         mZoneId = zoneId;
127         mId = id;
128         mStepSize = stepSize;
129         mDefaultGain = defaultGain;
130         mMinGain = minGain;
131         mMaxGain = maxGain;
132         mLimitedGainIndex = getIndexForGain(mMaxGain);
133         mUseCarVolumeGroupMute = useCarVolumeGroupMute;
134         List<AudioAttributes> volumeAttributes = new ArrayList<>();
135         for (int index = 0; index <  contextToAddress.size(); index++) {
136             int context = contextToAddress.keyAt(index);
137             List<AudioAttributes> audioAttributes =
138                     Arrays.asList(mCarAudioContext.getAudioAttributesForContext(context));
139             volumeAttributes.addAll(audioAttributes);
140         }
141 
142         mHasCriticalAudioContexts = containsCriticalAttributes(volumeAttributes);
143     }
144 
init()145     void init() {
146         synchronized (mLock) {
147             mStoredGainIndex = mSettingsManager.getStoredVolumeGainIndexForUser(
148                     mUserId, mZoneId, mId);
149             updateCurrentGainIndexLocked();
150         }
151     }
152 
153     @GuardedBy("mLock")
setBlockedLocked(int blockedIndex)154     private void setBlockedLocked(int blockedIndex) {
155         mBlockedGainIndex = blockedIndex;
156     }
157 
158     @GuardedBy("mLock")
resetBlockedLocked()159     private void resetBlockedLocked() {
160         setBlockedLocked(UNINITIALIZED);
161     }
162 
163     @GuardedBy("mLock")
isBlockedLocked()164     private boolean isBlockedLocked() {
165         return mBlockedGainIndex != UNINITIALIZED;
166     }
167 
168     @GuardedBy("mLock")
setLimitLocked(int limitIndex)169     private void setLimitLocked(int limitIndex) {
170         mLimitedGainIndex = limitIndex;
171     }
172 
173     @GuardedBy("mLock")
resetLimitLocked()174     private void resetLimitLocked() {
175         setLimitLocked(getIndexForGain(mMaxGain));
176     }
177 
178     @GuardedBy("mLock")
isLimitedLocked()179     private boolean isLimitedLocked() {
180         return mLimitedGainIndex != getIndexForGain(mMaxGain);
181     }
182 
183     @GuardedBy("mLock")
isOverLimitLocked()184     private boolean isOverLimitLocked() {
185         return isOverLimitLocked(mCurrentGainIndex);
186     }
187 
188     @GuardedBy("mLock")
isOverLimitLocked(int index)189     private boolean isOverLimitLocked(int index) {
190         return isLimitedLocked() && (index > mLimitedGainIndex);
191     }
192 
193     @GuardedBy("mLock")
setAttenuatedGainLocked(int attenuatedGainIndex)194     private void setAttenuatedGainLocked(int attenuatedGainIndex) {
195         mAttenuatedGainIndex = attenuatedGainIndex;
196     }
197 
198     @GuardedBy("mLock")
resetAttenuationLocked()199     private void resetAttenuationLocked() {
200         setAttenuatedGainLocked(UNINITIALIZED);
201     }
202 
203     @GuardedBy("mLock")
isAttenuatedLocked()204     private boolean isAttenuatedLocked() {
205         return mAttenuatedGainIndex != UNINITIALIZED;
206     }
207 
setBlocked(int blockedIndex)208     void setBlocked(int blockedIndex) {
209         synchronized (mLock) {
210             setBlockedLocked(blockedIndex);
211         }
212     }
213 
resetBlocked()214     void resetBlocked() {
215         synchronized (mLock) {
216             resetBlockedLocked();
217         }
218     }
219 
isBlocked()220     boolean isBlocked() {
221         synchronized (mLock) {
222             return isBlockedLocked();
223         }
224     }
225 
setLimit(int limitIndex)226     void setLimit(int limitIndex) {
227         synchronized (mLock) {
228             setLimitLocked(limitIndex);
229         }
230     }
231 
resetLimit()232     void resetLimit() {
233         synchronized (mLock) {
234             resetLimitLocked();
235         }
236     }
237 
isLimited()238     boolean isLimited() {
239         synchronized (mLock) {
240             return isLimitedLocked();
241         }
242     }
243 
isOverLimit()244     boolean isOverLimit() {
245         synchronized (mLock) {
246             return isOverLimitLocked();
247         }
248     }
249 
setAttenuatedGain(int attenuatedGainIndex)250     void setAttenuatedGain(int attenuatedGainIndex) {
251         synchronized (mLock) {
252             setAttenuatedGainLocked(attenuatedGainIndex);
253         }
254     }
255 
resetAttenuation()256     void resetAttenuation() {
257         synchronized (mLock) {
258             resetAttenuationLocked();
259         }
260     }
261 
isAttenuated()262     boolean isAttenuated() {
263         synchronized (mLock) {
264             return isAttenuatedLocked();
265         }
266     }
267 
268     @Nullable
getCarAudioDeviceInfoForAddress(String address)269     CarAudioDeviceInfo getCarAudioDeviceInfoForAddress(String address) {
270         return mAddressToCarAudioDeviceInfo.get(address);
271     }
272 
273     @AudioContext
getContexts()274     int[] getContexts() {
275         final int[] carAudioContexts = new int[mContextToAddress.size()];
276         for (int i = 0; i < carAudioContexts.length; i++) {
277             carAudioContexts[i] = mContextToAddress.keyAt(i);
278         }
279         return carAudioContexts;
280     }
281 
282     /**
283      * Returns the devices address for the given context
284      * or {@code null} if the context does not exist in the volume group
285      */
286     @Nullable
getAddressForContext(int audioContext)287     String getAddressForContext(int audioContext) {
288         return mContextToAddress.get(audioContext);
289     }
290 
291     /**
292      * Returns the audio devices for the given context
293      * or {@code null} if the context does not exist in the volume group
294      */
295     @Nullable
getAudioDeviceForContext(int audioContext)296     AudioDeviceInfo getAudioDeviceForContext(int audioContext) {
297         String address = getAddressForContext(audioContext);
298         if (address == null) {
299             return null;
300         }
301 
302         CarAudioDeviceInfo info = mAddressToCarAudioDeviceInfo.get(address);
303         if (info == null) {
304             return null;
305         }
306 
307         return info.getAudioDeviceInfo();
308     }
309 
310     @AudioContext
getContextsForAddress(@onNull String address)311     List<Integer> getContextsForAddress(@NonNull String address) {
312         List<Integer> carAudioContexts = new ArrayList<>();
313         for (int i = 0; i < mContextToAddress.size(); i++) {
314             String value = mContextToAddress.valueAt(i);
315             if (address.equals(value)) {
316                 carAudioContexts.add(mContextToAddress.keyAt(i));
317             }
318         }
319         return carAudioContexts;
320     }
321 
getAddresses()322     List<String> getAddresses() {
323         return new ArrayList<>(mAddressToCarAudioDeviceInfo.keySet());
324     }
325 
getMaxGainIndex()326     int getMaxGainIndex() {
327         synchronized (mLock) {
328             return getIndexForGain(mMaxGain);
329         }
330     }
331 
getMinGainIndex()332     int getMinGainIndex() {
333         synchronized (mLock) {
334             return getIndexForGain(mMinGain);
335         }
336     }
337 
getCurrentGainIndex()338     int getCurrentGainIndex() {
339         synchronized (mLock) {
340             if (mIsMuted) {
341                 return getIndexForGain(mMinGain);
342             }
343             if (isBlockedLocked()) {
344                 return mBlockedGainIndex;
345             }
346             if (isAttenuatedLocked()) {
347                 // Need to figure out if attenuation shall be hidden to end user
348                 // as while ducked from IAudioControl
349                 // Also, keep unchanged current index / use it as a cache of previous value
350                 //
351                 // TODO(b/) clarify in case of volume adjustment if the reference index is the
352                 // ducked index or the current index. Taking current may lead to gap of index > 1.
353                 return mAttenuatedGainIndex;
354             }
355             if (isOverLimitLocked()) {
356                 return mLimitedGainIndex;
357             }
358             return getCurrentGainIndexLocked();
359         }
360     }
361 
362     @GuardedBy("mLock")
getCurrentGainIndexLocked()363     private int getCurrentGainIndexLocked() {
364         return mCurrentGainIndex;
365     }
366 
367     /**
368      * Sets the gain on this group, gain will be set on all devices within volume group.
369      */
setCurrentGainIndex(int gainIndex)370     void setCurrentGainIndex(int gainIndex) {
371         Preconditions.checkArgument(isValidGainIndex(gainIndex),
372                 "Gain out of range (%d:%d) index %d", mMinGain, mMaxGain, gainIndex);
373         synchronized (mLock) {
374             if (isBlockedLocked()) {
375                 // prevent any volume change while {@link IAudioGainCallback} reported block event.
376                 // TODO(b/) callback mecanism to inform HMI/User of failure and reason why if needed
377                 return;
378             }
379             if (isOverLimitLocked(gainIndex)) {
380                 // TODO(b/) callback to inform if over limit index and why if needed.
381                 gainIndex = mLimitedGainIndex;
382             }
383             if (isAttenuatedLocked()) {
384                 resetAttenuationLocked();
385             }
386             if (mIsMuted) {
387                 setMuteLocked(false);
388             }
389             // In case of attenuation/Limitation, requested index is now the new reference for
390             // cached current index.
391             mCurrentGainIndex = gainIndex;
392 
393             setCurrentGainIndexLocked(mCurrentGainIndex);
394         }
395     }
396 
397     @GuardedBy("mLock")
setCurrentGainIndexLocked(int gainIndex)398     private void setCurrentGainIndexLocked(int gainIndex) {
399         int gainInMillibels = getGainForIndex(gainIndex);
400         for (int index = 0; index < mAddressToCarAudioDeviceInfo.size(); index++) {
401             CarAudioDeviceInfo info = mAddressToCarAudioDeviceInfo.valueAt(index);
402             info.setCurrentGain(gainInMillibels);
403         }
404         storeGainIndexForUserLocked(gainIndex, mUserId);
405     }
406 
hasCriticalAudioContexts()407     boolean hasCriticalAudioContexts() {
408         return mHasCriticalAudioContexts;
409     }
410 
411     @Override
412     @ExcludeFromCodeCoverageGeneratedReport(reason = BOILERPLATE_CODE)
toString()413     public String toString() {
414         synchronized (mLock) {
415             return "CarVolumeGroup id: " + mId
416                     + " currentGainIndex: " + mCurrentGainIndex
417                     + " contexts: " + Arrays.toString(getContexts())
418                     + " addresses: " + String.join(", ", getAddresses());
419         }
420     }
421 
422     @ExcludeFromCodeCoverageGeneratedReport(reason = DUMP_INFO)
dump(IndentingPrintWriter writer)423     void dump(IndentingPrintWriter writer) {
424         synchronized (mLock) {
425             writer.printf("CarVolumeGroup(%d)\n", mId);
426             writer.increaseIndent();
427             writer.printf("Zone Id(%b)\n", mZoneId);
428             writer.printf("Is Muted(%b)\n", mIsMuted);
429             writer.printf("UserId(%d)\n", mUserId);
430             writer.printf("Persist Volume Group Mute(%b)\n",
431                     mSettingsManager.isPersistVolumeGroupMuteEnabled(mUserId));
432             writer.printf("Step size: %d\n", mStepSize);
433             writer.printf("Gain values (min / max / default/ current): %d %d %d %d\n", mMinGain,
434                     mMaxGain, mDefaultGain, getGainForIndex(mCurrentGainIndex));
435             writer.printf("Gain indexes (min / max / default / current): %d %d %d %d\n",
436                     getMinGainIndex(), getMaxGainIndex(), getDefaultGainIndex(), mCurrentGainIndex);
437             for (int i = 0; i < mContextToAddress.size(); i++) {
438                 writer.printf("Context: %s -> Address: %s\n",
439                         mCarAudioContext.toString(mContextToAddress.keyAt(i)),
440                         mContextToAddress.valueAt(i));
441             }
442             mAddressToCarAudioDeviceInfo.keySet().stream()
443                     .map(mAddressToCarAudioDeviceInfo::get)
444                     .forEach((info -> info.dump(writer)));
445             writer.printf("Reported reasons:\n");
446             writer.increaseIndent();
447             for (int index = 0; index < mReasons.size(); index++) {
448                 int reason = mReasons.get(index);
449                 writer.printf("%s\n", reasonToString(reason));
450             }
451             writer.decreaseIndent();
452             writer.printf("Gain infos:\n");
453             writer.increaseIndent();
454             writer.printf(
455                     "Blocked: %b%s\n",
456                     isBlockedLocked(),
457                     (isBlockedLocked() ? " (at: " + mBlockedGainIndex + ")" : ""));
458             writer.printf(
459                     "Limited: %b%s\n",
460                     isLimitedLocked(),
461                     (isLimitedLocked() ? " (at: " + mLimitedGainIndex + ")" : ""));
462             writer.printf(
463                     "Attenuated: %b%s\n",
464                     isAttenuatedLocked(),
465                     (isAttenuatedLocked() ? " (at: " + mAttenuatedGainIndex + ")" : ""));
466             writer.decreaseIndent();
467             // Empty line for comfortable reading
468             writer.println();
469             writer.decreaseIndent();
470         }
471     }
472 
loadVolumesSettingsForUser(@serIdInt int userId)473     void loadVolumesSettingsForUser(@UserIdInt int userId) {
474         synchronized (mLock) {
475             //Update the volume for the new user
476             updateUserIdLocked(userId);
477             //Update the current gain index
478             updateCurrentGainIndexLocked();
479             setCurrentGainIndexLocked(getCurrentGainIndexLocked());
480             //Reset devices with current gain index
481             updateGroupMuteLocked();
482         }
483     }
484 
setMute(boolean mute)485     void setMute(boolean mute) {
486         synchronized (mLock) {
487             setMuteLocked(mute);
488         }
489     }
490 
491     @GuardedBy("mLock")
setMuteLocked(boolean mute)492     private void setMuteLocked(boolean mute) {
493         mIsMuted = mute;
494         if (mSettingsManager.isPersistVolumeGroupMuteEnabled(mUserId)) {
495             mSettingsManager.storeVolumeGroupMuteForUser(mUserId, mZoneId, mId, mute);
496         }
497     }
498 
isMuted()499     boolean isMuted() {
500         synchronized (mLock) {
501             return mIsMuted;
502         }
503     }
504 
containsCriticalAttributes(List<AudioAttributes> volumeAttributes)505     private static boolean containsCriticalAttributes(List<AudioAttributes> volumeAttributes) {
506         for (int index = 0; index < volumeAttributes.size(); index++) {
507             if (CarAudioContext.isCriticalAudioAudioAttribute(volumeAttributes.get(index))) {
508                 return true;
509             }
510         }
511         return false;
512     }
513 
514     @GuardedBy("mLock")
updateUserIdLocked(@serIdInt int userId)515     private void updateUserIdLocked(@UserIdInt int userId) {
516         mUserId = userId;
517         mStoredGainIndex = getCurrentGainIndexForUserLocked();
518     }
519 
520     @GuardedBy("mLock")
getCurrentGainIndexForUserLocked()521     private int getCurrentGainIndexForUserLocked() {
522         int gainIndexForUser = mSettingsManager.getStoredVolumeGainIndexForUser(mUserId, mZoneId,
523                 mId);
524         Slogf.i(CarLog.TAG_AUDIO, "updateUserId userId " + mUserId
525                 + " gainIndexForUser " + gainIndexForUser);
526         return gainIndexForUser;
527     }
528 
529     /**
530      * Update the current gain index based on the stored gain index
531      */
532     @GuardedBy("mLock")
updateCurrentGainIndexLocked()533     private void updateCurrentGainIndexLocked() {
534         if (isValidGainIndex(mStoredGainIndex)) {
535             mCurrentGainIndex = mStoredGainIndex;
536         } else {
537             mCurrentGainIndex = getIndexForGain(mDefaultGain);
538         }
539     }
540 
isValidGainIndex(int gainIndex)541     private boolean isValidGainIndex(int gainIndex) {
542         return gainIndex >= getIndexForGain(mMinGain)
543                 && gainIndex <= getIndexForGain(mMaxGain);
544     }
545 
getDefaultGainIndex()546     private int getDefaultGainIndex() {
547         synchronized (mLock) {
548             return getIndexForGain(mDefaultGain);
549         }
550     }
551 
552     @GuardedBy("mLock")
storeGainIndexForUserLocked(int gainIndex, @UserIdInt int userId)553     private void storeGainIndexForUserLocked(int gainIndex, @UserIdInt int userId) {
554         mSettingsManager.storeVolumeGainIndexForUser(userId,
555                 mZoneId, mId, gainIndex);
556     }
557 
getGainForIndex(int gainIndex)558     private int getGainForIndex(int gainIndex) {
559         return mMinGain + gainIndex * mStepSize;
560     }
561 
getIndexForGain(int gainInMillibel)562     private int getIndexForGain(int gainInMillibel) {
563         return (gainInMillibel - mMinGain) / mStepSize;
564     }
565 
566     @GuardedBy("mLock")
updateGroupMuteLocked()567     private void updateGroupMuteLocked() {
568         if (!mUseCarVolumeGroupMute) {
569             return;
570         }
571         if (!mSettingsManager.isPersistVolumeGroupMuteEnabled(mUserId)) {
572             mIsMuted = false;
573             return;
574         }
575         mIsMuted = mSettingsManager.getVolumeGroupMuteForUser(mUserId, mZoneId, mId);
576     }
577 
onAudioGainChanged(List<Integer> halReasons, CarAudioGainConfigInfo gain)578     void onAudioGainChanged(List<Integer> halReasons, CarAudioGainConfigInfo gain) {
579         if (getCarAudioDeviceInfoForAddress(gain.getDeviceAddress()) == null) {
580             Slogf.e(CarLog.TAG_AUDIO,
581                     "onAudioGainChanged no port found for address %s on group %d",
582                     gain.getDeviceAddress(),
583                     mId);
584             return;
585         }
586         synchronized (mLock) {
587             mReasons = new ArrayList<>(halReasons);
588             int halIndex = gain.getVolumeIndex();
589             if (CarAudioGainMonitor.shouldBlockVolumeRequest(halReasons)) {
590                 setBlockedLocked(halIndex);
591             } else {
592                 resetBlockedLocked();
593             }
594             if (CarAudioGainMonitor.shouldLimitVolume(halReasons)) {
595                 setLimitLocked(halIndex);
596             } else {
597                 resetLimitLocked();
598             }
599             if (CarAudioGainMonitor.shouldDuckGain(halReasons)) {
600                 setAttenuatedGainLocked(halIndex);
601             } else {
602                 resetAttenuationLocked();
603             }
604             int indexToBroadCast = mCurrentGainIndex;
605             if (isBlockedLocked()) {
606                 indexToBroadCast = mBlockedGainIndex;
607             } else if (isAttenuatedLocked()) {
608                 indexToBroadCast = mAttenuatedGainIndex;
609             } else if (isOverLimitLocked()) {
610                 // TODO(b/) callback to inform if over limit index and why if needed.
611                 indexToBroadCast = mLimitedGainIndex;
612             }
613             // Blocked/Attenuated index shall have been already apply by Audio HAL on HW.
614             // However, keep in sync & broadcast to all ports this volume group deals with.
615             //
616             // Do not update current gain cache, keep it for restoring rather using reported index
617             // when the event is cleared.
618             setCurrentGainIndexLocked(indexToBroadCast);
619         }
620     }
621 
getCarVolumeGroupInfo()622     CarVolumeGroupInfo getCarVolumeGroupInfo() {
623         int gainIndex;
624         boolean isMuted;
625         boolean isBlocked;
626         boolean isAttenuated;
627         synchronized (mLock) {
628             gainIndex = mCurrentGainIndex;
629             isMuted = mIsMuted;
630             isBlocked = isBlockedLocked();
631             isAttenuated = isAttenuatedLocked();
632         }
633 
634         return new CarVolumeGroupInfo.Builder("group id " + mId, mZoneId, mId)
635                 .setVolumeGainIndex(gainIndex).setMaxVolumeGainIndex(getMaxGainIndex())
636                 .setMinVolumeGainIndex(getMinGainIndex()).setMuted(isMuted).setBlocked(isBlocked)
637                 .setAttenuated(isAttenuated).build();
638     }
639 
getAudioAttributes()640     List<AudioAttributes> getAudioAttributes() {
641         List<AudioAttributes> audioAttributes = new ArrayList<>();
642         for (int index = 0; index < mContextToAddress.size(); index++) {
643             int context = mContextToAddress.keyAt(index);
644             AudioAttributes[] contextAttributes =
645                     mCarAudioContext.getAudioAttributesForContext(context);
646             for (int attrIndex = 0; attrIndex < contextAttributes.length; attrIndex++) {
647                 audioAttributes.add(contextAttributes[attrIndex]);
648             }
649         }
650 
651         return audioAttributes;
652     }
653 
654     static final class Builder {
655         private static final int UNSET_STEP_SIZE = -1;
656 
657         private final int mId;
658         private final int mZoneId;
659         private final boolean mUseCarVolumeGroupMute;
660         private final CarAudioSettings mCarAudioSettings;
661         private final SparseArray<String> mContextToAddress = new SparseArray<>();
662         private final CarAudioContext mCarAudioContext;
663         private final ArrayMap<String, CarAudioDeviceInfo> mAddressToCarAudioDeviceInfo =
664                 new ArrayMap<>();
665 
666         @VisibleForTesting
667         int mStepSize = UNSET_STEP_SIZE;
668         @VisibleForTesting
669         int mDefaultGain = Integer.MIN_VALUE;
670         @VisibleForTesting
671         int mMaxGain = Integer.MIN_VALUE;
672         @VisibleForTesting
673         int mMinGain = Integer.MAX_VALUE;
674 
Builder(CarAudioSettings carAudioSettings, CarAudioContext carAudioContext, int zoneId, int id, boolean useCarVolumeGroupMute)675         Builder(CarAudioSettings carAudioSettings, CarAudioContext carAudioContext,
676                 int zoneId, int id, boolean useCarVolumeGroupMute) {
677             mCarAudioSettings = Objects.requireNonNull(carAudioSettings,
678                     "Car audio settings can not be null");
679             mCarAudioContext = Objects.requireNonNull(carAudioContext,
680                     "Car audio context can not be null");
681             mZoneId = zoneId;
682             mId = id;
683             mUseCarVolumeGroupMute = useCarVolumeGroupMute;
684         }
685 
setDeviceInfoForContext(int carAudioContextId, CarAudioDeviceInfo info)686         Builder setDeviceInfoForContext(int carAudioContextId, CarAudioDeviceInfo info) {
687             Preconditions.checkArgument(mContextToAddress.get(carAudioContextId) == null,
688                     "Context %s has already been set to %s",
689                     mCarAudioContext.toString(carAudioContextId),
690                     mContextToAddress.get(carAudioContextId));
691 
692             if (mAddressToCarAudioDeviceInfo.isEmpty()) {
693                 mStepSize = info.getStepValue();
694             } else {
695                 Preconditions.checkArgument(
696                         info.getStepValue() == mStepSize,
697                         "Gain controls within one group must have same step value");
698             }
699 
700             mAddressToCarAudioDeviceInfo.put(info.getAddress(), info);
701             mContextToAddress.put(carAudioContextId, info.getAddress());
702 
703             if (info.getDefaultGain() > mDefaultGain) {
704                 // We're arbitrarily selecting the highest
705                 // device default gain as the group's default.
706                 mDefaultGain = info.getDefaultGain();
707             }
708             if (info.getMaxGain() > mMaxGain) {
709                 mMaxGain = info.getMaxGain();
710             }
711             if (info.getMinGain() < mMinGain) {
712                 mMinGain = info.getMinGain();
713             }
714 
715             return this;
716         }
717 
build()718         CarVolumeGroup build() {
719             Preconditions.checkArgument(mStepSize != UNSET_STEP_SIZE,
720                     "setDeviceInfoForContext has to be called at least once before building");
721             CarVolumeGroup group = new CarVolumeGroup(mCarAudioContext, mCarAudioSettings,
722                     mContextToAddress, mAddressToCarAudioDeviceInfo, mZoneId, mId, mStepSize,
723                     mDefaultGain, mMinGain, mMaxGain, mUseCarVolumeGroupMute);
724             group.init();
725             return group;
726         }
727     }
728 }
729