• 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.internal.ExcludeFromCodeCoverageGeneratedReport.BOILERPLATE_CODE;
19 import static com.android.car.internal.ExcludeFromCodeCoverageGeneratedReport.DUMP_INFO;
20 
21 import android.annotation.NonNull;
22 import android.annotation.Nullable;
23 import android.annotation.UserIdInt;
24 import android.car.media.CarAudioManager;
25 import android.media.AudioDevicePort;
26 import android.os.UserHandle;
27 import android.util.IndentingPrintWriter;
28 import android.util.Slog;
29 import android.util.SparseArray;
30 
31 import com.android.car.CarLog;
32 import com.android.car.audio.CarAudioContext.AudioContext;
33 import com.android.car.internal.ExcludeFromCodeCoverageGeneratedReport;
34 import com.android.internal.annotations.GuardedBy;
35 import com.android.internal.annotations.VisibleForTesting;
36 import com.android.internal.util.Preconditions;
37 
38 import java.util.ArrayList;
39 import java.util.Arrays;
40 import java.util.HashMap;
41 import java.util.List;
42 import java.util.Map;
43 
44 /**
45  * A class encapsulates a volume group in car.
46  *
47  * Volume in a car is controlled by group. A group holds one or more car audio contexts.
48  * Call {@link CarAudioManager#getVolumeGroupCount()} to get the count of {@link CarVolumeGroup}
49  * supported in a car.
50  */
51 /* package */ final class CarVolumeGroup {
52 
53     private final boolean mUseCarVolumeGroupMute;
54     private final boolean mHasCriticalAudioContexts;
55     private final CarAudioSettings mSettingsManager;
56     private final int mDefaultGain;
57     private final int mId;
58     private final int mMaxGain;
59     private final int mMinGain;
60     private final int mStepSize;
61     private final int mZoneId;
62     private final SparseArray<String> mContextToAddress;
63     private final Map<String, CarAudioDeviceInfo> mAddressToCarAudioDeviceInfo;
64 
65     private final Object mLock = new Object();
66 
67     @GuardedBy("mLock")
68     private int mStoredGainIndex;
69     @GuardedBy("mLock")
70     private int mCurrentGainIndex = -1;
71     @GuardedBy("mLock")
72     private boolean mIsMuted;
73     @GuardedBy("mLock")
74     private @UserIdInt int mUserId = UserHandle.USER_CURRENT;
75 
CarVolumeGroup(int zoneId, int id, CarAudioSettings settingsManager, int stepSize, int defaultGain, int minGain, int maxGain, SparseArray<String> contextToAddress, Map<String, CarAudioDeviceInfo> addressToCarAudioDeviceInfo, boolean useCarVolumeGroupMute)76     private CarVolumeGroup(int zoneId, int id, CarAudioSettings settingsManager, int stepSize,
77             int defaultGain, int minGain, int maxGain, SparseArray<String> contextToAddress,
78             Map<String, CarAudioDeviceInfo> addressToCarAudioDeviceInfo,
79             boolean useCarVolumeGroupMute) {
80 
81         mSettingsManager = settingsManager;
82         mZoneId = zoneId;
83         mId = id;
84         mStepSize = stepSize;
85         mDefaultGain = defaultGain;
86         mMinGain = minGain;
87         mMaxGain = maxGain;
88         mContextToAddress = contextToAddress;
89         mAddressToCarAudioDeviceInfo = addressToCarAudioDeviceInfo;
90         mUseCarVolumeGroupMute = useCarVolumeGroupMute;
91 
92         mHasCriticalAudioContexts = containsCriticalAudioContext(contextToAddress);
93     }
94 
init()95     void init() {
96         mStoredGainIndex = mSettingsManager.getStoredVolumeGainIndexForUser(mUserId, mZoneId, mId);
97         updateCurrentGainIndexLocked();
98     }
99 
100     @Nullable
getCarAudioDeviceInfoForAddress(String address)101     CarAudioDeviceInfo getCarAudioDeviceInfoForAddress(String address) {
102         return mAddressToCarAudioDeviceInfo.get(address);
103     }
104 
105     @AudioContext
getContexts()106     int[] getContexts() {
107         final int[] carAudioContexts = new int[mContextToAddress.size()];
108         for (int i = 0; i < carAudioContexts.length; i++) {
109             carAudioContexts[i] = mContextToAddress.keyAt(i);
110         }
111         return carAudioContexts;
112     }
113 
114     /**
115      * Returns the devices address for the given context
116      * or {@code null} if the context does not exist in the volume group
117      */
118     @Nullable
getAddressForContext(int audioContext)119     String getAddressForContext(int audioContext) {
120         return mContextToAddress.get(audioContext);
121     }
122 
123     @AudioContext
getContextsForAddress(@onNull String address)124     List<Integer> getContextsForAddress(@NonNull String address) {
125         List<Integer> carAudioContexts = new ArrayList<>();
126         for (int i = 0; i < mContextToAddress.size(); i++) {
127             String value = mContextToAddress.valueAt(i);
128             if (address.equals(value)) {
129                 carAudioContexts.add(mContextToAddress.keyAt(i));
130             }
131         }
132         return carAudioContexts;
133     }
134 
getAddresses()135     List<String> getAddresses() {
136         return new ArrayList<>(mAddressToCarAudioDeviceInfo.keySet());
137     }
138 
getMaxGainIndex()139     int getMaxGainIndex() {
140         synchronized (mLock) {
141             return getIndexForGain(mMaxGain);
142         }
143     }
144 
getMinGainIndex()145     int getMinGainIndex() {
146         synchronized (mLock) {
147             return getIndexForGain(mMinGain);
148         }
149     }
150 
getCurrentGainIndex()151     int getCurrentGainIndex() {
152         synchronized (mLock) {
153             return mCurrentGainIndex;
154         }
155     }
156 
157     /**
158      * Sets the gain on this group, gain will be set on all devices within volume group.
159      */
setCurrentGainIndex(int gainIndex)160     void setCurrentGainIndex(int gainIndex) {
161         int gainInMillibels = getGainForIndex(gainIndex);
162         Preconditions.checkArgument(isValidGainIndex(gainIndex),
163                 "Gain out of range (%d:%d) %d index %d", mMinGain, mMaxGain,
164                 gainInMillibels, gainIndex);
165         synchronized (mLock) {
166             for (String address : mAddressToCarAudioDeviceInfo.keySet()) {
167                 CarAudioDeviceInfo info = mAddressToCarAudioDeviceInfo.get(address);
168                 info.setCurrentGain(gainInMillibels);
169             }
170 
171             mCurrentGainIndex = gainIndex;
172 
173             storeGainIndexForUserLocked(mCurrentGainIndex, mUserId);
174         }
175     }
176 
177     @Nullable
getAudioDevicePortForContext(int carAudioContext)178     AudioDevicePort getAudioDevicePortForContext(int carAudioContext) {
179         final String address = mContextToAddress.get(carAudioContext);
180         if (address == null || mAddressToCarAudioDeviceInfo.get(address) == null) {
181             return null;
182         }
183 
184         return mAddressToCarAudioDeviceInfo.get(address).getAudioDevicePort();
185     }
186 
hasCriticalAudioContexts()187     boolean hasCriticalAudioContexts() {
188         return mHasCriticalAudioContexts;
189     }
190 
191     @Override
192     @ExcludeFromCodeCoverageGeneratedReport(reason = BOILERPLATE_CODE)
toString()193     public String toString() {
194         return "CarVolumeGroup id: " + mId
195                 + " currentGainIndex: " + mCurrentGainIndex
196                 + " contexts: " + Arrays.toString(getContexts())
197                 + " addresses: " + String.join(", ", getAddresses());
198     }
199 
200     @ExcludeFromCodeCoverageGeneratedReport(reason = DUMP_INFO)
dump(IndentingPrintWriter writer)201     void dump(IndentingPrintWriter writer) {
202         synchronized (mLock) {
203             writer.printf("CarVolumeGroup(%d)\n", mId);
204             writer.increaseIndent();
205             writer.printf("Zone Id(%b)\n", mZoneId);
206             writer.printf("Is Muted(%b)\n", mIsMuted);
207             writer.printf("UserId(%d)\n", mUserId);
208             writer.printf("Persist Volume Group Mute(%b)\n",
209                     mSettingsManager.isPersistVolumeGroupMuteEnabled(mUserId));
210             writer.printf("Step size: %d\n", mStepSize);
211             writer.printf("Gain values (min / max / default/ current): %d %d %d %d\n", mMinGain,
212                     mMaxGain, mDefaultGain, getGainForIndex(mCurrentGainIndex));
213             writer.printf("Gain indexes (min / max / default / current): %d %d %d %d\n",
214                     getMinGainIndex(), getMaxGainIndex(), getDefaultGainIndex(), mCurrentGainIndex);
215             for (int i = 0; i < mContextToAddress.size(); i++) {
216                 writer.printf("Context: %s -> Address: %s\n",
217                         CarAudioContext.toString(mContextToAddress.keyAt(i)),
218                         mContextToAddress.valueAt(i));
219             }
220             mAddressToCarAudioDeviceInfo.keySet().stream()
221                     .map(mAddressToCarAudioDeviceInfo::get)
222                     .forEach((info -> info.dump(writer)));
223 
224             // Empty line for comfortable reading
225             writer.println();
226             writer.decreaseIndent();
227         }
228     }
229 
loadVolumesSettingsForUser(@serIdInt int userId)230     void loadVolumesSettingsForUser(@UserIdInt int userId) {
231         synchronized (mLock) {
232             //Update the volume for the new user
233             updateUserIdLocked(userId);
234             //Update the current gain index
235             updateCurrentGainIndexLocked();
236             //Reset devices with current gain index
237             updateGroupMuteLocked();
238         }
239         setCurrentGainIndex(getCurrentGainIndex());
240     }
241 
setMute(boolean mute)242     void setMute(boolean mute) {
243         synchronized (mLock) {
244             mIsMuted = mute;
245             if (mSettingsManager.isPersistVolumeGroupMuteEnabled(mUserId)) {
246                 mSettingsManager.storeVolumeGroupMuteForUser(mUserId, mZoneId, mId, mute);
247             }
248         }
249     }
250 
isMuted()251     boolean isMuted() {
252         synchronized (mLock) {
253             return mIsMuted;
254         }
255     }
256 
containsCriticalAudioContext(SparseArray<String> contextToAddress)257     private static boolean containsCriticalAudioContext(SparseArray<String> contextToAddress) {
258         for (int i = 0; i < contextToAddress.size(); i++) {
259             int audioContext = contextToAddress.keyAt(i);
260             if (CarAudioContext.isCriticalAudioContext(audioContext)) {
261                 return true;
262             }
263         }
264         return false;
265     }
266 
267     @GuardedBy("mLock")
updateUserIdLocked(@serIdInt int userId)268     private void updateUserIdLocked(@UserIdInt int userId) {
269         mUserId = userId;
270         mStoredGainIndex = getCurrentGainIndexForUserLocked();
271     }
272 
273     @GuardedBy("mLock")
getCurrentGainIndexForUserLocked()274     private int getCurrentGainIndexForUserLocked() {
275         int gainIndexForUser = mSettingsManager.getStoredVolumeGainIndexForUser(mUserId, mZoneId,
276                 mId);
277         Slog.i(CarLog.TAG_AUDIO, "updateUserId userId " + mUserId
278                 + " gainIndexForUser " + gainIndexForUser);
279         return gainIndexForUser;
280     }
281 
282     /**
283      * Update the current gain index based on the stored gain index
284      */
285     @GuardedBy("mLock")
updateCurrentGainIndexLocked()286     private void updateCurrentGainIndexLocked() {
287         if (isValidGainIndex(mStoredGainIndex)) {
288             mCurrentGainIndex = mStoredGainIndex;
289         } else {
290             mCurrentGainIndex = getIndexForGain(mDefaultGain);
291         }
292     }
293 
isValidGainIndex(int gainIndex)294     private boolean isValidGainIndex(int gainIndex) {
295         return gainIndex >= getIndexForGain(mMinGain)
296                 && gainIndex <= getIndexForGain(mMaxGain);
297     }
298 
getDefaultGainIndex()299     private int getDefaultGainIndex() {
300         synchronized (mLock) {
301             return getIndexForGain(mDefaultGain);
302         }
303     }
304 
305     @GuardedBy("mLock")
storeGainIndexForUserLocked(int gainIndex, @UserIdInt int userId)306     private void storeGainIndexForUserLocked(int gainIndex, @UserIdInt int userId) {
307         mSettingsManager.storeVolumeGainIndexForUser(userId,
308                 mZoneId, mId, gainIndex);
309     }
310 
getGainForIndex(int gainIndex)311     private int getGainForIndex(int gainIndex) {
312         return mMinGain + gainIndex * mStepSize;
313     }
314 
getIndexForGain(int gainInMillibel)315     private int getIndexForGain(int gainInMillibel) {
316         return (gainInMillibel - mMinGain) / mStepSize;
317     }
318 
319     @GuardedBy("mLock")
updateGroupMuteLocked()320     private void updateGroupMuteLocked() {
321         if (!mUseCarVolumeGroupMute) {
322             return;
323         }
324         if (!mSettingsManager.isPersistVolumeGroupMuteEnabled(mUserId)) {
325             mIsMuted = false;
326             return;
327         }
328         mIsMuted = mSettingsManager.getVolumeGroupMuteForUser(mUserId, mZoneId, mId);
329     }
330 
331     static final class Builder {
332         private static final int UNSET_STEP_SIZE = -1;
333 
334         private final int mId;
335         private final int mZoneId;
336         private final boolean mUseCarVolumeGroupMute;
337         private final CarAudioSettings mCarAudioSettings;
338         private final SparseArray<String> mContextToAddress = new SparseArray<>();
339         private final Map<String, CarAudioDeviceInfo> mAddressToCarAudioDeviceInfo =
340                 new HashMap<>();
341 
342         @VisibleForTesting
343         int mStepSize = UNSET_STEP_SIZE;
344         @VisibleForTesting
345         int mDefaultGain = Integer.MIN_VALUE;
346         @VisibleForTesting
347         int mMaxGain = Integer.MIN_VALUE;
348         @VisibleForTesting
349         int mMinGain = Integer.MAX_VALUE;
350 
Builder(int zoneId, int id, CarAudioSettings carAudioSettings, boolean useCarVolumeGroupMute)351         Builder(int zoneId, int id, CarAudioSettings carAudioSettings,
352                 boolean useCarVolumeGroupMute) {
353             mZoneId = zoneId;
354             mId = id;
355             mCarAudioSettings = carAudioSettings;
356             mUseCarVolumeGroupMute = useCarVolumeGroupMute;
357         }
358 
setDeviceInfoForContext(int carAudioContext, CarAudioDeviceInfo info)359         Builder setDeviceInfoForContext(int carAudioContext, CarAudioDeviceInfo info) {
360             Preconditions.checkArgument(mContextToAddress.get(carAudioContext) == null,
361                     "Context %s has already been set to %s",
362                     CarAudioContext.toString(carAudioContext),
363                     mContextToAddress.get(carAudioContext));
364 
365             if (mAddressToCarAudioDeviceInfo.isEmpty()) {
366                 mStepSize = info.getStepValue();
367             } else {
368                 Preconditions.checkArgument(
369                         info.getStepValue() == mStepSize,
370                         "Gain controls within one group must have same step value");
371             }
372 
373             mAddressToCarAudioDeviceInfo.put(info.getAddress(), info);
374             mContextToAddress.put(carAudioContext, info.getAddress());
375 
376             if (info.getDefaultGain() > mDefaultGain) {
377                 // We're arbitrarily selecting the highest
378                 // device default gain as the group's default.
379                 mDefaultGain = info.getDefaultGain();
380             }
381             if (info.getMaxGain() > mMaxGain) {
382                 mMaxGain = info.getMaxGain();
383             }
384             if (info.getMinGain() < mMinGain) {
385                 mMinGain = info.getMinGain();
386             }
387 
388             return this;
389         }
390 
build()391         CarVolumeGroup build() {
392             Preconditions.checkArgument(mStepSize != UNSET_STEP_SIZE,
393                     "setDeviceInfoForContext has to be called at least once before building");
394             CarVolumeGroup group = new CarVolumeGroup(mZoneId, mId, mCarAudioSettings, mStepSize,
395                     mDefaultGain, mMinGain, mMaxGain, mContextToAddress,
396                     mAddressToCarAudioDeviceInfo, mUseCarVolumeGroupMute);
397             group.init();
398             return group;
399         }
400     }
401 }
402