1 /* 2 * Copyright (C) 2023 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.settingslib.bluetooth; 18 19 import android.content.Context; 20 import android.media.AudioAttributes; 21 import android.media.AudioDeviceAttributes; 22 import android.media.AudioDeviceInfo; 23 import android.media.AudioManager; 24 import android.media.audiopolicy.AudioProductStrategy; 25 26 import androidx.annotation.Nullable; 27 import androidx.annotation.VisibleForTesting; 28 29 import java.util.ArrayList; 30 import java.util.List; 31 import java.util.Set; 32 import java.util.stream.Collectors; 33 34 /** 35 * A helper class to configure the routing strategy for hearing aids. 36 */ 37 public class HearingAidAudioRoutingHelper { 38 39 private final AudioManager mAudioManager; 40 HearingAidAudioRoutingHelper(Context context)41 public HearingAidAudioRoutingHelper(Context context) { 42 mAudioManager = context.getSystemService(AudioManager.class); 43 } 44 45 /** 46 * Gets the list of {@link AudioProductStrategy} referred by the given list of usage values 47 * defined in {@link AudioAttributes} 48 */ getSupportedStrategies(int[] attributeSdkUsageList)49 public List<AudioProductStrategy> getSupportedStrategies(int[] attributeSdkUsageList) { 50 final List<AudioAttributes> audioAttrList = new ArrayList<>(attributeSdkUsageList.length); 51 for (int attributeSdkUsage : attributeSdkUsageList) { 52 audioAttrList.add(new AudioAttributes.Builder().setUsage(attributeSdkUsage).build()); 53 } 54 55 final List<AudioProductStrategy> allStrategies = getAudioProductStrategies(); 56 final List<AudioProductStrategy> supportedStrategies = new ArrayList<>(); 57 for (AudioProductStrategy strategy : allStrategies) { 58 for (AudioAttributes audioAttr : audioAttrList) { 59 if (strategy.supportsAudioAttributes(audioAttr)) { 60 supportedStrategies.add(strategy); 61 } 62 } 63 } 64 65 return supportedStrategies.stream().distinct().collect(Collectors.toList()); 66 } 67 68 /** 69 * Sets the preferred device for the given strategies. 70 * 71 * @param supportedStrategies A list of {@link AudioProductStrategy} used to configure audio 72 * routing 73 * @param hearingDevice {@link AudioDeviceAttributes} of the device to be changed in audio 74 * routing 75 * @param routingValue one of value defined in 76 * {@link HearingAidAudioRoutingConstants.RoutingValue}, denotes routing 77 * destination. 78 * @return {code true} if the routing value successfully configure 79 */ setPreferredDeviceRoutingStrategies( List<AudioProductStrategy> supportedStrategies, AudioDeviceAttributes hearingDevice, @HearingAidAudioRoutingConstants.RoutingValue int routingValue)80 public boolean setPreferredDeviceRoutingStrategies( 81 List<AudioProductStrategy> supportedStrategies, AudioDeviceAttributes hearingDevice, 82 @HearingAidAudioRoutingConstants.RoutingValue int routingValue) { 83 boolean status; 84 switch (routingValue) { 85 case HearingAidAudioRoutingConstants.RoutingValue.AUTO: 86 status = removePreferredDeviceForStrategies(supportedStrategies); 87 return status; 88 case HearingAidAudioRoutingConstants.RoutingValue.HEARING_DEVICE: 89 status = removePreferredDeviceForStrategies(supportedStrategies); 90 status &= setPreferredDeviceForStrategies(supportedStrategies, hearingDevice); 91 return status; 92 case HearingAidAudioRoutingConstants.RoutingValue.DEVICE_SPEAKER: 93 status = removePreferredDeviceForStrategies(supportedStrategies); 94 status &= setPreferredDeviceForStrategies(supportedStrategies, 95 HearingAidAudioRoutingConstants.DEVICE_SPEAKER_OUT); 96 return status; 97 default: 98 throw new IllegalArgumentException("Unexpected routingValue: " + routingValue); 99 } 100 } 101 102 /** 103 * Gets the matched hearing device {@link AudioDeviceAttributes} for {@code device}. 104 * 105 * <p>Will also try to match the {@link CachedBluetoothDevice#getSubDevice()} of {@code device} 106 * 107 * @param device the {@link CachedBluetoothDevice} need to be hearing aid device 108 * @return the requested AudioDeviceAttributes or {@code null} if not match 109 */ 110 @Nullable getMatchedHearingDeviceAttributes(CachedBluetoothDevice device)111 public AudioDeviceAttributes getMatchedHearingDeviceAttributes(CachedBluetoothDevice device) { 112 if (device == null || !device.isHearingAidDevice()) { 113 return null; 114 } 115 116 AudioDeviceInfo[] audioDevices = mAudioManager.getDevices(AudioManager.GET_DEVICES_OUTPUTS); 117 for (AudioDeviceInfo audioDevice : audioDevices) { 118 // ASHA for TYPE_HEARING_AID, HAP for TYPE_BLE_HEADSET 119 if (audioDevice.getType() == AudioDeviceInfo.TYPE_HEARING_AID 120 || audioDevice.getType() == AudioDeviceInfo.TYPE_BLE_HEADSET) { 121 if (matchAddress(device, audioDevice)) { 122 return new AudioDeviceAttributes(audioDevice); 123 } 124 } 125 } 126 return null; 127 } 128 matchAddress(CachedBluetoothDevice device, AudioDeviceInfo audioDevice)129 private boolean matchAddress(CachedBluetoothDevice device, AudioDeviceInfo audioDevice) { 130 final String audioDeviceAddress = audioDevice.getAddress(); 131 final CachedBluetoothDevice subDevice = device.getSubDevice(); 132 final Set<CachedBluetoothDevice> memberDevices = device.getMemberDevice(); 133 134 return device.getAddress().equals(audioDeviceAddress) 135 || (subDevice != null && subDevice.getAddress().equals(audioDeviceAddress)) 136 || (!memberDevices.isEmpty() && memberDevices.stream().anyMatch( 137 m -> m.getAddress().equals(audioDeviceAddress))); 138 } 139 setPreferredDeviceForStrategies(List<AudioProductStrategy> strategies, AudioDeviceAttributes audioDevice)140 private boolean setPreferredDeviceForStrategies(List<AudioProductStrategy> strategies, 141 AudioDeviceAttributes audioDevice) { 142 boolean status = true; 143 for (AudioProductStrategy strategy : strategies) { 144 status &= mAudioManager.setPreferredDeviceForStrategy(strategy, audioDevice); 145 146 } 147 148 return status; 149 } 150 removePreferredDeviceForStrategies(List<AudioProductStrategy> strategies)151 private boolean removePreferredDeviceForStrategies(List<AudioProductStrategy> strategies) { 152 boolean status = true; 153 for (AudioProductStrategy strategy : strategies) { 154 if (mAudioManager.getPreferredDeviceForStrategy(strategy) != null) { 155 status &= mAudioManager.removePreferredDeviceForStrategy(strategy); 156 } 157 } 158 159 return status; 160 } 161 162 @VisibleForTesting getAudioProductStrategies()163 public List<AudioProductStrategy> getAudioProductStrategies() { 164 return AudioManager.getAudioProductStrategies(); 165 } 166 } 167