| /* |
| * Copyright (C) 2022 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| package com.android.car.audio; |
| |
| import static com.android.car.internal.ExcludeFromCodeCoverageGeneratedReport.PRIVATE_CONSTRUCTOR; |
| |
| import android.annotation.Nullable; |
| import android.car.builtin.media.AudioManagerHelper; |
| import android.car.builtin.util.Slogf; |
| import android.media.AudioAttributes; |
| import android.media.AudioManager; |
| import android.media.audiopolicy.AudioProductStrategy; |
| import android.media.audiopolicy.AudioVolumeGroup; |
| import android.util.SparseArray; |
| |
| import com.android.car.internal.ExcludeFromCodeCoverageGeneratedReport; |
| import com.android.car.internal.util.VersionUtils; |
| import com.android.internal.util.Preconditions; |
| |
| import java.util.List; |
| |
| /** |
| * Helper for audio related operations for core audio routing and volume management |
| * based on {@link AudioProductStrategy} and {@link AudioVolumeGroup} |
| */ |
| final class CoreAudioHelper { |
| static final String TAG = "CoreAudioHelper"; |
| |
| private static final boolean DEBUG = false; |
| |
| @ExcludeFromCodeCoverageGeneratedReport(reason = PRIVATE_CONSTRUCTOR) |
| private CoreAudioHelper() { |
| throw new UnsupportedOperationException("CoreAudioHelper class is non-instantiable, " |
| + "contains static members only"); |
| } |
| |
| /** Invalid strategy id returned when none matches a given request. */ |
| static final int INVALID_STRATEGY = -1; |
| |
| /** Invalid group id returned when none matches a given request. */ |
| static final int INVALID_GROUP_ID = -1; |
| |
| /** |
| * Default {@link AudioAttributes} used to identify the default {@link AudioProductStrategy} |
| * and {@link AudioVolumeGroup}. |
| */ |
| static final AudioAttributes DEFAULT_ATTRIBUTES = new AudioAttributes.Builder().build(); |
| |
| /** |
| * Due to testing issue with static mock, use lazy initialize pattern for static variables |
| */ |
| private static List<AudioProductStrategy> getAudioProductStrategies() { |
| return StaticLazyInitializer.sAudioProductStrategies; |
| } |
| private static List<AudioVolumeGroup> getAudioVolumeGroups() { |
| return StaticLazyInitializer.sAudioVolumeGroups; |
| } |
| private static SparseArray<String> getGroupIdToNames() { |
| return StaticLazyInitializer.sGroupIdToNames; |
| } |
| |
| private static class StaticLazyInitializer { |
| /** |
| * @see AudioProductStrategy |
| */ |
| static final List<AudioProductStrategy> sAudioProductStrategies = |
| AudioManager.getAudioProductStrategies(); |
| /** |
| * @see AudioVolumeGroup |
| */ |
| static final List<AudioVolumeGroup> sAudioVolumeGroups = |
| AudioManager.getAudioVolumeGroups(); |
| static final SparseArray<String> sGroupIdToNames = new SparseArray<>() { |
| { |
| for (int index = 0; index < sAudioVolumeGroups.size(); index++) { |
| AudioVolumeGroup group = sAudioVolumeGroups.get(index); |
| put(group.getId(), group.name()); |
| } |
| } |
| }; |
| } |
| |
| /** |
| * Identifies the {@link AudioProductStrategy} supporting the given {@link AudioAttributes}. |
| * |
| * @param attributes {@link AudioAttributes} to look for. |
| * @return the id of the {@link AudioProductStrategy} supporting the |
| * given {@link AudioAttributes} if found, {@link #INVALID_STRATEGY} id otherwise. |
| */ |
| public static int getStrategyForAudioAttributes(AudioAttributes attributes) { |
| Preconditions.checkNotNull(attributes, "Audio Attributes must not be null"); |
| for (int index = 0; index < getAudioProductStrategies().size(); index++) { |
| AudioProductStrategy strategy = getAudioProductStrategies().get(index); |
| if (strategy.supportsAudioAttributes(attributes)) { |
| return strategy.getId(); |
| } |
| } |
| return INVALID_STRATEGY; |
| } |
| |
| /** |
| * Identifies the {@link AudioProductStrategy} supporting the given {@link AudioAttributes} |
| * and fallbacking on the default strategy supporting {@code DEFAULT_ATTRIBUTES} otherwise. |
| * |
| * @param attributes {@link AudioAttributes} supported by the |
| * {@link AudioProductStrategy} to look for. |
| * @return the id of the {@link AudioProductStrategy} supporting the |
| * given {@link AudioAttributes}, otherwise the id of the default strategy, aka the |
| * strategy supporting {@code DEFAULT_ATTRIBUTES}, {@code INVALID_STRATEGY} id otherwise. |
| */ |
| public static int getStrategyForAudioAttributesOrDefault(AudioAttributes attributes) { |
| int strategyId = getStrategyForAudioAttributes(attributes); |
| return strategyId == INVALID_STRATEGY |
| ? getStrategyForAudioAttributes(DEFAULT_ATTRIBUTES) : strategyId; |
| } |
| |
| /** |
| * Gets the {@link AudioProductStrategy} referred by its unique identifier. |
| * |
| * @param strategyId id of the {@link AudioProductStrategy} to look for |
| * @return the {@link AudioProductStrategy} referred by the given id if found, {@code null} |
| * otherwise. |
| */ |
| @Nullable |
| public static AudioProductStrategy getStrategy(int strategyId) { |
| for (int index = 0; index < getAudioProductStrategies().size(); index++) { |
| AudioProductStrategy strategy = getAudioProductStrategies().get(index); |
| if (strategy.getId() == strategyId) { |
| return strategy; |
| } |
| } |
| return null; |
| } |
| |
| /** |
| * Checks if the {@link AudioProductStrategy} referred by it id is the default. |
| * |
| * @param strategyId to look for |
| * @return {@code true} if the {@link AudioProductStrategy} referred by |
| * its id is the default, aka supports {@code DEFAULT_ATTRIBUTES}, {@code false} otherwise. |
| */ |
| public static boolean isDefaultStrategy(int strategyId) { |
| for (int index = 0; index < getAudioProductStrategies().size(); index++) { |
| AudioProductStrategy strategy = getAudioProductStrategies().get(index); |
| if (strategy.getId() == strategyId) { |
| return strategy.supportsAudioAttributes(DEFAULT_ATTRIBUTES); |
| } |
| } |
| return false; |
| } |
| |
| /** |
| * Gets the {@link AudioVolumeGroup} referred by it name. |
| * |
| * @param groupName name of the {@link AudioVolumeGroup} to look for. |
| * @return the {@link AudioVolumeGroup} referred by the given id if found, {@code null} |
| * otherwise. |
| */ |
| @Nullable |
| public static AudioVolumeGroup getVolumeGroup(String groupName) { |
| for (int index = 0; index < getAudioVolumeGroups().size(); index++) { |
| AudioVolumeGroup group = getAudioVolumeGroups().get(index); |
| if (DEBUG) { |
| Slogf.d(TAG, "requested %s has %s,", groupName, group); |
| } |
| if (group.name().equals(groupName)) { |
| return group; |
| } |
| } |
| return null; |
| } |
| |
| /** |
| * Gets the most representative {@link AudioAttributes} of a given {@link AudioVolumeGroup} |
| * referred by it s name. |
| * <p>When relying on core audio to control volume, Volume APIs are based on AudioAttributes, |
| * thus, selecting the most representative attributes (not default without tag, with tag as |
| * fallback, {@link #DEFAULT_ATTRIBUTES} otherwise) will help identify the request. |
| * |
| * @param groupName name of the {@link AudioVolumeGroup} to look for. |
| * @return the best {@link AudioAttributes} for a given volume group id, |
| * {@link #DEFAULT_ATTRIBUTES} otherwise. |
| */ |
| public static AudioAttributes selectAttributesForVolumeGroupName(String groupName) { |
| AudioVolumeGroup group = getVolumeGroup(groupName); |
| AudioAttributes bestAttributes = DEFAULT_ATTRIBUTES; |
| if (group == null) { |
| return bestAttributes; |
| } |
| for (int index = 0; index < group.getAudioAttributes().size(); index++) { |
| AudioAttributes attributes = group.getAudioAttributes().get(index); |
| // bestAttributes attributes are not default and without tag (most generic as possible) |
| if (!attributes.equals(DEFAULT_ATTRIBUTES)) { |
| bestAttributes = attributes; |
| if (!VersionUtils.isPlatformVersionAtLeastU() |
| || AudioManagerHelper.getFormattedTags(attributes).equals("")) { |
| break; |
| } |
| } |
| } |
| return bestAttributes; |
| } |
| |
| /** |
| * Gets the name of the {@link AudioVolumeGroup} supporting given {@link AudioAttributes}, |
| * {@code null} is returned if none is found. |
| * |
| * @param attributes {@link AudioAttributes} supported by the group to look for. |
| * |
| * @return the name of the {@link AudioVolumeGroup} supporting the given audio attributes, |
| * {@code null} otherwise. |
| */ |
| @Nullable |
| public static String getVolumeGroupNameForAudioAttributes(AudioAttributes attributes) { |
| Preconditions.checkNotNull(attributes, "Audio Attributes must not be null"); |
| int volumeGroupId = getVolumeGroupIdForAudioAttributes(attributes); |
| return volumeGroupId != AudioVolumeGroup.DEFAULT_VOLUME_GROUP |
| ? getVolumeGroupNameFromCoreId(volumeGroupId) : null; |
| } |
| |
| /** |
| * Gets the name of the {@link AudioVolumeGroup} referred by its id. |
| * |
| * @param coreGroupId id of the volume group to look for. |
| * @return the volume group id referred by its name if found, throws an exception otherwise. |
| */ |
| @Nullable |
| public static String getVolumeGroupNameFromCoreId(int coreGroupId) { |
| return getGroupIdToNames().get(coreGroupId); |
| } |
| |
| /** |
| * Gets the {@link AudioVolumeGroup} id associated to the given {@link AudioAttributes}. |
| * |
| * @param attributes {@link AudioAttributes} to be considered |
| * @return the id of the {@link AudioVolumeGroup} supporting the given {@link AudioAttributes} |
| * if found, {@link #INVALID_GROUP_ID} otherwise. |
| */ |
| public static int getVolumeGroupIdForAudioAttributes(AudioAttributes attributes) { |
| Preconditions.checkNotNull(attributes, "Audio Attributes must not be null"); |
| if (!VersionUtils.isPlatformVersionAtLeastU()) { |
| Slogf.e(TAG, "AudioManagerHelper.getVolumeGroupIdForAudioAttributes() not" |
| + " supported for this build version, returning INVALID_GROUP_ID"); |
| return INVALID_GROUP_ID; |
| } |
| for (int index = 0; index < getAudioProductStrategies().size(); index++) { |
| AudioProductStrategy strategy = getAudioProductStrategies().get(index); |
| int volumeGroupId = |
| AudioManagerHelper.getVolumeGroupIdForAudioAttributes(strategy, attributes); |
| Slogf.d(TAG, "getVolumeGroupIdForAudioAttributes %s %s,", volumeGroupId, strategy); |
| if (volumeGroupId != AudioVolumeGroup.DEFAULT_VOLUME_GROUP) { |
| return volumeGroupId; |
| } |
| } |
| return INVALID_GROUP_ID; |
| } |
| } |