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