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