• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2022 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 
17 package com.android.car.audio;
18 
19 import static com.android.car.internal.ExcludeFromCodeCoverageGeneratedReport.PRIVATE_CONSTRUCTOR;
20 
21 import android.annotation.Nullable;
22 import android.car.builtin.media.AudioManagerHelper;
23 import android.car.builtin.util.Slogf;
24 import android.media.AudioAttributes;
25 import android.media.AudioManager;
26 import android.media.audiopolicy.AudioProductStrategy;
27 import android.media.audiopolicy.AudioVolumeGroup;
28 import android.util.SparseArray;
29 
30 import com.android.car.internal.ExcludeFromCodeCoverageGeneratedReport;
31 import com.android.car.internal.util.VersionUtils;
32 import com.android.internal.util.Preconditions;
33 
34 import java.util.List;
35 import java.util.Objects;
36 
37 /**
38  * Helper for audio related operations for core audio routing and volume management
39  * based on {@link AudioProductStrategy} and {@link AudioVolumeGroup}
40  */
41 final class CoreAudioHelper {
42     static final String TAG = "CoreAudioHelper";
43 
44     private static final boolean DEBUG = false;
45 
46     @ExcludeFromCodeCoverageGeneratedReport(reason = PRIVATE_CONSTRUCTOR)
CoreAudioHelper()47     private CoreAudioHelper() {
48         throw new UnsupportedOperationException("CoreAudioHelper class is non-instantiable, "
49                 + "contains static members only");
50     }
51 
52     /** Invalid strategy id returned when none matches a given request. */
53     static final int INVALID_STRATEGY = -1;
54 
55     /** Invalid group id returned when none matches a given request. */
56     static final int INVALID_GROUP_ID = -1;
57 
58     /**
59      * Default {@link AudioAttributes} used to identify the default {@link AudioProductStrategy}
60      * and {@link AudioVolumeGroup}.
61      */
62     static final AudioAttributes DEFAULT_ATTRIBUTES = new AudioAttributes.Builder().build();
63 
64     /**
65      * Due to testing issue with static mock, use lazy initialize pattern for static variables
66      */
getAudioProductStrategies()67     private static List<AudioProductStrategy> getAudioProductStrategies() {
68         return StaticLazyInitializer.sAudioProductStrategies;
69     }
getAudioVolumeGroups()70     private static List<AudioVolumeGroup> getAudioVolumeGroups() {
71         return StaticLazyInitializer.sAudioVolumeGroups;
72     }
getGroupIdToNames()73     private static SparseArray<String> getGroupIdToNames() {
74         return StaticLazyInitializer.sGroupIdToNames;
75     }
76 
77     private static class StaticLazyInitializer {
78         /**
79          * @see AudioProductStrategy
80          */
81         static final List<AudioProductStrategy> sAudioProductStrategies =
82                 AudioManager.getAudioProductStrategies();
83         /**
84          * @see AudioVolumeGroup
85          */
86         static final List<AudioVolumeGroup> sAudioVolumeGroups =
87                 AudioManager.getAudioVolumeGroups();
88         static final SparseArray<String> sGroupIdToNames = new SparseArray<>() {
89             {
90                 for (int index = 0; index < sAudioVolumeGroups.size(); index++) {
91                     AudioVolumeGroup group = sAudioVolumeGroups.get(index);
92                     put(group.getId(), group.name());
93                 }
94             }
95         };
96     }
97 
98     /**
99      * Identifies the {@link AudioProductStrategy} supporting the given {@link AudioAttributes}.
100      *
101      * @param attributes {@link AudioAttributes} to look for.
102      * @return the id of the {@link AudioProductStrategy} supporting the
103      * given {@link AudioAttributes} if found, {@link #INVALID_STRATEGY} id otherwise.
104      */
getStrategyForAudioAttributes(AudioAttributes attributes)105     public static int getStrategyForAudioAttributes(AudioAttributes attributes) {
106         Preconditions.checkNotNull(attributes, "Audio Attributes must not be null");
107         for (int index = 0; index < getAudioProductStrategies().size(); index++) {
108             AudioProductStrategy strategy = getAudioProductStrategies().get(index);
109             if (strategy.supportsAudioAttributes(attributes)) {
110                 return strategy.getId();
111             }
112         }
113         return INVALID_STRATEGY;
114     }
115 
116     /**
117      * Identifies the {@link AudioProductStrategy} supporting the given {@link AudioAttributes}
118      * and fallbacking on the default strategy supporting {@code DEFAULT_ATTRIBUTES} otherwise.
119      *
120      * @param attributes {@link AudioAttributes} supported by the
121      * {@link AudioProductStrategy} to look for.
122      * @return the id of the {@link AudioProductStrategy} supporting the
123      * given {@link AudioAttributes}, otherwise the id of the default strategy, aka the
124      * strategy supporting {@code DEFAULT_ATTRIBUTES}, {@code INVALID_STRATEGY} id otherwise.
125      */
getStrategyForAudioAttributesOrDefault(AudioAttributes attributes)126     public static int getStrategyForAudioAttributesOrDefault(AudioAttributes attributes) {
127         int strategyId = getStrategyForAudioAttributes(attributes);
128         return strategyId == INVALID_STRATEGY
129                 ? getStrategyForAudioAttributes(DEFAULT_ATTRIBUTES) : strategyId;
130     }
131 
132     /**
133      * Gets the {@link AudioProductStrategy} referred by its unique identifier.
134      *
135      * @param strategyId id of the {@link AudioProductStrategy} to look for
136      * @return the {@link AudioProductStrategy} referred by the given id if found, {@code null}
137      * otherwise.
138      */
139     @Nullable
getStrategy(int strategyId)140     public static AudioProductStrategy getStrategy(int strategyId) {
141         for (int index = 0; index < getAudioProductStrategies().size(); index++) {
142             AudioProductStrategy strategy = getAudioProductStrategies().get(index);
143             if (strategy.getId() == strategyId) {
144                 return strategy;
145             }
146         }
147         return null;
148     }
149 
150     /**
151      * Checks if the {@link AudioProductStrategy} referred by it id is the default.
152      *
153      * @param strategyId to look for
154      * @return {@code true} if the {@link AudioProductStrategy} referred by
155      * its id is the default, aka supports {@code DEFAULT_ATTRIBUTES}, {@code false} otherwise.
156      */
isDefaultStrategy(int strategyId)157     public static boolean isDefaultStrategy(int strategyId) {
158         for (int index = 0; index < getAudioProductStrategies().size(); index++) {
159             AudioProductStrategy strategy = getAudioProductStrategies().get(index);
160             if (strategy.getId() == strategyId) {
161                 return strategy.supportsAudioAttributes(DEFAULT_ATTRIBUTES);
162             }
163         }
164         return false;
165     }
166 
167     /**
168      * Gets the {@link AudioVolumeGroup} referred by it name.
169      *
170      * @param groupName name of the {@link AudioVolumeGroup} to look for.
171      * @return the {@link AudioVolumeGroup} referred by the given id if found, {@code null}
172      * otherwise.
173      */
174     @Nullable
getVolumeGroup(String groupName)175     public static AudioVolumeGroup getVolumeGroup(String groupName) {
176         for (int index = 0; index < getAudioVolumeGroups().size(); index++) {
177             AudioVolumeGroup group = getAudioVolumeGroups().get(index);
178             if (DEBUG) {
179                 Slogf.d(TAG, "requested %s has %s,", groupName, group);
180             }
181             if (group.name().equals(groupName)) {
182                 return group;
183             }
184         }
185         return null;
186     }
187 
188     /**
189      * Gets the most representative {@link AudioAttributes} of a given {@link AudioVolumeGroup}
190      * referred by it s name.
191      * <p>When relying on core audio to control volume, Volume APIs are based on AudioAttributes,
192      * thus, selecting the most representative attributes (not default without tag, with tag as
193      * fallback, {@link #DEFAULT_ATTRIBUTES} otherwise) will help identify the request.
194      *
195      * @param groupName name of the {@link AudioVolumeGroup} to look for.
196      * @return the best {@link AudioAttributes} for a given volume group id,
197      * {@link #DEFAULT_ATTRIBUTES} otherwise.
198      */
selectAttributesForVolumeGroupName(String groupName)199     public static AudioAttributes selectAttributesForVolumeGroupName(String groupName) {
200         AudioVolumeGroup group = getVolumeGroup(groupName);
201         AudioAttributes bestAttributes = DEFAULT_ATTRIBUTES;
202         if (group == null) {
203             return bestAttributes;
204         }
205         for (int index = 0; index < group.getAudioAttributes().size(); index++) {
206             AudioAttributes attributes = group.getAudioAttributes().get(index);
207             // bestAttributes attributes are not default and without tag (most generic as possible)
208             if (!attributes.equals(DEFAULT_ATTRIBUTES)) {
209                 bestAttributes = attributes;
210                 if (!VersionUtils.isPlatformVersionAtLeastU()
211                         || Objects.equals(AudioManagerHelper.getFormattedTags(attributes), "")) {
212                     break;
213                 }
214             }
215         }
216         return bestAttributes;
217     }
218 
219     /**
220      * Gets the name of the {@link AudioVolumeGroup} supporting given {@link AudioAttributes},
221      * {@code null} is returned if none is found.
222      *
223      * @param attributes {@link AudioAttributes} supported by the group to look for.
224      *
225      * @return the name of the {@link AudioVolumeGroup} supporting the given audio attributes,
226      * {@code null} otherwise.
227      */
228     @Nullable
getVolumeGroupNameForAudioAttributes(AudioAttributes attributes)229     public static String getVolumeGroupNameForAudioAttributes(AudioAttributes attributes) {
230         Preconditions.checkNotNull(attributes, "Audio Attributes must not be null");
231         int volumeGroupId = getVolumeGroupIdForAudioAttributes(attributes);
232         return volumeGroupId != AudioVolumeGroup.DEFAULT_VOLUME_GROUP
233                 ? getVolumeGroupNameFromCoreId(volumeGroupId) : null;
234     }
235 
236     /**
237      * Gets the name of the {@link AudioVolumeGroup} referred by its id.
238      *
239      * @param coreGroupId id of the volume group to look for.
240      * @return the volume group id referred by its name if found, throws an exception otherwise.
241      */
242     @Nullable
getVolumeGroupNameFromCoreId(int coreGroupId)243     public static String getVolumeGroupNameFromCoreId(int coreGroupId) {
244         return getGroupIdToNames().get(coreGroupId);
245     }
246 
247     /**
248      * Gets the {@link AudioVolumeGroup} id associated to the given {@link AudioAttributes}.
249      *
250      * @param attributes {@link AudioAttributes} to be considered
251      * @return the id of the {@link AudioVolumeGroup} supporting the given {@link AudioAttributes}
252      * if found, {@link #INVALID_GROUP_ID} otherwise.
253      */
getVolumeGroupIdForAudioAttributes(AudioAttributes attributes)254     public static int getVolumeGroupIdForAudioAttributes(AudioAttributes attributes) {
255         Preconditions.checkNotNull(attributes, "Audio Attributes must not be null");
256         if (!VersionUtils.isPlatformVersionAtLeastU()) {
257             Slogf.e(TAG, "AudioManagerHelper.getVolumeGroupIdForAudioAttributes() not"
258                     + " supported for this build version, returning INVALID_GROUP_ID");
259             return INVALID_GROUP_ID;
260         }
261         for (int index = 0; index < getAudioProductStrategies().size(); index++) {
262             AudioProductStrategy strategy = getAudioProductStrategies().get(index);
263             int volumeGroupId =
264                     AudioManagerHelper.getVolumeGroupIdForAudioAttributes(strategy, attributes);
265             Slogf.d(TAG, "getVolumeGroupIdForAudioAttributes %s %s,", volumeGroupId, strategy);
266             if (volumeGroupId != AudioVolumeGroup.DEFAULT_VOLUME_GROUP) {
267                 return volumeGroupId;
268             }
269         }
270         return INVALID_GROUP_ID;
271     }
272 }
273