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 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(int audioDeviceType)62 public boolean isAudioDeviceSetForType(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(int audioDeviceType)89 public void setTestCommunicationDevice(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(int audioDeviceType, BluetoothDevice btDevice)122 public boolean setCommunicationDevice(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(int audioDeviceType, BluetoothDevice btDevice)136 private boolean processSetCommunicationDevice(int audioDeviceType, 137 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 && !handleLeAudioDeviceSwitch) { 149 Log.i(this, "Communication device is already set for this audio type"); 150 return false; 151 } 152 153 AudioDeviceInfo activeDevice = null; 154 List<AudioDeviceInfo> devices = mAudioManager.getAvailableCommunicationDevices(); 155 if (devices.size() == 0) { 156 Log.w(this, "No communication devices available"); 157 return false; 158 } 159 160 for (AudioDeviceInfo device : devices) { 161 Log.i(this, "Available device type: " + device.getType()); 162 // Ensure that we do not select the same BT LE audio device for communication. 163 if ((audioDeviceType == device.getType() 164 || isUsbHeadsetType(audioDeviceType, device.getType())) 165 && !device.getAddress().equals(mBtAudioDevice)) { 166 activeDevice = device; 167 break; 168 } 169 } 170 171 if (activeDevice == null) { 172 Log.i(this, "No active device of type(s) %s available", 173 audioDeviceType == AudioDeviceInfo.TYPE_WIRED_HEADSET 174 ? Arrays.asList(AudioDeviceInfo.TYPE_WIRED_HEADSET, 175 AudioDeviceInfo.TYPE_USB_HEADSET) 176 : audioDeviceType); 177 return false; 178 } 179 180 // Force clear previous communication device, if one was set, before setting the new device. 181 if (mAudioDeviceType != sAUDIO_DEVICE_TYPE_INVALID) { 182 processClearCommunicationDevice(mAudioDeviceType); 183 } 184 185 // Turn activeDevice ON. 186 boolean result = mAudioManager.setCommunicationDevice(activeDevice); 187 if (!result) { 188 Log.w(this, "Could not set active device"); 189 } else { 190 Log.i(this, "Active device set"); 191 mAudioDeviceType = activeDevice.getType(); 192 if (isBtDevice) { 193 mBtAudioDevice = activeDevice.getAddress(); 194 if (audioDeviceType == AudioDeviceInfo.TYPE_BLE_HEADSET) { 195 mBluetoothRouteManager.onAudioOn(mBtAudioDevice); 196 } 197 } else if (Flags.communicationDeviceProtectedByLock()) { 198 // Clear BT device if it's still stored. Handles race condition for when a non-BT 199 // device is set for communication shortly after a BT (LE) device is set for 200 // communication but the selection hasn't been cleared yet. 201 mBtAudioDevice = null; 202 } 203 } 204 return result; 205 } 206 /* 207 * Clears the communication device for the passed in audio device types, given that the device 208 * has previously been set for communication. 209 * @param audioDeviceTypes The supported audio device types for the device. 210 */ clearCommunicationDevice(int audioDeviceType)211 public void clearCommunicationDevice(int audioDeviceType) { 212 if (Flags.communicationDeviceProtectedByLock()) { 213 mLock.lock(); 214 } 215 try { 216 processClearCommunicationDevice(audioDeviceType); 217 } finally { 218 if (Flags.communicationDeviceProtectedByLock()) { 219 mLock.unlock(); 220 } 221 } 222 } 223 processClearCommunicationDevice(int audioDeviceType)224 public void processClearCommunicationDevice(int audioDeviceType) { 225 if (audioDeviceType == sAUDIO_DEVICE_TYPE_INVALID) { 226 Log.i(this, "clearCommunicationDevice: Skip clearing communication device" 227 + "for invalid audio type (-1)."); 228 } 229 230 // There is only one audio device type associated with each type of BT device. 231 boolean isBtDevice = BT_AUDIO_DEVICE_INFO_TYPES.contains(audioDeviceType); 232 Log.i(this, "clearCommunicationDevice: type = %s, isBtDevice = %s", 233 audioDeviceType, isBtDevice); 234 235 if (audioDeviceType != mAudioDeviceType 236 && !isUsbHeadsetType(audioDeviceType, mAudioDeviceType)) { 237 Log.i(this, "Unable to clear communication device of type(s), %s. " 238 + "Device does not correspond to the locally requested device type.", 239 audioDeviceType == AudioDeviceInfo.TYPE_WIRED_HEADSET 240 ? Arrays.asList(AudioDeviceInfo.TYPE_WIRED_HEADSET, 241 AudioDeviceInfo.TYPE_USB_HEADSET) 242 : audioDeviceType 243 ); 244 return; 245 } 246 247 if (mAudioManager == null) { 248 Log.i(this, "clearCommunicationDevice: mAudioManager is null"); 249 return; 250 } 251 252 // Clear device and reset locally saved device type. 253 mAudioManager.clearCommunicationDevice(); 254 mAudioDeviceType = sAUDIO_DEVICE_TYPE_INVALID; 255 256 if (isBtDevice && mBtAudioDevice != null) { 257 // Signal that BT audio was lost for device. 258 mBluetoothRouteManager.onAudioLost(mBtAudioDevice); 259 mBtAudioDevice = null; 260 } 261 } 262 isUsbHeadsetType(int audioDeviceType, int sourceType)263 private boolean isUsbHeadsetType(int audioDeviceType, int sourceType) { 264 return audioDeviceType == AudioDeviceInfo.TYPE_WIRED_HEADSET 265 && sourceType == AudioDeviceInfo.TYPE_USB_HEADSET; 266 } 267 } 268