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 getAudioProductStrategies()62 private static List<AudioProductStrategy> getAudioProductStrategies() { 63 return AudioManagerWrapper.getAudioProductStrategies(); 64 } getAudioVolumeGroups()65 private static List<AudioVolumeGroup> getAudioVolumeGroups() { 66 return AudioManagerWrapper.getAudioVolumeGroups(); 67 } 68 69 /** 70 * Identifies the {@link AudioProductStrategy} supporting the given {@link AudioAttributes}. 71 * 72 * @param attributes {@link AudioAttributes} to look for. 73 * @return the id of the {@link AudioProductStrategy} supporting the 74 * given {@link AudioAttributes} if found, {@link #INVALID_STRATEGY} id otherwise. 75 */ getStrategyForAudioAttributes(AudioAttributes attributes)76 public static int getStrategyForAudioAttributes(AudioAttributes attributes) { 77 Preconditions.checkNotNull(attributes, "Audio Attributes can not be null"); 78 for (int index = 0; index < getAudioProductStrategies().size(); index++) { 79 AudioProductStrategy strategy = getAudioProductStrategies().get(index); 80 if (strategy.supportsAudioAttributes(attributes)) { 81 return strategy.getId(); 82 } 83 } 84 return INVALID_STRATEGY; 85 } 86 getStrategyForContextName(String contextName)87 public static int getStrategyForContextName(String contextName) { 88 Preconditions.checkNotNull(contextName, "Context name can not be null"); 89 for (int index = 0; index < getAudioProductStrategies().size(); index++) { 90 AudioProductStrategy strategy = getAudioProductStrategies().get(index); 91 if (Objects.equals(strategy.getName(), contextName)) { 92 return strategy.getId(); 93 } 94 } 95 return INVALID_STRATEGY; 96 } 97 98 /** 99 * Identifies the {@link AudioProductStrategy} supporting the given {@link AudioAttributes} 100 * and fallbacking on the default strategy supporting {@code DEFAULT_ATTRIBUTES} otherwise. 101 * 102 * @param attributes {@link AudioAttributes} supported by the 103 * {@link AudioProductStrategy} to look for. 104 * @return the id of the {@link AudioProductStrategy} supporting the 105 * given {@link AudioAttributes}, otherwise the id of the default strategy, aka the 106 * strategy supporting {@code DEFAULT_ATTRIBUTES}, {@code INVALID_STRATEGY} id otherwise. 107 */ getStrategyForAudioAttributesOrDefault(AudioAttributes attributes)108 public static int getStrategyForAudioAttributesOrDefault(AudioAttributes attributes) { 109 int strategyId = getStrategyForAudioAttributes(attributes); 110 return strategyId == INVALID_STRATEGY 111 ? getStrategyForAudioAttributes(DEFAULT_ATTRIBUTES) : strategyId; 112 } 113 114 @Nullable getProductStrategyForAudioAttributes( AudioAttributes attributes)115 static AudioProductStrategy getProductStrategyForAudioAttributes( 116 AudioAttributes attributes) { 117 Preconditions.checkNotNull(attributes, "Audio attributes can not be null"); 118 for (int index = 0; index < getAudioProductStrategies().size(); index++) { 119 AudioProductStrategy strategy = getAudioProductStrategies().get(index); 120 if (!strategy.supportsAudioAttributes(attributes)) { 121 continue; 122 } 123 return strategy; 124 } 125 return null; 126 } 127 128 /** 129 * Gets the {@link AudioProductStrategy} referred by its unique identifier. 130 * 131 * @param strategyId id of the {@link AudioProductStrategy} to look for 132 * @return the {@link AudioProductStrategy} referred by the given id if found, {@code null} 133 * otherwise. 134 */ 135 @Nullable getStrategy(int strategyId)136 public static AudioProductStrategy getStrategy(int strategyId) { 137 for (int index = 0; index < getAudioProductStrategies().size(); index++) { 138 AudioProductStrategy strategy = getAudioProductStrategies().get(index); 139 if (strategy.getId() == strategyId) { 140 return strategy; 141 } 142 } 143 return null; 144 } 145 146 /** 147 * Checks if the {@link AudioProductStrategy} referred by it id is the default. 148 * 149 * @param strategyId to look for 150 * @return {@code true} if the {@link AudioProductStrategy} referred by 151 * its id is the default, aka supports {@code DEFAULT_ATTRIBUTES}, {@code false} otherwise. 152 */ isDefaultStrategy(int strategyId)153 public static boolean isDefaultStrategy(int strategyId) { 154 for (int index = 0; index < getAudioProductStrategies().size(); index++) { 155 AudioProductStrategy strategy = getAudioProductStrategies().get(index); 156 if (strategy.getId() == strategyId) { 157 return strategy.supportsAudioAttributes(DEFAULT_ATTRIBUTES); 158 } 159 } 160 return false; 161 } 162 163 /** 164 * Gets the {@link AudioVolumeGroup} referred by it name. 165 * 166 * @param groupName name of the {@link AudioVolumeGroup} to look for. 167 * @return the {@link AudioVolumeGroup} referred by the given id if found, {@code null} 168 * otherwise. 169 */ 170 @Nullable getVolumeGroup(String groupName)171 public static AudioVolumeGroup getVolumeGroup(String groupName) { 172 for (int index = 0; index < getAudioVolumeGroups().size(); index++) { 173 AudioVolumeGroup group = getAudioVolumeGroups().get(index); 174 if (DEBUG) { 175 Slogf.d(TAG, "requested %s has %s,", groupName, group); 176 } 177 if (group.name().equals(groupName)) { 178 return group; 179 } 180 } 181 return null; 182 } 183 184 /** 185 * Gets the most representative {@link AudioAttributes} of a given {@link AudioVolumeGroup} 186 * referred by it s name. 187 * <p>When relying on core audio to control volume, Volume APIs are based on AudioAttributes, 188 * thus, selecting the most representative attributes (not default without tag, with tag as 189 * fallback, {@link #DEFAULT_ATTRIBUTES} otherwise) will help identify the request. 190 * 191 * @param groupName name of the {@link AudioVolumeGroup} to look for. 192 * @return the best {@link AudioAttributes} for a given volume group id, 193 * {@link #DEFAULT_ATTRIBUTES} otherwise. 194 */ selectAttributesForVolumeGroupName(String groupName)195 public static AudioAttributes selectAttributesForVolumeGroupName(String groupName) { 196 AudioVolumeGroup group = getVolumeGroup(groupName); 197 AudioAttributes bestAttributes = DEFAULT_ATTRIBUTES; 198 if (group == null) { 199 return bestAttributes; 200 } 201 for (int index = 0; index < group.getAudioAttributes().size(); index++) { 202 AudioAttributes attributes = group.getAudioAttributes().get(index); 203 // bestAttributes attributes are not default and without tag (most generic as possible) 204 if (!attributes.equals(DEFAULT_ATTRIBUTES)) { 205 bestAttributes = attributes; 206 if (Objects.equals(AudioManagerHelper.getFormattedTags(attributes), "")) { 207 break; 208 } 209 } 210 } 211 return bestAttributes; 212 } 213 214 /** 215 * Gets the name of the {@link AudioVolumeGroup} supporting given {@link AudioAttributes}, 216 * {@code null} is returned if none is found. 217 * 218 * @param attributes {@link AudioAttributes} supported by the group to look for. 219 * 220 * @return the name of the {@link AudioVolumeGroup} supporting the given audio attributes, 221 * {@code null} otherwise. 222 */ 223 @Nullable getVolumeGroupNameForAudioAttributes(AudioAttributes attributes)224 public static String getVolumeGroupNameForAudioAttributes(AudioAttributes attributes) { 225 Preconditions.checkNotNull(attributes, "Audio Attributes can not be null"); 226 int volumeGroupId = getVolumeGroupIdForAudioAttributes(attributes); 227 return volumeGroupId != AudioVolumeGroup.DEFAULT_VOLUME_GROUP 228 ? getVolumeGroupNameFromCoreId(volumeGroupId) : null; 229 } 230 231 /** 232 * Gets the name of the {@link AudioVolumeGroup} referred by its id. 233 * 234 * @param coreGroupId id of the volume group to look for. 235 * @return the volume group id referred by its name if found, throws an exception otherwise. 236 */ 237 @Nullable getVolumeGroupNameFromCoreId(int coreGroupId)238 public static String getVolumeGroupNameFromCoreId(int coreGroupId) { 239 List<AudioVolumeGroup> audioVolumeGroups = 240 AudioManagerWrapper.getAudioVolumeGroups(); 241 SparseArray<String> groupIdToNames = new SparseArray<>() { 242 { 243 for (int index = 0; index < audioVolumeGroups.size(); index++) { 244 AudioVolumeGroup group = audioVolumeGroups.get(index); 245 put(group.getId(), group.name()); 246 } 247 } 248 }; 249 return groupIdToNames.get(coreGroupId); 250 } 251 252 /** 253 * Gets the {@link AudioVolumeGroup} id associated to the given {@link AudioAttributes}. 254 * 255 * @param attributes {@link AudioAttributes} to be considered 256 * @return the id of the {@link AudioVolumeGroup} supporting the given {@link AudioAttributes} 257 * if found, {@link #INVALID_GROUP_ID} otherwise. 258 */ getVolumeGroupIdForAudioAttributes(AudioAttributes attributes)259 public static int getVolumeGroupIdForAudioAttributes(AudioAttributes attributes) { 260 Preconditions.checkNotNull(attributes, "Audio Attributes can not be null"); 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