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.server.telecom; 18 19 import static com.android.server.telecom.CallAudioRouteAdapter.BT_AUDIO_CONNECTED; 20 import static com.android.server.telecom.CallAudioRouteAdapter.BT_AUDIO_DISCONNECTED; 21 import static com.android.server.telecom.CallAudioRouteAdapter.PENDING_ROUTE_FAILED; 22 import static com.android.server.telecom.CallAudioRouteAdapter.SPEAKER_OFF; 23 import static com.android.server.telecom.CallAudioRouteAdapter.SPEAKER_ON; 24 25 import android.annotation.IntDef; 26 import android.bluetooth.BluetoothDevice; 27 import android.bluetooth.BluetoothHeadset; 28 import android.bluetooth.BluetoothStatusCodes; 29 import android.media.AudioDeviceInfo; 30 import android.media.AudioManager; 31 import android.sysprop.BluetoothProperties; 32 import android.telecom.Log; 33 import android.util.Pair; 34 35 import com.android.internal.annotations.VisibleForTesting; 36 import com.android.server.telecom.bluetooth.BluetoothRouteManager; 37 38 import java.lang.annotation.Retention; 39 import java.lang.annotation.RetentionPolicy; 40 import java.util.ArrayList; 41 import java.util.HashMap; 42 import java.util.List; 43 import java.util.Objects; 44 import java.util.Set; 45 import java.util.concurrent.CompletableFuture; 46 import java.util.concurrent.ExecutionException; 47 import java.util.concurrent.RejectedExecutionException; 48 import java.util.concurrent.ScheduledExecutorService; 49 import java.util.concurrent.ScheduledThreadPoolExecutor; 50 import java.util.concurrent.TimeUnit; 51 52 public class AudioRoute { 53 public static class Factory { 54 private final ScheduledExecutorService mScheduledExecutorService = 55 new ScheduledThreadPoolExecutor(1); 56 private CompletableFuture<AudioRoute> mAudioRouteFuture; create(@udioRouteType int type, String bluetoothAddress, AudioManager audioManager)57 public AudioRoute create(@AudioRouteType int type, String bluetoothAddress, 58 AudioManager audioManager) throws RuntimeException { 59 mAudioRouteFuture = new CompletableFuture(); 60 createRetry(type, bluetoothAddress, audioManager, MAX_CONNECTION_RETRIES); 61 try { 62 return mAudioRouteFuture.get(); 63 } catch (InterruptedException | ExecutionException e) { 64 throw new RuntimeException("Error when creating requested audio route"); 65 } 66 } createRetry(@udioRouteType int type, String bluetoothAddress, AudioManager audioManager, int retryCount)67 private void createRetry(@AudioRouteType int type, String bluetoothAddress, 68 AudioManager audioManager, int retryCount) { 69 // Early exit if exceeded max number of retries (and complete the future). 70 if (retryCount == 0) { 71 mAudioRouteFuture.complete(null); 72 return; 73 } 74 75 Log.i(this, "createRetry; type=%s, address=%s, retryCount=%d", 76 DEVICE_TYPE_STRINGS.get(type), bluetoothAddress, retryCount); 77 AudioDeviceInfo routeInfo = null; 78 List<AudioDeviceInfo> infos = audioManager.getAvailableCommunicationDevices(); 79 List<Integer> possibleInfoTypes = AUDIO_ROUTE_TYPE_TO_DEVICE_INFO_TYPE.get(type); 80 for (AudioDeviceInfo info : infos) { 81 Log.i(this, "type: " + info.getType()); 82 if (possibleInfoTypes != null && possibleInfoTypes.contains(info.getType())) { 83 if (BT_AUDIO_ROUTE_TYPES.contains(type)) { 84 if (bluetoothAddress.equals(info.getAddress())) { 85 routeInfo = info; 86 break; 87 } 88 } else { 89 routeInfo = info; 90 break; 91 } 92 } 93 } 94 // Try connecting BT device anyway (to handle wearables not showing as available 95 // communication device or LE device not showing up since it may not be the lead 96 // device). 97 if (routeInfo == null && bluetoothAddress == null) { 98 try { 99 mScheduledExecutorService.schedule( 100 () -> createRetry(type, bluetoothAddress, audioManager, retryCount - 1), 101 RETRY_TIME_DELAY, TimeUnit.MILLISECONDS); 102 } catch (RejectedExecutionException e) { 103 Log.e(this, e, "Could not schedule retry for audio routing."); 104 } 105 } else { 106 mAudioRouteFuture.complete(new AudioRoute(type, bluetoothAddress, routeInfo)); 107 } 108 } 109 } 110 111 private static final long RETRY_TIME_DELAY = 500L; 112 private static final int MAX_CONNECTION_RETRIES = 2; 113 public static final int TYPE_INVALID = 0; 114 public static final int TYPE_EARPIECE = 1; 115 public static final int TYPE_WIRED = 2; 116 public static final int TYPE_SPEAKER = 3; 117 public static final int TYPE_DOCK = 4; 118 public static final int TYPE_BLUETOOTH_SCO = 5; 119 public static final int TYPE_BLUETOOTH_HA = 6; 120 public static final int TYPE_BLUETOOTH_LE = 7; 121 public static final int TYPE_STREAMING = 8; 122 // Used by auto 123 public static final int TYPE_BUS = 9; 124 @IntDef(prefix = "TYPE", value = { 125 TYPE_INVALID, 126 TYPE_EARPIECE, 127 TYPE_WIRED, 128 TYPE_SPEAKER, 129 TYPE_DOCK, 130 TYPE_BLUETOOTH_SCO, 131 TYPE_BLUETOOTH_HA, 132 TYPE_BLUETOOTH_LE, 133 TYPE_STREAMING, 134 TYPE_BUS 135 }) 136 @Retention(RetentionPolicy.SOURCE) 137 public @interface AudioRouteType {} 138 139 private @AudioRouteType int mAudioRouteType; 140 private String mBluetoothAddress; 141 private AudioDeviceInfo mInfo; 142 private boolean mIsDestRouteForWatch; 143 private boolean mIsScoManagedByAudio; 144 public static final Set<Integer> BT_AUDIO_DEVICE_INFO_TYPES = Set.of( 145 AudioDeviceInfo.TYPE_BLE_HEADSET, 146 AudioDeviceInfo.TYPE_BLE_SPEAKER, 147 AudioDeviceInfo.TYPE_BLE_BROADCAST, 148 AudioDeviceInfo.TYPE_HEARING_AID, 149 AudioDeviceInfo.TYPE_BLUETOOTH_SCO 150 ); 151 152 public static final Set<Integer> BT_AUDIO_ROUTE_TYPES = Set.of( 153 AudioRoute.TYPE_BLUETOOTH_SCO, 154 AudioRoute.TYPE_BLUETOOTH_HA, 155 AudioRoute.TYPE_BLUETOOTH_LE 156 ); 157 158 public static final HashMap<Integer, String> DEVICE_TYPE_STRINGS; 159 static { 160 DEVICE_TYPE_STRINGS = new HashMap<>(); DEVICE_TYPE_STRINGS.put(TYPE_EARPIECE, "TYPE_EARPIECE")161 DEVICE_TYPE_STRINGS.put(TYPE_EARPIECE, "TYPE_EARPIECE"); DEVICE_TYPE_STRINGS.put(TYPE_WIRED, "TYPE_WIRED_HEADSET")162 DEVICE_TYPE_STRINGS.put(TYPE_WIRED, "TYPE_WIRED_HEADSET"); DEVICE_TYPE_STRINGS.put(TYPE_SPEAKER, "TYPE_SPEAKER")163 DEVICE_TYPE_STRINGS.put(TYPE_SPEAKER, "TYPE_SPEAKER"); DEVICE_TYPE_STRINGS.put(TYPE_DOCK, "TYPE_DOCK")164 DEVICE_TYPE_STRINGS.put(TYPE_DOCK, "TYPE_DOCK"); DEVICE_TYPE_STRINGS.put(TYPE_BUS, "TYPE_BUS")165 DEVICE_TYPE_STRINGS.put(TYPE_BUS, "TYPE_BUS"); DEVICE_TYPE_STRINGS.put(TYPE_BLUETOOTH_SCO, "TYPE_BLUETOOTH_SCO")166 DEVICE_TYPE_STRINGS.put(TYPE_BLUETOOTH_SCO, "TYPE_BLUETOOTH_SCO"); DEVICE_TYPE_STRINGS.put(TYPE_BLUETOOTH_HA, "TYPE_BLUETOOTH_HA")167 DEVICE_TYPE_STRINGS.put(TYPE_BLUETOOTH_HA, "TYPE_BLUETOOTH_HA"); DEVICE_TYPE_STRINGS.put(TYPE_BLUETOOTH_LE, "TYPE_BLUETOOTH_LE")168 DEVICE_TYPE_STRINGS.put(TYPE_BLUETOOTH_LE, "TYPE_BLUETOOTH_LE"); DEVICE_TYPE_STRINGS.put(TYPE_STREAMING, "TYPE_STREAMING")169 DEVICE_TYPE_STRINGS.put(TYPE_STREAMING, "TYPE_STREAMING"); 170 } 171 172 public static final HashMap<Integer, Integer> DEVICE_INFO_TYPE_TO_AUDIO_ROUTE_TYPE; 173 static { 174 DEVICE_INFO_TYPE_TO_AUDIO_ROUTE_TYPE = new HashMap<>(); DEVICE_INFO_TYPE_TO_AUDIO_ROUTE_TYPE.put(AudioDeviceInfo.TYPE_BUILTIN_EARPIECE, TYPE_EARPIECE)175 DEVICE_INFO_TYPE_TO_AUDIO_ROUTE_TYPE.put(AudioDeviceInfo.TYPE_BUILTIN_EARPIECE, 176 TYPE_EARPIECE); DEVICE_INFO_TYPE_TO_AUDIO_ROUTE_TYPE.put(AudioDeviceInfo.TYPE_BUILTIN_SPEAKER, TYPE_SPEAKER)177 DEVICE_INFO_TYPE_TO_AUDIO_ROUTE_TYPE.put(AudioDeviceInfo.TYPE_BUILTIN_SPEAKER, 178 TYPE_SPEAKER); DEVICE_INFO_TYPE_TO_AUDIO_ROUTE_TYPE.put(AudioDeviceInfo.TYPE_WIRED_HEADSET, TYPE_WIRED)179 DEVICE_INFO_TYPE_TO_AUDIO_ROUTE_TYPE.put(AudioDeviceInfo.TYPE_WIRED_HEADSET, TYPE_WIRED); DEVICE_INFO_TYPE_TO_AUDIO_ROUTE_TYPE.put(AudioDeviceInfo.TYPE_WIRED_HEADPHONES, TYPE_WIRED)180 DEVICE_INFO_TYPE_TO_AUDIO_ROUTE_TYPE.put(AudioDeviceInfo.TYPE_WIRED_HEADPHONES, TYPE_WIRED); DEVICE_INFO_TYPE_TO_AUDIO_ROUTE_TYPE.put(AudioDeviceInfo.TYPE_BLUETOOTH_SCO, TYPE_BLUETOOTH_SCO)181 DEVICE_INFO_TYPE_TO_AUDIO_ROUTE_TYPE.put(AudioDeviceInfo.TYPE_BLUETOOTH_SCO, 182 TYPE_BLUETOOTH_SCO); DEVICE_INFO_TYPE_TO_AUDIO_ROUTE_TYPE.put(AudioDeviceInfo.TYPE_USB_DEVICE, TYPE_WIRED)183 DEVICE_INFO_TYPE_TO_AUDIO_ROUTE_TYPE.put(AudioDeviceInfo.TYPE_USB_DEVICE, TYPE_WIRED); DEVICE_INFO_TYPE_TO_AUDIO_ROUTE_TYPE.put(AudioDeviceInfo.TYPE_USB_ACCESSORY, TYPE_WIRED)184 DEVICE_INFO_TYPE_TO_AUDIO_ROUTE_TYPE.put(AudioDeviceInfo.TYPE_USB_ACCESSORY, TYPE_WIRED); DEVICE_INFO_TYPE_TO_AUDIO_ROUTE_TYPE.put(AudioDeviceInfo.TYPE_DOCK, TYPE_DOCK)185 DEVICE_INFO_TYPE_TO_AUDIO_ROUTE_TYPE.put(AudioDeviceInfo.TYPE_DOCK, TYPE_DOCK); DEVICE_INFO_TYPE_TO_AUDIO_ROUTE_TYPE.put(AudioDeviceInfo.TYPE_USB_HEADSET, TYPE_WIRED)186 DEVICE_INFO_TYPE_TO_AUDIO_ROUTE_TYPE.put(AudioDeviceInfo.TYPE_USB_HEADSET, TYPE_WIRED); DEVICE_INFO_TYPE_TO_AUDIO_ROUTE_TYPE.put(AudioDeviceInfo.TYPE_HEARING_AID, TYPE_BLUETOOTH_HA)187 DEVICE_INFO_TYPE_TO_AUDIO_ROUTE_TYPE.put(AudioDeviceInfo.TYPE_HEARING_AID, 188 TYPE_BLUETOOTH_HA); DEVICE_INFO_TYPE_TO_AUDIO_ROUTE_TYPE.put(AudioDeviceInfo.TYPE_BLE_HEADSET, TYPE_BLUETOOTH_LE)189 DEVICE_INFO_TYPE_TO_AUDIO_ROUTE_TYPE.put(AudioDeviceInfo.TYPE_BLE_HEADSET, 190 TYPE_BLUETOOTH_LE); DEVICE_INFO_TYPE_TO_AUDIO_ROUTE_TYPE.put(AudioDeviceInfo.TYPE_BLE_SPEAKER, TYPE_BLUETOOTH_LE)191 DEVICE_INFO_TYPE_TO_AUDIO_ROUTE_TYPE.put(AudioDeviceInfo.TYPE_BLE_SPEAKER, 192 TYPE_BLUETOOTH_LE); DEVICE_INFO_TYPE_TO_AUDIO_ROUTE_TYPE.put(AudioDeviceInfo.TYPE_BLE_BROADCAST, TYPE_BLUETOOTH_LE)193 DEVICE_INFO_TYPE_TO_AUDIO_ROUTE_TYPE.put(AudioDeviceInfo.TYPE_BLE_BROADCAST, 194 TYPE_BLUETOOTH_LE); DEVICE_INFO_TYPE_TO_AUDIO_ROUTE_TYPE.put(AudioDeviceInfo.TYPE_DOCK_ANALOG, TYPE_DOCK)195 DEVICE_INFO_TYPE_TO_AUDIO_ROUTE_TYPE.put(AudioDeviceInfo.TYPE_DOCK_ANALOG, TYPE_DOCK); DEVICE_INFO_TYPE_TO_AUDIO_ROUTE_TYPE.put(AudioDeviceInfo.TYPE_BUS, TYPE_BUS)196 DEVICE_INFO_TYPE_TO_AUDIO_ROUTE_TYPE.put(AudioDeviceInfo.TYPE_BUS, TYPE_BUS); 197 } 198 199 private static final HashMap<Integer, List<Integer>> AUDIO_ROUTE_TYPE_TO_DEVICE_INFO_TYPE; 200 static { 201 AUDIO_ROUTE_TYPE_TO_DEVICE_INFO_TYPE = new HashMap<>(); 202 List<Integer> earpieceDeviceInfoTypes = new ArrayList<>(); 203 earpieceDeviceInfoTypes.add(AudioDeviceInfo.TYPE_BUILTIN_EARPIECE); AUDIO_ROUTE_TYPE_TO_DEVICE_INFO_TYPE.put(TYPE_EARPIECE, earpieceDeviceInfoTypes)204 AUDIO_ROUTE_TYPE_TO_DEVICE_INFO_TYPE.put(TYPE_EARPIECE, earpieceDeviceInfoTypes); 205 206 List<Integer> wiredDeviceInfoTypes = new ArrayList<>(); 207 wiredDeviceInfoTypes.add(AudioDeviceInfo.TYPE_WIRED_HEADSET); 208 wiredDeviceInfoTypes.add(AudioDeviceInfo.TYPE_WIRED_HEADPHONES); 209 wiredDeviceInfoTypes.add(AudioDeviceInfo.TYPE_USB_DEVICE); 210 wiredDeviceInfoTypes.add(AudioDeviceInfo.TYPE_USB_ACCESSORY); 211 wiredDeviceInfoTypes.add(AudioDeviceInfo.TYPE_USB_HEADSET); AUDIO_ROUTE_TYPE_TO_DEVICE_INFO_TYPE.put(TYPE_WIRED, wiredDeviceInfoTypes)212 AUDIO_ROUTE_TYPE_TO_DEVICE_INFO_TYPE.put(TYPE_WIRED, wiredDeviceInfoTypes); 213 214 List<Integer> speakerDeviceInfoTypes = new ArrayList<>(); 215 speakerDeviceInfoTypes.add(AudioDeviceInfo.TYPE_BUILTIN_SPEAKER); AUDIO_ROUTE_TYPE_TO_DEVICE_INFO_TYPE.put(TYPE_SPEAKER, speakerDeviceInfoTypes)216 AUDIO_ROUTE_TYPE_TO_DEVICE_INFO_TYPE.put(TYPE_SPEAKER, speakerDeviceInfoTypes); 217 218 List<Integer> dockDeviceInfoTypes = new ArrayList<>(); 219 dockDeviceInfoTypes.add(AudioDeviceInfo.TYPE_DOCK); 220 dockDeviceInfoTypes.add(AudioDeviceInfo.TYPE_DOCK_ANALOG); AUDIO_ROUTE_TYPE_TO_DEVICE_INFO_TYPE.put(TYPE_DOCK, dockDeviceInfoTypes)221 AUDIO_ROUTE_TYPE_TO_DEVICE_INFO_TYPE.put(TYPE_DOCK, dockDeviceInfoTypes); 222 223 List<Integer> busDeviceInfoTypes = new ArrayList<>(); 224 busDeviceInfoTypes.add(AudioDeviceInfo.TYPE_BUS); AUDIO_ROUTE_TYPE_TO_DEVICE_INFO_TYPE.put(TYPE_BUS, busDeviceInfoTypes)225 AUDIO_ROUTE_TYPE_TO_DEVICE_INFO_TYPE.put(TYPE_BUS, busDeviceInfoTypes); 226 227 List<Integer> bluetoothScoDeviceInfoTypes = new ArrayList<>(); 228 bluetoothScoDeviceInfoTypes.add(AudioDeviceInfo.TYPE_BLUETOOTH_A2DP); 229 bluetoothScoDeviceInfoTypes.add(AudioDeviceInfo.TYPE_BLUETOOTH_SCO); AUDIO_ROUTE_TYPE_TO_DEVICE_INFO_TYPE.put(TYPE_BLUETOOTH_SCO, bluetoothScoDeviceInfoTypes)230 AUDIO_ROUTE_TYPE_TO_DEVICE_INFO_TYPE.put(TYPE_BLUETOOTH_SCO, bluetoothScoDeviceInfoTypes); 231 232 List<Integer> bluetoothHearingAidDeviceInfoTypes = new ArrayList<>(); 233 bluetoothHearingAidDeviceInfoTypes.add(AudioDeviceInfo.TYPE_HEARING_AID); AUDIO_ROUTE_TYPE_TO_DEVICE_INFO_TYPE.put(TYPE_BLUETOOTH_HA, bluetoothHearingAidDeviceInfoTypes)234 AUDIO_ROUTE_TYPE_TO_DEVICE_INFO_TYPE.put(TYPE_BLUETOOTH_HA, 235 bluetoothHearingAidDeviceInfoTypes); 236 237 List<Integer> bluetoothLeDeviceInfoTypes = new ArrayList<>(); 238 bluetoothLeDeviceInfoTypes.add(AudioDeviceInfo.TYPE_BLE_HEADSET); 239 bluetoothLeDeviceInfoTypes.add(AudioDeviceInfo.TYPE_BLE_SPEAKER); 240 bluetoothLeDeviceInfoTypes.add(AudioDeviceInfo.TYPE_BLE_BROADCAST); AUDIO_ROUTE_TYPE_TO_DEVICE_INFO_TYPE.put(TYPE_BLUETOOTH_LE, bluetoothLeDeviceInfoTypes)241 AUDIO_ROUTE_TYPE_TO_DEVICE_INFO_TYPE.put(TYPE_BLUETOOTH_LE, bluetoothLeDeviceInfoTypes); 242 } 243 getType()244 public int getType() { 245 return mAudioRouteType; 246 } 247 isWatch()248 public boolean isWatch() { 249 return mIsDestRouteForWatch; 250 } 251 getBluetoothAddress()252 String getBluetoothAddress() { 253 return mBluetoothAddress; 254 } 255 256 // Invoked when entered pending route whose dest route is this route onDestRouteAsPendingRoute(boolean active, PendingAudioRoute pendingAudioRoute, BluetoothDevice device, AudioManager audioManager, BluetoothRouteManager bluetoothRouteManager, boolean isScoAlreadyConnected)257 void onDestRouteAsPendingRoute(boolean active, PendingAudioRoute pendingAudioRoute, 258 BluetoothDevice device, AudioManager audioManager, 259 BluetoothRouteManager bluetoothRouteManager, boolean isScoAlreadyConnected) { 260 Log.i(this, "onDestRouteAsPendingRoute: active (%b), type (%s), isScoAlreadyConnected(%s)", 261 active, DEVICE_TYPE_STRINGS.get(mAudioRouteType), isScoAlreadyConnected); 262 if (pendingAudioRoute.isActive() && !active) { 263 clearCommunicationDevice(pendingAudioRoute, bluetoothRouteManager, audioManager); 264 } else if (active) { 265 // Handle BT routing case. 266 if (BT_AUDIO_ROUTE_TYPES.contains(mAudioRouteType)) { 267 // Check if the communication device was set for the device, even if 268 // BluetoothHeadset#connectAudio reports that the SCO connection wasn't 269 // successfully established. 270 boolean connectedBtAudio = connectBtAudio(pendingAudioRoute, device, 271 audioManager, bluetoothRouteManager, isScoAlreadyConnected); 272 // Special handling for SCO case. 273 if (!mIsScoManagedByAudio && mAudioRouteType == TYPE_BLUETOOTH_SCO) { 274 // Set whether the dest route is for the watch 275 mIsDestRouteForWatch = bluetoothRouteManager.isWatch(device); 276 if (connectedBtAudio || isScoAlreadyConnected) { 277 pendingAudioRoute.setCommunicationDeviceType(mAudioRouteType); 278 if (!isScoAlreadyConnected) { 279 pendingAudioRoute.addMessage(BT_AUDIO_CONNECTED, mBluetoothAddress); 280 } 281 } else { 282 pendingAudioRoute.onMessageReceived(new Pair<>(PENDING_ROUTE_FAILED, 283 mBluetoothAddress), mBluetoothAddress); 284 } 285 return; 286 } 287 } else if (mAudioRouteType == TYPE_SPEAKER && !this.equals( 288 pendingAudioRoute.getOrigRoute())) { 289 pendingAudioRoute.addMessage(SPEAKER_ON, null); 290 } 291 292 boolean result = false; 293 List<AudioDeviceInfo> devices = audioManager.getAvailableCommunicationDevices(); 294 for (AudioDeviceInfo deviceInfo : devices) { 295 // It's possible for the AudioDeviceInfo to be updated for the BT device so adjust 296 // mInfo accordingly. 297 // Note: we need to check the device type as well since a dual mode (LE and HFP) BT 298 // device can change type during a call if the user toggles LE for the device. 299 boolean isSameDeviceType = 300 !pendingAudioRoute.getFeatureFlags().checkDeviceTypeOnRouteChange() || 301 (pendingAudioRoute.getFeatureFlags().checkDeviceTypeOnRouteChange() 302 && mAudioRouteType 303 == DEVICE_INFO_TYPE_TO_AUDIO_ROUTE_TYPE.get( 304 deviceInfo.getType())); 305 if (BT_AUDIO_ROUTE_TYPES.contains(mAudioRouteType) && mBluetoothAddress 306 .equals(deviceInfo.getAddress()) 307 && isSameDeviceType) { 308 mInfo = deviceInfo; 309 } 310 if (deviceInfo.equals(mInfo)) { 311 result = audioManager.setCommunicationDevice(mInfo); 312 if (result) { 313 pendingAudioRoute.setCommunicationDeviceType(mAudioRouteType); 314 if (mAudioRouteType == TYPE_BLUETOOTH_SCO 315 && !isScoAlreadyConnected 316 && mIsScoManagedByAudio) { 317 pendingAudioRoute.addMessage(BT_AUDIO_CONNECTED, mBluetoothAddress); 318 } 319 } 320 Log.i(this, "onDestRouteAsPendingRoute: route=%s, " 321 + "AudioManager#setCommunicationDevice(%s)=%b", this, 322 audioDeviceTypeToString(mInfo.getType()), result); 323 break; 324 } 325 } 326 327 // It's possible that BluetoothStateReceiver needs to report that the device is active 328 // before being able to successfully set the communication device. Refrain from sending 329 // pending route failed message for BT route until the second attempt fails. 330 if (!result && !BT_AUDIO_ROUTE_TYPES.contains(mAudioRouteType)) { 331 pendingAudioRoute.onMessageReceived(new Pair<>(PENDING_ROUTE_FAILED, null), null); 332 } 333 } 334 } 335 336 /** 337 * Takes care of cleaning up original audio route (i.e. clearCommunicationDevice, 338 * sending SPEAKER_OFF, or disconnecting SCO). 339 * @param wasActive Was the origin route active or not. 340 * @param pendingAudioRoute The pending audio route change we're performing. 341 * @param audioManager Good 'ol audio manager. 342 * @param bluetoothRouteManager The BT route manager. 343 */ onOrigRouteAsPendingRoute(boolean wasActive, PendingAudioRoute pendingAudioRoute, AudioManager audioManager, BluetoothRouteManager bluetoothRouteManager, boolean isScoAlreadyConnected)344 void onOrigRouteAsPendingRoute(boolean wasActive, PendingAudioRoute pendingAudioRoute, 345 AudioManager audioManager, BluetoothRouteManager bluetoothRouteManager, 346 boolean isScoAlreadyConnected) { 347 Log.i(this, "onOrigRouteAsPendingRoute: wasActive (%b), type (%s), pending(%s)," 348 + "isScoAlreadyConnected(%s)", wasActive, DEVICE_TYPE_STRINGS.get(mAudioRouteType), 349 pendingAudioRoute, isScoAlreadyConnected); 350 if (wasActive && !isScoAlreadyConnected) { 351 int result = clearCommunicationDevice(pendingAudioRoute, bluetoothRouteManager, 352 audioManager); 353 if (mAudioRouteType == TYPE_SPEAKER) { 354 pendingAudioRoute.addMessage(SPEAKER_OFF, null); 355 } else if (mAudioRouteType == TYPE_BLUETOOTH_SCO 356 && result == BluetoothStatusCodes.SUCCESS) { 357 // Only send BT_AUDIO_DISCONNECTED for SCO if disconnect was successful. 358 pendingAudioRoute.addMessage(BT_AUDIO_DISCONNECTED, mBluetoothAddress); 359 } 360 } 361 } 362 363 @VisibleForTesting AudioRoute(@udioRouteType int type, String bluetoothAddress, AudioDeviceInfo info)364 public AudioRoute(@AudioRouteType int type, String bluetoothAddress, AudioDeviceInfo info) { 365 mAudioRouteType = type; 366 mBluetoothAddress = bluetoothAddress; 367 mInfo = info; 368 // Indication that SCO is managed by audio (i.e. supports setCommunicationDevice). 369 mIsScoManagedByAudio = android.media.audio.Flags.scoManagedByAudio() 370 && BluetoothProperties.isScoManagedByAudioEnabled().orElse(false); 371 } 372 373 @Override equals(Object obj)374 public boolean equals(Object obj) { 375 if (obj == null) { 376 return false; 377 } 378 if (!(obj instanceof AudioRoute otherRoute)) { 379 return false; 380 } 381 if (mAudioRouteType != otherRoute.getType()) { 382 return false; 383 } 384 return !BT_AUDIO_ROUTE_TYPES.contains(mAudioRouteType) || mBluetoothAddress.equals( 385 otherRoute.getBluetoothAddress()); 386 } 387 388 @Override hashCode()389 public int hashCode() { 390 return Objects.hash(mAudioRouteType, mBluetoothAddress); 391 } 392 393 @Override toString()394 public String toString() { 395 return getClass().getSimpleName() + "[Type=" + DEVICE_TYPE_STRINGS.get(mAudioRouteType) 396 + ", Address=" + ((mBluetoothAddress != null) ? mBluetoothAddress : "invalid") 397 + "]"; 398 } 399 connectBtAudio(PendingAudioRoute pendingAudioRoute, BluetoothDevice device, AudioManager audioManager, BluetoothRouteManager bluetoothRouteManager, boolean isScoAlreadyConnected)400 private boolean connectBtAudio(PendingAudioRoute pendingAudioRoute, BluetoothDevice device, 401 AudioManager audioManager, BluetoothRouteManager bluetoothRouteManager, 402 boolean isScoAlreadyConnected) { 403 // Ensure that if another BT device was set, it is disconnected before connecting 404 // the new one. 405 AudioRoute currentRoute = pendingAudioRoute.getOrigRoute(); 406 if (!isScoAlreadyConnected && currentRoute.getBluetoothAddress() != null && 407 !currentRoute.getBluetoothAddress().equals(device.getAddress())) { 408 clearCommunicationDevice(pendingAudioRoute, bluetoothRouteManager, audioManager); 409 } 410 411 // Connect to the device (explicit handling for HFP devices). 412 boolean success = false; 413 if (device != null) { 414 success = bluetoothRouteManager.getDeviceManager() 415 .connectAudio(device, mAudioRouteType, mIsScoManagedByAudio); 416 } 417 418 Log.i(this, "connectBtAudio: routeToConnectTo = %s, successful = %b", 419 this, success); 420 return success; 421 } 422 423 /** 424 * Clears the communication device; this takes into account the fact that SCO devices require 425 * us to call {@link BluetoothHeadset#disconnectAudio()} rather than 426 * {@link AudioManager#clearCommunicationDevice()}. 427 * As a general rule, if we are transitioning from an active route to another active route, we 428 * do NOT need to call {@link AudioManager#clearCommunicationDevice()}, but if the device is a 429 * legacy SCO device we WILL need to call {@link BluetoothHeadset#disconnectAudio()}. We rely 430 * on the {@link PendingAudioRoute#isActive()} indicator to tell us if the destination route 431 * is going to be active or not. 432 * @param pendingAudioRoute The pending audio route transition we're implementing. 433 * @param bluetoothRouteManager The BT route manager. 434 * @param audioManager The audio manager. 435 * @return -1 if nothing was done, or the result code from the BT SCO disconnect. 436 */ clearCommunicationDevice(PendingAudioRoute pendingAudioRoute, BluetoothRouteManager bluetoothRouteManager, AudioManager audioManager)437 int clearCommunicationDevice(PendingAudioRoute pendingAudioRoute, 438 BluetoothRouteManager bluetoothRouteManager, AudioManager audioManager) { 439 // Try to see if there's a previously set device for communication that should be cleared. 440 // This only serves to help in the SCO case to ensure that we disconnect the headset. 441 if (pendingAudioRoute.getCommunicationDeviceType() == AudioRoute.TYPE_INVALID) { 442 return -1; 443 } 444 445 int result = BluetoothStatusCodes.SUCCESS; 446 boolean shouldDisconnectSco = !mIsScoManagedByAudio 447 && pendingAudioRoute.getCommunicationDeviceType() == TYPE_BLUETOOTH_SCO; 448 if (shouldDisconnectSco) { 449 Log.i(this, "Disconnecting SCO device via BluetoothHeadset."); 450 result = bluetoothRouteManager.getDeviceManager().disconnectSco(); 451 } 452 // Only clear communication device if the destination route will be inactive; route to 453 // route transitions do not require clearing the communication device. 454 boolean onlyClearCommunicationDeviceOnInactive = 455 pendingAudioRoute.getFeatureFlags().onlyClearCommunicationDeviceOnInactive(); 456 if ((!onlyClearCommunicationDeviceOnInactive && !shouldDisconnectSco) 457 || !pendingAudioRoute.isActive()) { 458 Log.i(this, 459 "clearCommunicationDevice: AudioManager#clearCommunicationDevice, type=%s", 460 DEVICE_TYPE_STRINGS.get(pendingAudioRoute.getCommunicationDeviceType())); 461 audioManager.clearCommunicationDevice(); 462 } 463 464 if (result == BluetoothStatusCodes.SUCCESS) { 465 if (pendingAudioRoute.getFeatureFlags().resolveActiveBtRoutingAndBtTimingIssue()) { 466 maybeClearConnectedPendingMessages(pendingAudioRoute); 467 } 468 pendingAudioRoute.setCommunicationDeviceType(AudioRoute.TYPE_INVALID); 469 } 470 return result; 471 } 472 maybeClearConnectedPendingMessages(PendingAudioRoute pendingAudioRoute)473 private void maybeClearConnectedPendingMessages(PendingAudioRoute pendingAudioRoute) { 474 // If we're still waiting on BT_AUDIO_CONNECTED/SPEAKER_ON but have routed out of it 475 // since and disconnected the device, then remove that message so we aren't waiting for 476 // it in the message queue. 477 if (mAudioRouteType == TYPE_BLUETOOTH_SCO) { 478 Log.i(this, "clearCommunicationDevice: Clearing pending " 479 + "BT_AUDIO_CONNECTED messages."); 480 pendingAudioRoute.clearPendingMessage( 481 new Pair<>(BT_AUDIO_CONNECTED, mBluetoothAddress)); 482 } else if (mAudioRouteType == TYPE_SPEAKER) { 483 Log.i(this, "clearCommunicationDevice: Clearing pending SPEAKER_ON messages."); 484 pendingAudioRoute.clearPendingMessage(new Pair<>(SPEAKER_ON, null)); 485 } 486 } 487 488 /** 489 * Get a human readable (for logs) version of an an audio device type. 490 * @param type the device type 491 * @return the human readable string 492 */ audioDeviceTypeToString(int type)493 private static String audioDeviceTypeToString(int type) { 494 return switch (type) { 495 case AudioDeviceInfo.TYPE_BUILTIN_EARPIECE -> "earpiece"; 496 case AudioDeviceInfo.TYPE_BUILTIN_SPEAKER -> "speaker"; 497 case AudioDeviceInfo.TYPE_BUS -> "bus(auto speaker)"; 498 case AudioDeviceInfo.TYPE_BLUETOOTH_SCO -> "bt sco"; 499 case AudioDeviceInfo.TYPE_BLE_HEADSET -> "bt le"; 500 case AudioDeviceInfo.TYPE_HEARING_AID -> "bt hearing aid"; 501 case AudioDeviceInfo.TYPE_USB_HEADSET -> "usb headset"; 502 case AudioDeviceInfo.TYPE_WIRED_HEADSET -> "wired headset"; 503 default -> Integer.toString(type); 504 }; 505 } 506 } 507