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.server.telecom; 18 19 import static com.android.server.telecom.AudioRoute.BT_AUDIO_DEVICE_INFO_TYPES; 20 21 import android.bluetooth.BluetoothDevice; 22 import android.content.Context; 23 import android.media.AudioDeviceInfo; 24 import android.media.AudioManager; 25 import android.telecom.Log; 26 27 import com.android.internal.annotations.VisibleForTesting; 28 import com.android.server.telecom.bluetooth.BluetoothRouteManager; 29 import com.android.server.telecom.flags.Flags; 30 31 import java.util.Arrays; 32 import java.util.List; 33 import java.util.concurrent.Semaphore; 34 import java.util.concurrent.locks.Lock; 35 import java.util.concurrent.locks.ReentrantLock; 36 37 /** 38 * Helper class used to keep track of the requested communication device within Telecom for audio 39 * use cases. Handles the set/clear communication use case logic for all audio routes (speaker, BT, 40 * headset, and earpiece). For BT devices, this handles switches between hearing aids, SCO, and LE 41 * audio (also takes into account switching between multiple LE audio devices). 42 */ 43 public class CallAudioCommunicationDeviceTracker { 44 45 // Use -1 indicates device is not set for any communication use case 46 private static final int sAUDIO_DEVICE_TYPE_INVALID = -1; 47 private AudioManager mAudioManager; 48 private BluetoothRouteManager mBluetoothRouteManager; 49 private @AudioDeviceInfo.AudioDeviceType int mAudioDeviceType = sAUDIO_DEVICE_TYPE_INVALID; 50 // Keep track of the locally requested BT audio device if set 51 private String mBtAudioDevice = null; 52 private final Lock mLock = new ReentrantLock(); 53 CallAudioCommunicationDeviceTracker(Context context)54 public CallAudioCommunicationDeviceTracker(Context context) { 55 mAudioManager = context.getSystemService(AudioManager.class); 56 } 57 setBluetoothRouteManager(BluetoothRouteManager bluetoothRouteManager)58 public void setBluetoothRouteManager(BluetoothRouteManager bluetoothRouteManager) { 59 mBluetoothRouteManager = bluetoothRouteManager; 60 } 61 isAudioDeviceSetForType(@udioDeviceInfo.AudioDeviceType int audioDeviceType)62 public boolean isAudioDeviceSetForType(@AudioDeviceInfo.AudioDeviceType int audioDeviceType) { 63 if (Flags.communicationDeviceProtectedByLock()) { 64 mLock.lock(); 65 } 66 try { 67 return mAudioDeviceType == audioDeviceType; 68 } finally { 69 if (Flags.communicationDeviceProtectedByLock()) { 70 mLock.unlock(); 71 } 72 } 73 } 74 getCurrentLocallyRequestedCommunicationDevice()75 public int getCurrentLocallyRequestedCommunicationDevice() { 76 if (Flags.communicationDeviceProtectedByLock()) { 77 mLock.lock(); 78 } 79 try { 80 return mAudioDeviceType; 81 } finally { 82 if (Flags.communicationDeviceProtectedByLock()) { 83 mLock.unlock(); 84 } 85 } 86 } 87 88 @VisibleForTesting setTestCommunicationDevice(@udioDeviceInfo.AudioDeviceType int audioDeviceType)89 public void setTestCommunicationDevice(@AudioDeviceInfo.AudioDeviceType int audioDeviceType) { 90 mAudioDeviceType = audioDeviceType; 91 } 92 clearBtCommunicationDevice()93 public void clearBtCommunicationDevice() { 94 if (Flags.communicationDeviceProtectedByLock()) { 95 mLock.lock(); 96 } 97 try { 98 if (mBtAudioDevice == null) { 99 Log.i(this, "No bluetooth device was set for communication that can be cleared."); 100 } else { 101 // If mBtAudioDevice is set, we know a BT audio device was set for communication so 102 // mAudioDeviceType corresponds to a BT device type (e.g. hearing aid, SCO, LE). 103 processClearCommunicationDevice(mAudioDeviceType); 104 } 105 } finally { 106 if (Flags.communicationDeviceProtectedByLock()) { 107 mLock.unlock(); 108 } 109 } 110 } 111 112 /* 113 * Sets the communication device for the passed in audio device type, if it's available for 114 * communication use cases. Tries to clear any communication device which was previously 115 * requested for communication before setting the new device. 116 * @param audioDeviceTypes The supported audio device types for the device. 117 * @param btDevice The bluetooth device to connect to (only used for switching between multiple 118 * LE audio devices). 119 * @return {@code true} if the device was set for communication, {@code false} if the device 120 * wasn't set. 121 */ setCommunicationDevice(@udioDeviceInfo.AudioDeviceType int audioDeviceType, BluetoothDevice btDevice)122 public boolean setCommunicationDevice(@AudioDeviceInfo.AudioDeviceType int audioDeviceType, 123 BluetoothDevice btDevice) { 124 if (Flags.communicationDeviceProtectedByLock()) { 125 mLock.lock(); 126 } 127 try { 128 return processSetCommunicationDevice(audioDeviceType, btDevice); 129 } finally { 130 if (Flags.communicationDeviceProtectedByLock()) { 131 mLock.unlock(); 132 } 133 } 134 } 135 processSetCommunicationDevice( @udioDeviceInfo.AudioDeviceType int audioDeviceType, BluetoothDevice btDevice)136 private boolean processSetCommunicationDevice( 137 @AudioDeviceInfo.AudioDeviceType int audioDeviceType, BluetoothDevice btDevice) { 138 // There is only one audio device type associated with each type of BT device. 139 boolean isBtDevice = BT_AUDIO_DEVICE_INFO_TYPES.contains(audioDeviceType); 140 Log.i(this, "setCommunicationDevice: type = %s, isBtDevice = %s, btDevice = %s", 141 audioDeviceType, isBtDevice, btDevice); 142 143 // Account for switching between multiple LE audio devices. 144 boolean handleLeAudioDeviceSwitch = btDevice != null 145 && !btDevice.getAddress().equals(mBtAudioDevice); 146 if ((audioDeviceType == mAudioDeviceType 147 || isUsbHeadsetType(audioDeviceType, mAudioDeviceType) 148 || isSpeakerType(audioDeviceType, mAudioDeviceType)) 149 && !handleLeAudioDeviceSwitch) { 150 Log.i(this, "Communication device is already set for this audio type"); 151 return false; 152 } 153 154 AudioDeviceInfo activeDevice = null; 155 List<AudioDeviceInfo> devices = mAudioManager.getAvailableCommunicationDevices(); 156 if (devices.size() == 0) { 157 Log.w(this, "No communication devices available"); 158 return false; 159 } 160 161 for (AudioDeviceInfo device : devices) { 162 Log.i(this, "Available device type: " + device.getType()); 163 // Ensure that we do not select the same BT LE audio device for communication. 164 if ((audioDeviceType == device.getType() 165 || isUsbHeadsetType(audioDeviceType, device.getType()) 166 || isSpeakerType(audioDeviceType, device.getType())) 167 && !device.getAddress().equals(mBtAudioDevice)) { 168 activeDevice = device; 169 break; 170 } 171 } 172 173 if (activeDevice == null) { 174 Log.i(this, "No active device of type(s) %s available", 175 audioDeviceType == AudioDeviceInfo.TYPE_WIRED_HEADSET 176 ? Arrays.asList(AudioDeviceInfo.TYPE_WIRED_HEADSET, 177 AudioDeviceInfo.TYPE_USB_HEADSET) 178 : audioDeviceType); 179 return false; 180 } 181 182 // Force clear previous communication device, if one was set, before setting the new device. 183 if (mAudioDeviceType != sAUDIO_DEVICE_TYPE_INVALID) { 184 processClearCommunicationDevice(mAudioDeviceType); 185 } 186 187 // Turn activeDevice ON. 188 boolean result = mAudioManager.setCommunicationDevice(activeDevice); 189 if (!result) { 190 Log.w(this, "Could not set active device"); 191 } else { 192 Log.i(this, "Active device set"); 193 mAudioDeviceType = activeDevice.getType(); 194 if (isBtDevice) { 195 mBtAudioDevice = activeDevice.getAddress(); 196 if (audioDeviceType == AudioDeviceInfo.TYPE_BLE_HEADSET) { 197 mBluetoothRouteManager.onAudioOn(mBtAudioDevice); 198 } 199 } else if (Flags.communicationDeviceProtectedByLock()) { 200 // Clear BT device if it's still stored. Handles race condition for when a non-BT 201 // device is set for communication shortly after a BT (LE) device is set for 202 // communication but the selection hasn't been cleared yet. 203 mBtAudioDevice = null; 204 } 205 } 206 return result; 207 } 208 /* 209 * Clears the communication device for the passed in audio device types, given that the device 210 * has previously been set for communication. 211 * @param audioDeviceTypes The supported audio device types for the device. 212 */ clearCommunicationDevice(@udioDeviceInfo.AudioDeviceType int audioDeviceType)213 public void clearCommunicationDevice(@AudioDeviceInfo.AudioDeviceType int audioDeviceType) { 214 if (Flags.communicationDeviceProtectedByLock()) { 215 mLock.lock(); 216 } 217 try { 218 processClearCommunicationDevice(audioDeviceType); 219 } finally { 220 if (Flags.communicationDeviceProtectedByLock()) { 221 mLock.unlock(); 222 } 223 } 224 } 225 processClearCommunicationDevice( @udioDeviceInfo.AudioDeviceType int audioDeviceType)226 public void processClearCommunicationDevice( 227 @AudioDeviceInfo.AudioDeviceType int audioDeviceType) { 228 if (audioDeviceType == sAUDIO_DEVICE_TYPE_INVALID) { 229 Log.i(this, "clearCommunicationDevice: Skip clearing communication device" 230 + "for invalid audio type (-1)."); 231 } 232 233 // There is only one audio device type associated with each type of BT device. 234 boolean isBtDevice = BT_AUDIO_DEVICE_INFO_TYPES.contains(audioDeviceType); 235 Log.i(this, "clearCommunicationDevice: type = %s, isBtDevice = %s", 236 audioDeviceType, isBtDevice); 237 238 if (audioDeviceType != mAudioDeviceType 239 && !isUsbHeadsetType(audioDeviceType, mAudioDeviceType) 240 && !isSpeakerType(audioDeviceType, mAudioDeviceType)) { 241 Log.i(this, "Unable to clear communication device of type(s) %s. " 242 + "Device does not correspond to the locally requested device type %s.", 243 audioDeviceType == AudioDeviceInfo.TYPE_WIRED_HEADSET 244 ? Arrays.asList(AudioDeviceInfo.TYPE_WIRED_HEADSET, 245 AudioDeviceInfo.TYPE_USB_HEADSET) 246 : audioDeviceType, 247 mAudioDeviceType 248 ); 249 return; 250 } 251 252 if (mAudioManager == null) { 253 Log.i(this, "clearCommunicationDevice: mAudioManager is null"); 254 return; 255 } 256 257 // Clear device and reset locally saved device type. 258 Log.i(this, "clearCommunicationDevice: AudioManager#clearCommunicationDevice()"); 259 mAudioManager.clearCommunicationDevice(); 260 mAudioDeviceType = sAUDIO_DEVICE_TYPE_INVALID; 261 262 if (isBtDevice && mBtAudioDevice != null) { 263 // Signal that BT audio was lost for device. 264 mBluetoothRouteManager.onAudioLost(mBtAudioDevice); 265 mBtAudioDevice = null; 266 } 267 } 268 isUsbHeadsetType(@udioDeviceInfo.AudioDeviceType int audioDeviceType, @AudioDeviceInfo.AudioDeviceType int sourceType)269 private boolean isUsbHeadsetType(@AudioDeviceInfo.AudioDeviceType int audioDeviceType, 270 @AudioDeviceInfo.AudioDeviceType int sourceType) { 271 return audioDeviceType == AudioDeviceInfo.TYPE_WIRED_HEADSET 272 && sourceType == AudioDeviceInfo.TYPE_USB_HEADSET; 273 } 274 isSpeakerType(@udioDeviceInfo.AudioDeviceType int audioDeviceType, @AudioDeviceInfo.AudioDeviceType int sourceType)275 private boolean isSpeakerType(@AudioDeviceInfo.AudioDeviceType int audioDeviceType, 276 @AudioDeviceInfo.AudioDeviceType int sourceType) { 277 if (!Flags.busDeviceIsASpeaker()) return false; 278 return audioDeviceType == AudioDeviceInfo.TYPE_BUILTIN_SPEAKER 279 && sourceType == AudioDeviceInfo.TYPE_BUS; 280 } 281 } 282