/* * Copyright (C) 2023 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.settingslib.bluetooth; import android.content.Context; import android.media.AudioAttributes; import android.media.AudioDeviceAttributes; import android.media.AudioDeviceInfo; import android.media.AudioManager; import android.media.audiopolicy.AudioProductStrategy; import androidx.annotation.Nullable; import androidx.annotation.VisibleForTesting; import java.util.ArrayList; import java.util.List; import java.util.Set; import java.util.stream.Collectors; /** * A helper class to configure the routing strategy for hearing aids. */ public class HearingAidAudioRoutingHelper { private final AudioManager mAudioManager; public HearingAidAudioRoutingHelper(Context context) { mAudioManager = context.getSystemService(AudioManager.class); } /** * Gets the list of {@link AudioProductStrategy} referred by the given list of usage values * defined in {@link AudioAttributes} */ public List getSupportedStrategies(int[] attributeSdkUsageList) { final List audioAttrList = new ArrayList<>(attributeSdkUsageList.length); for (int attributeSdkUsage : attributeSdkUsageList) { audioAttrList.add(new AudioAttributes.Builder().setUsage(attributeSdkUsage).build()); } final List allStrategies = getAudioProductStrategies(); final List supportedStrategies = new ArrayList<>(); for (AudioProductStrategy strategy : allStrategies) { for (AudioAttributes audioAttr : audioAttrList) { if (strategy.supportsAudioAttributes(audioAttr)) { supportedStrategies.add(strategy); } } } return supportedStrategies.stream().distinct().collect(Collectors.toList()); } /** * Sets the preferred device for the given strategies. * * @param supportedStrategies A list of {@link AudioProductStrategy} used to configure audio * routing * @param hearingDevice {@link AudioDeviceAttributes} of the device to be changed in audio * routing * @param routingValue one of value defined in * {@link HearingAidAudioRoutingConstants.RoutingValue}, denotes routing * destination. * @return {code true} if the routing value successfully configure */ public boolean setPreferredDeviceRoutingStrategies( List supportedStrategies, AudioDeviceAttributes hearingDevice, @HearingAidAudioRoutingConstants.RoutingValue int routingValue) { boolean status; switch (routingValue) { case HearingAidAudioRoutingConstants.RoutingValue.AUTO: status = removePreferredDeviceForStrategies(supportedStrategies); return status; case HearingAidAudioRoutingConstants.RoutingValue.HEARING_DEVICE: status = removePreferredDeviceForStrategies(supportedStrategies); status &= setPreferredDeviceForStrategies(supportedStrategies, hearingDevice); return status; case HearingAidAudioRoutingConstants.RoutingValue.DEVICE_SPEAKER: status = removePreferredDeviceForStrategies(supportedStrategies); status &= setPreferredDeviceForStrategies(supportedStrategies, HearingAidAudioRoutingConstants.DEVICE_SPEAKER_OUT); return status; default: throw new IllegalArgumentException("Unexpected routingValue: " + routingValue); } } /** * Gets the matched hearing device {@link AudioDeviceAttributes} for {@code device}. * *

Will also try to match the {@link CachedBluetoothDevice#getSubDevice()} of {@code device} * * @param device the {@link CachedBluetoothDevice} need to be hearing aid device * @return the requested AudioDeviceAttributes or {@code null} if not match */ @Nullable public AudioDeviceAttributes getMatchedHearingDeviceAttributes(CachedBluetoothDevice device) { if (device == null || !device.isHearingAidDevice()) { return null; } AudioDeviceInfo[] audioDevices = mAudioManager.getDevices(AudioManager.GET_DEVICES_OUTPUTS); for (AudioDeviceInfo audioDevice : audioDevices) { // ASHA for TYPE_HEARING_AID, HAP for TYPE_BLE_HEADSET if (audioDevice.getType() == AudioDeviceInfo.TYPE_HEARING_AID || audioDevice.getType() == AudioDeviceInfo.TYPE_BLE_HEADSET) { if (matchAddress(device, audioDevice)) { return new AudioDeviceAttributes(audioDevice); } } } return null; } private boolean matchAddress(CachedBluetoothDevice device, AudioDeviceInfo audioDevice) { final String audioDeviceAddress = audioDevice.getAddress(); final CachedBluetoothDevice subDevice = device.getSubDevice(); final Set memberDevices = device.getMemberDevice(); return device.getAddress().equals(audioDeviceAddress) || (subDevice != null && subDevice.getAddress().equals(audioDeviceAddress)) || (!memberDevices.isEmpty() && memberDevices.stream().anyMatch( m -> m.getAddress().equals(audioDeviceAddress))); } private boolean setPreferredDeviceForStrategies(List strategies, AudioDeviceAttributes audioDevice) { boolean status = true; for (AudioProductStrategy strategy : strategies) { status &= mAudioManager.setPreferredDeviceForStrategy(strategy, audioDevice); } return status; } private boolean removePreferredDeviceForStrategies(List strategies) { boolean status = true; for (AudioProductStrategy strategy : strategies) { status &= mAudioManager.removePreferredDeviceForStrategy(strategy); } return status; } @VisibleForTesting public List getAudioProductStrategies() { return AudioManager.getAudioProductStrategies(); } }