1 /* 2 * Copyright (C) 2016 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.bluetooth; 18 19 import static com.android.server.telecom.AudioRoute.TYPE_BLUETOOTH_HA; 20 import static com.android.server.telecom.AudioRoute.TYPE_BLUETOOTH_SCO; 21 import static com.android.server.telecom.CallAudioRouteAdapter.BT_DEVICE_REMOVED; 22 import static com.android.server.telecom.CallAudioRouteAdapter.SWITCH_BASELINE_ROUTE; 23 import static com.android.server.telecom.CallAudioRouteController.INCLUDE_BLUETOOTH_IN_BASELINE; 24 25 import android.bluetooth.BluetoothAdapter; 26 import android.bluetooth.BluetoothDevice; 27 import android.bluetooth.BluetoothHeadset; 28 import android.bluetooth.BluetoothHearingAid; 29 import android.bluetooth.BluetoothLeAudio; 30 import android.bluetooth.BluetoothLeAudioCodecStatus; 31 import android.bluetooth.BluetoothProfile; 32 import android.bluetooth.BluetoothStatusCodes; 33 import android.content.Context; 34 import android.media.AudioDeviceInfo; 35 import android.media.AudioManager; 36 import android.os.Bundle; 37 import android.telecom.Log; 38 import android.util.ArraySet; 39 import android.util.LocalLog; 40 import android.util.Pair; 41 42 import com.android.internal.annotations.VisibleForTesting; 43 import com.android.internal.util.IndentingPrintWriter; 44 import com.android.server.telecom.AudioRoute; 45 import com.android.server.telecom.CallAudioCommunicationDeviceTracker; 46 import com.android.server.telecom.CallAudioRouteAdapter; 47 import com.android.server.telecom.CallAudioRouteController; 48 import com.android.server.telecom.flags.FeatureFlags; 49 50 import java.util.ArrayList; 51 import java.util.Collection; 52 import java.util.Collections; 53 import java.util.HashMap; 54 import java.util.LinkedHashMap; 55 import java.util.LinkedHashSet; 56 import java.util.LinkedList; 57 import java.util.List; 58 import java.util.Map; 59 import java.util.Objects; 60 import java.util.Set; 61 import java.util.concurrent.CompletableFuture; 62 import java.util.concurrent.ExecutionException; 63 import java.util.concurrent.Executor; 64 import java.util.concurrent.TimeUnit; 65 import java.util.concurrent.TimeoutException; 66 67 public class BluetoothDeviceManager { 68 69 public static final int DEVICE_TYPE_HEADSET = 0; 70 public static final int DEVICE_TYPE_HEARING_AID = 1; 71 public static final int DEVICE_TYPE_LE_AUDIO = 2; 72 73 private static final Map<Integer, Integer> PROFILE_TO_AUDIO_ROUTE_MAP = new HashMap<>(); 74 static { PROFILE_TO_AUDIO_ROUTE_MAP.put(BluetoothProfile.HEADSET, AudioRoute.TYPE_BLUETOOTH_SCO)75 PROFILE_TO_AUDIO_ROUTE_MAP.put(BluetoothProfile.HEADSET, 76 AudioRoute.TYPE_BLUETOOTH_SCO); PROFILE_TO_AUDIO_ROUTE_MAP.put(BluetoothProfile.LE_AUDIO, AudioRoute.TYPE_BLUETOOTH_LE)77 PROFILE_TO_AUDIO_ROUTE_MAP.put(BluetoothProfile.LE_AUDIO, 78 AudioRoute.TYPE_BLUETOOTH_LE); PROFILE_TO_AUDIO_ROUTE_MAP.put(BluetoothProfile.HEARING_AID, TYPE_BLUETOOTH_HA)79 PROFILE_TO_AUDIO_ROUTE_MAP.put(BluetoothProfile.HEARING_AID, 80 TYPE_BLUETOOTH_HA); 81 } 82 83 private BluetoothLeAudio.Callback mLeAudioCallbacks = 84 new BluetoothLeAudio.Callback() { 85 @Override 86 public void onCodecConfigChanged(int groupId, BluetoothLeAudioCodecStatus status) {} 87 @Override 88 public void onGroupStatusChanged(int groupId, int groupStatus) {} 89 @Override 90 public void onGroupNodeAdded(BluetoothDevice device, int groupId) { 91 Log.i(this, (device == null ? "device is null" : device.getAddress()) 92 + " group added " + groupId); 93 if (device == null || groupId == BluetoothLeAudio.GROUP_ID_INVALID) { 94 Log.w(this, "invalid parameter"); 95 return; 96 } 97 98 synchronized (mLock) { 99 mGroupsByDevice.put(device, groupId); 100 } 101 } 102 @Override 103 public void onGroupNodeRemoved(BluetoothDevice device, int groupId) { 104 Log.i(this, (device == null ? "device is null" : device.getAddress()) 105 + " group removed " + groupId); 106 if (device == null || groupId == BluetoothLeAudio.GROUP_ID_INVALID) { 107 Log.w(this, "invalid parameter"); 108 return; 109 } 110 111 synchronized (mLock) { 112 mGroupsByDevice.remove(device); 113 } 114 } 115 }; 116 117 private final BluetoothProfile.ServiceListener mBluetoothProfileServiceListener = 118 new BluetoothProfile.ServiceListener() { 119 @Override 120 public void onServiceConnected(int profile, BluetoothProfile proxy) { 121 Log.startSession("BPSL.oSC"); 122 try { 123 synchronized (mLock) { 124 String logString; 125 if (profile == BluetoothProfile.HEADSET) { 126 if (mFeatureFlags.useRefactoredAudioRouteSwitching()) { 127 mBluetoothHeadsetFuture.complete((BluetoothHeadset) proxy); 128 } 129 mBluetoothHeadset = (BluetoothHeadset) proxy; 130 logString = "Got BluetoothHeadset: " + mBluetoothHeadset; 131 } else if (profile == BluetoothProfile.HEARING_AID) { 132 mBluetoothHearingAid = (BluetoothHearingAid) proxy; 133 logString = "Got BluetoothHearingAid: " 134 + mBluetoothHearingAid; 135 } else if (profile == BluetoothProfile.LE_AUDIO) { 136 mBluetoothLeAudioService = (BluetoothLeAudio) proxy; 137 logString = ("Got BluetoothLeAudio: " + mBluetoothLeAudioService ) 138 + (", mLeAudioCallbackRegistered: " 139 + mLeAudioCallbackRegistered); 140 if (!mLeAudioCallbackRegistered) { 141 if (mFeatureFlags.postponeRegisterToLeaudio()) { 142 mExecutor.execute(this::registerToLeAudio); 143 } else { 144 registerToLeAudio(); 145 } 146 } 147 } else { 148 logString = "Connected to non-requested bluetooth service." + 149 " Not changing bluetooth headset."; 150 } 151 Log.i(BluetoothDeviceManager.this, logString); 152 mLocalLog.log(logString); 153 } 154 } finally { 155 Log.endSession(); 156 } 157 } 158 159 private void registerToLeAudio() { 160 synchronized (mLock) { 161 String logString = "Register to leAudio"; 162 163 if (mLeAudioCallbackRegistered) { 164 logString += ", but already registered"; 165 Log.i(BluetoothDeviceManager.this, logString); 166 mLocalLog.log(logString); 167 return; 168 } 169 try { 170 mLeAudioCallbackRegistered = true; 171 mBluetoothLeAudioService.registerCallback( 172 mExecutor, mLeAudioCallbacks); 173 } catch (IllegalStateException e) { 174 mLeAudioCallbackRegistered = false; 175 logString += ", but failed: " + e; 176 } 177 Log.i(BluetoothDeviceManager.this, logString); 178 mLocalLog.log(logString); 179 } 180 } 181 182 @Override 183 public void onServiceDisconnected(int profile) { 184 Log.startSession("BPSL.oSD"); 185 try { 186 synchronized (mLock) { 187 LinkedHashMap<String, BluetoothDevice> lostServiceDevices; 188 String logString; 189 if (profile == BluetoothProfile.HEADSET) { 190 if (mFeatureFlags.useRefactoredAudioRouteSwitching()) { 191 mBluetoothHeadsetFuture.complete(null); 192 } 193 mBluetoothHeadset = null; 194 lostServiceDevices = mHfpDevicesByAddress; 195 mBluetoothRouteManager.onActiveDeviceChanged(null, 196 DEVICE_TYPE_HEADSET); 197 logString = "Lost BluetoothHeadset service. " + 198 "Removing all tracked devices"; 199 } else if (profile == BluetoothProfile.HEARING_AID) { 200 mBluetoothHearingAid = null; 201 logString = "Lost BluetoothHearingAid service. " + 202 "Removing all tracked devices."; 203 lostServiceDevices = mHearingAidDevicesByAddress; 204 mBluetoothRouteManager.onActiveDeviceChanged(null, 205 DEVICE_TYPE_HEARING_AID); 206 } else if (profile == BluetoothProfile.LE_AUDIO) { 207 mBluetoothLeAudioService = null; 208 logString = "Lost BluetoothLeAudio service. " + 209 "Removing all tracked devices."; 210 lostServiceDevices = mLeAudioDevicesByAddress; 211 mBluetoothRouteManager.onActiveDeviceChanged(null, 212 DEVICE_TYPE_LE_AUDIO); 213 } else { 214 return; 215 } 216 Log.i(BluetoothDeviceManager.this, logString); 217 mLocalLog.log(logString); 218 219 if (mFeatureFlags.useRefactoredAudioRouteSwitching()) { 220 handleAudioRefactoringServiceDisconnected(profile); 221 } else { 222 List<BluetoothDevice> devicesToRemove = new LinkedList<>( 223 lostServiceDevices.values()); 224 lostServiceDevices.clear(); 225 for (BluetoothDevice device : devicesToRemove) { 226 mBluetoothRouteManager.onDeviceLost(device.getAddress()); 227 } 228 } 229 } 230 } finally { 231 Log.endSession(); 232 } 233 } 234 }; 235 handleAudioRefactoringServiceDisconnected(int profile)236 private void handleAudioRefactoringServiceDisconnected(int profile) { 237 CallAudioRouteController controller = (CallAudioRouteController) 238 mCallAudioRouteAdapter; 239 Map<AudioRoute, BluetoothDevice> btRoutes = controller 240 .getBluetoothRoutes(); 241 List<Pair<AudioRoute, BluetoothDevice>> btRoutesToRemove = 242 new ArrayList<>(); 243 for (AudioRoute route: btRoutes.keySet()) { 244 if (route.getType() != PROFILE_TO_AUDIO_ROUTE_MAP.get(profile)) { 245 continue; 246 } 247 BluetoothDevice device = btRoutes.get(route); 248 // Prevent concurrent modification exception by just iterating through keys instead of 249 // simultaneously removing them. 250 btRoutesToRemove.add(new Pair<>(route, device)); 251 } 252 253 for (Pair<AudioRoute, BluetoothDevice> routeToRemove: 254 btRoutesToRemove) { 255 AudioRoute route = routeToRemove.first; 256 BluetoothDevice device = routeToRemove.second; 257 mCallAudioRouteAdapter.sendMessageWithSessionInfo( 258 BT_DEVICE_REMOVED, route.getType(), device); 259 } 260 mCallAudioRouteAdapter.sendMessageWithSessionInfo( 261 SWITCH_BASELINE_ROUTE, INCLUDE_BLUETOOTH_IN_BASELINE, (String) null); 262 } 263 264 private final LinkedHashMap<String, BluetoothDevice> mHfpDevicesByAddress = 265 new LinkedHashMap<>(); 266 private final LinkedHashMap<String, BluetoothDevice> mHearingAidDevicesByAddress = 267 new LinkedHashMap<>(); 268 private final LinkedHashMap<BluetoothDevice, Long> mHearingAidDeviceSyncIds = 269 new LinkedHashMap<>(); 270 private final LinkedHashMap<String, BluetoothDevice> mLeAudioDevicesByAddress = 271 new LinkedHashMap<>(); 272 private final LinkedHashMap<BluetoothDevice, Integer> mGroupsByDevice = 273 new LinkedHashMap<>(); 274 private final ArrayList<LinkedHashMap<String, BluetoothDevice>> 275 mDevicesByAddressMaps = new ArrayList<LinkedHashMap<String, BluetoothDevice>>(); { 276 mDevicesByAddressMaps.add(mHfpDevicesByAddress); 277 mDevicesByAddressMaps.add(mHearingAidDevicesByAddress); 278 mDevicesByAddressMaps.add(mLeAudioDevicesByAddress); 279 } 280 private int mGroupIdActive = BluetoothLeAudio.GROUP_ID_INVALID; 281 private int mGroupIdPending = BluetoothLeAudio.GROUP_ID_INVALID; 282 private final LocalLog mLocalLog = new LocalLog(20); 283 284 // This lock only protects internal state -- it doesn't lock on anything going into Telecom. 285 private final Object mLock = new Object(); 286 287 private BluetoothRouteManager mBluetoothRouteManager; 288 private BluetoothHeadset mBluetoothHeadset; 289 private CompletableFuture<BluetoothHeadset> mBluetoothHeadsetFuture; 290 private BluetoothHearingAid mBluetoothHearingAid; 291 private boolean mLeAudioCallbackRegistered = false; 292 private BluetoothLeAudio mBluetoothLeAudioService; 293 private boolean mLeAudioSetAsCommunicationDevice = false; 294 private String mLeAudioDevice; 295 private String mHearingAidDevice; 296 private boolean mHearingAidSetAsCommunicationDevice = false; 297 private BluetoothDevice mBluetoothHearingAidActiveDeviceCache; 298 private BluetoothAdapter mBluetoothAdapter; 299 private AudioManager mAudioManager; 300 private Executor mExecutor; 301 private CallAudioCommunicationDeviceTracker mCommunicationDeviceTracker; 302 private CallAudioRouteAdapter mCallAudioRouteAdapter; 303 private FeatureFlags mFeatureFlags; 304 BluetoothDeviceManager(Context context, BluetoothAdapter bluetoothAdapter, CallAudioCommunicationDeviceTracker communicationDeviceTracker, FeatureFlags featureFlags)305 public BluetoothDeviceManager(Context context, BluetoothAdapter bluetoothAdapter, 306 CallAudioCommunicationDeviceTracker communicationDeviceTracker, 307 FeatureFlags featureFlags) { 308 mFeatureFlags = featureFlags; 309 if (bluetoothAdapter != null) { 310 mBluetoothAdapter = bluetoothAdapter; 311 if (mFeatureFlags.useRefactoredAudioRouteSwitching()) { 312 mBluetoothHeadsetFuture = new CompletableFuture<>(); 313 } 314 bluetoothAdapter.getProfileProxy(context, mBluetoothProfileServiceListener, 315 BluetoothProfile.HEADSET); 316 bluetoothAdapter.getProfileProxy(context, mBluetoothProfileServiceListener, 317 BluetoothProfile.HEARING_AID); 318 bluetoothAdapter.getProfileProxy(context, mBluetoothProfileServiceListener, 319 BluetoothProfile.LE_AUDIO); 320 mAudioManager = context.getSystemService(AudioManager.class); 321 mExecutor = context.getMainExecutor(); 322 mCommunicationDeviceTracker = communicationDeviceTracker; 323 } 324 } 325 setBluetoothRouteManager(BluetoothRouteManager brm)326 public void setBluetoothRouteManager(BluetoothRouteManager brm) { 327 mBluetoothRouteManager = brm; 328 } 329 getLeAudioConnectedDevices()330 private List<BluetoothDevice> getLeAudioConnectedDevices() { 331 synchronized (mLock) { 332 // Let's get devices which are a group leaders 333 ArrayList<BluetoothDevice> devices = new ArrayList<>(); 334 335 if (mGroupsByDevice.isEmpty() || mBluetoothLeAudioService == null) { 336 return devices; 337 } 338 339 for (LinkedHashMap.Entry<BluetoothDevice, Integer> entry : mGroupsByDevice.entrySet()) { 340 if (Objects.equals(entry.getKey(), 341 mBluetoothLeAudioService.getConnectedGroupLeadDevice(entry.getValue()))) { 342 devices.add(entry.getKey()); 343 } 344 } 345 devices.removeIf(device -> !mLeAudioDevicesByAddress.containsValue(device)); 346 return devices; 347 } 348 } 349 getNumConnectedDevices()350 public int getNumConnectedDevices() { 351 return getConnectedDevices().size(); 352 } 353 getConnectedDevices()354 public Collection<BluetoothDevice> getConnectedDevices() { 355 synchronized (mLock) { 356 ArraySet<BluetoothDevice> result = new ArraySet<>(); 357 358 // Set storing the group ids of all dual mode audio devices to de-dupe them 359 Set<Integer> dualModeGroupIds = new ArraySet<>(); 360 for (BluetoothDevice hfpDevice: mHfpDevicesByAddress.values()) { 361 result.add(hfpDevice); 362 if (mBluetoothLeAudioService == null) { 363 continue; 364 } 365 int groupId = mBluetoothLeAudioService.getGroupId(hfpDevice); 366 if (groupId != BluetoothLeAudio.GROUP_ID_INVALID) { 367 dualModeGroupIds.add(groupId); 368 } 369 } 370 371 result.addAll(mHearingAidDevicesByAddress.values()); 372 if (mBluetoothLeAudioService == null) { 373 return Collections.unmodifiableCollection(result); 374 } 375 for (BluetoothDevice leAudioDevice: getLeAudioConnectedDevices()) { 376 // Exclude dual mode audio devices included from the HFP devices list 377 int groupId = mBluetoothLeAudioService.getGroupId(leAudioDevice); 378 if (groupId != BluetoothLeAudio.GROUP_ID_INVALID 379 && !dualModeGroupIds.contains(groupId)) { 380 result.add(leAudioDevice); 381 } 382 } 383 return Collections.unmodifiableCollection(result); 384 } 385 } 386 387 // Same as getConnectedDevices except it filters out the hearing aid devices that are linked 388 // together by their hiSyncId. getUniqueConnectedDevices()389 public Collection<BluetoothDevice> getUniqueConnectedDevices() { 390 ArraySet<BluetoothDevice> result; 391 synchronized (mLock) { 392 result = new ArraySet<>(mHfpDevicesByAddress.values()); 393 } 394 Set<Long> seenHiSyncIds = new LinkedHashSet<>(); 395 // Add the left-most active device to the seen list so that we match up with the list 396 // generated in BluetoothRouteManager. 397 if (mBluetoothAdapter != null) { 398 for (BluetoothDevice device : mBluetoothAdapter.getActiveDevices( 399 BluetoothProfile.HEARING_AID)) { 400 if (device != null) { 401 result.add(device); 402 seenHiSyncIds.add(mHearingAidDeviceSyncIds.getOrDefault(device, -1L)); 403 break; 404 } 405 } 406 } 407 synchronized (mLock) { 408 for (BluetoothDevice d : mHearingAidDevicesByAddress.values()) { 409 long hiSyncId = mHearingAidDeviceSyncIds.getOrDefault(d, -1L); 410 if (seenHiSyncIds.contains(hiSyncId)) { 411 continue; 412 } 413 result.add(d); 414 seenHiSyncIds.add(hiSyncId); 415 } 416 } 417 418 if (mBluetoothLeAudioService != null) { 419 result.addAll(getLeAudioConnectedDevices()); 420 } 421 422 return Collections.unmodifiableCollection(result); 423 } 424 getBluetoothHeadset()425 public BluetoothHeadset getBluetoothHeadset() { 426 if (mFeatureFlags.useRefactoredAudioRouteSwitching()) { 427 try { 428 mBluetoothHeadset = mBluetoothHeadsetFuture.get(500L, 429 TimeUnit.MILLISECONDS); 430 return mBluetoothHeadset; 431 } catch (TimeoutException | InterruptedException | ExecutionException e) { 432 // ignore 433 Log.w(this, "Acquire BluetoothHeadset service failed due to: " + e); 434 return null; 435 } 436 } else { 437 return mBluetoothHeadset; 438 } 439 } 440 getBluetoothAdapter()441 public BluetoothAdapter getBluetoothAdapter() { 442 return mBluetoothAdapter; 443 } 444 getBluetoothHearingAid()445 public BluetoothHearingAid getBluetoothHearingAid() { 446 return mBluetoothHearingAid; 447 } 448 getLeAudioService()449 public BluetoothLeAudio getLeAudioService() { 450 return mBluetoothLeAudioService; 451 } 452 setHeadsetServiceForTesting(BluetoothHeadset bluetoothHeadset)453 public void setHeadsetServiceForTesting(BluetoothHeadset bluetoothHeadset) { 454 mBluetoothHeadset = bluetoothHeadset; 455 } 456 setHearingAidServiceForTesting(BluetoothHearingAid bluetoothHearingAid)457 public void setHearingAidServiceForTesting(BluetoothHearingAid bluetoothHearingAid) { 458 mBluetoothHearingAid = bluetoothHearingAid; 459 } 460 setLeAudioServiceForTesting(BluetoothLeAudio bluetoothLeAudio)461 public void setLeAudioServiceForTesting(BluetoothLeAudio bluetoothLeAudio) { 462 mBluetoothLeAudioService = bluetoothLeAudio; 463 mBluetoothLeAudioService.registerCallback(mExecutor, mLeAudioCallbacks); 464 } 465 getDeviceTypeString(int deviceType)466 public static String getDeviceTypeString(int deviceType) { 467 switch (deviceType) { 468 case DEVICE_TYPE_LE_AUDIO: 469 return "LeAudio"; 470 case DEVICE_TYPE_HEARING_AID: 471 return "HearingAid"; 472 case DEVICE_TYPE_HEADSET: 473 return "HFP"; 474 default: 475 return "unknown type"; 476 } 477 } 478 479 @VisibleForTesting onDeviceConnected(BluetoothDevice device, int deviceType)480 public void onDeviceConnected(BluetoothDevice device, int deviceType) { 481 synchronized (mLock) { 482 clearDeviceFromDeviceMaps(device.getAddress()); 483 LinkedHashMap<String, BluetoothDevice> targetDeviceMap; 484 if (deviceType == DEVICE_TYPE_LE_AUDIO) { 485 if (mBluetoothLeAudioService == null) { 486 Log.w(this, "LE audio service null when receiving device added broadcast"); 487 return; 488 } 489 /* Check if group is known. */ 490 if (!mGroupsByDevice.containsKey(device)) { 491 int groupId = mBluetoothLeAudioService.getGroupId(device); 492 /* If it is not yet assigned, then it will be provided in the callback */ 493 if (groupId != BluetoothLeAudio.GROUP_ID_INVALID) { 494 mGroupsByDevice.put(device, groupId); 495 } 496 } 497 targetDeviceMap = mLeAudioDevicesByAddress; 498 } else if (deviceType == DEVICE_TYPE_HEARING_AID) { 499 if (mBluetoothHearingAid == null) { 500 Log.w(this, "Hearing aid service null when receiving device added broadcast"); 501 return; 502 } 503 long hiSyncId = mBluetoothHearingAid.getHiSyncId(device); 504 mHearingAidDeviceSyncIds.put(device, hiSyncId); 505 targetDeviceMap = mHearingAidDevicesByAddress; 506 } else if (deviceType == DEVICE_TYPE_HEADSET) { 507 if (getBluetoothHeadset() == null) { 508 Log.w(this, "Headset service null when receiving device added broadcast"); 509 return; 510 } 511 targetDeviceMap = mHfpDevicesByAddress; 512 } else { 513 Log.w(this, "Device: " + device.getAddress() + " with invalid type: " 514 + getDeviceTypeString(deviceType)); 515 return; 516 } 517 if (!targetDeviceMap.containsKey(device.getAddress())) { 518 Log.i(this, "Adding device with address: " + device + " and devicetype=" 519 + getDeviceTypeString(deviceType)); 520 targetDeviceMap.put(device.getAddress(), device); 521 mBluetoothRouteManager.onDeviceAdded(device.getAddress()); 522 } 523 } 524 } 525 clearDeviceFromDeviceMaps(String deviceAddress)526 void clearDeviceFromDeviceMaps(String deviceAddress) { 527 for (LinkedHashMap<String, BluetoothDevice> deviceMap : mDevicesByAddressMaps) { 528 deviceMap.remove(deviceAddress); 529 } 530 } 531 onDeviceDisconnected(BluetoothDevice device, int deviceType)532 void onDeviceDisconnected(BluetoothDevice device, int deviceType) { 533 mLocalLog.log("Device disconnected -- address: " + device.getAddress() + " deviceType: " 534 + deviceType); 535 synchronized (mLock) { 536 LinkedHashMap<String, BluetoothDevice> targetDeviceMap; 537 if (deviceType == DEVICE_TYPE_LE_AUDIO) { 538 targetDeviceMap = mLeAudioDevicesByAddress; 539 } else if (deviceType == DEVICE_TYPE_HEARING_AID) { 540 mHearingAidDeviceSyncIds.remove(device); 541 targetDeviceMap = mHearingAidDevicesByAddress; 542 } else if (deviceType == DEVICE_TYPE_HEADSET) { 543 targetDeviceMap = mHfpDevicesByAddress; 544 } else { 545 Log.w(this, "Device: " + device.getAddress() + " with invalid type: " 546 + getDeviceTypeString(deviceType)); 547 return; 548 } 549 if (targetDeviceMap.containsKey(device.getAddress())) { 550 Log.i(this, "Removing device with address: " + device + " and devicetype=" 551 + getDeviceTypeString(deviceType)); 552 targetDeviceMap.remove(device.getAddress()); 553 mBluetoothRouteManager.onDeviceLost(device.getAddress()); 554 } 555 } 556 } 557 disconnectAudio()558 public void disconnectAudio() { 559 if (mFeatureFlags.callAudioCommunicationDeviceRefactor()) { 560 mCommunicationDeviceTracker.clearBtCommunicationDevice(); 561 disconnectSco(); 562 } else { 563 disconnectSco(); 564 clearLeAudioCommunicationDevice(); 565 clearHearingAidCommunicationDevice(); 566 } 567 } 568 disconnectSco()569 public int disconnectSco() { 570 int result = BluetoothStatusCodes.ERROR_UNKNOWN; 571 if (getBluetoothHeadset() == null) { 572 Log.w(this, "Trying to disconnect audio but no headset service exists."); 573 } else { 574 result = mBluetoothHeadset.disconnectAudio(); 575 } 576 return result; 577 } 578 isLeAudioCommunicationDevice()579 public boolean isLeAudioCommunicationDevice() { 580 return mLeAudioSetAsCommunicationDevice; 581 } 582 isHearingAidSetAsCommunicationDevice()583 public boolean isHearingAidSetAsCommunicationDevice() { 584 return mHearingAidSetAsCommunicationDevice; 585 } 586 clearLeAudioCommunicationDevice()587 public void clearLeAudioCommunicationDevice() { 588 Log.i(this, "clearLeAudioCommunicationDevice: mLeAudioSetAsCommunicationDevice = " + 589 mLeAudioSetAsCommunicationDevice + " device = " + mLeAudioDevice); 590 if (!mLeAudioSetAsCommunicationDevice) { 591 return; 592 } 593 mLeAudioSetAsCommunicationDevice = false; 594 if (mLeAudioDevice != null) { 595 mBluetoothRouteManager.onAudioLost(mLeAudioDevice); 596 mLeAudioDevice = null; 597 } 598 599 if (mAudioManager == null) { 600 Log.i(this, "clearLeAudioCommunicationDevice: mAudioManager is null"); 601 return; 602 } 603 604 AudioDeviceInfo audioDeviceInfo = mAudioManager.getCommunicationDevice(); 605 if (audioDeviceInfo != null && audioDeviceInfo.getType() 606 == AudioDeviceInfo.TYPE_BLE_HEADSET) { 607 mBluetoothRouteManager.onAudioLost(audioDeviceInfo.getAddress()); 608 mAudioManager.clearCommunicationDevice(); 609 } 610 } 611 clearHearingAidCommunicationDevice()612 public void clearHearingAidCommunicationDevice() { 613 Log.i(this, "clearHearingAidCommunicationDevice: mHearingAidSetAsCommunicationDevice = " 614 + mHearingAidSetAsCommunicationDevice); 615 if (!mHearingAidSetAsCommunicationDevice) { 616 return; 617 } 618 mHearingAidSetAsCommunicationDevice = false; 619 if (mHearingAidDevice != null) { 620 mBluetoothRouteManager.onAudioLost(mHearingAidDevice); 621 mHearingAidDevice = null; 622 } 623 624 if (mAudioManager == null) { 625 Log.i(this, "clearHearingAidCommunicationDevice: mAudioManager is null"); 626 return; 627 } 628 629 AudioDeviceInfo audioDeviceInfo = mAudioManager.getCommunicationDevice(); 630 if (audioDeviceInfo != null && audioDeviceInfo.getType() 631 == AudioDeviceInfo.TYPE_HEARING_AID) { 632 mAudioManager.clearCommunicationDevice(); 633 } 634 } 635 setLeAudioCommunicationDevice()636 public boolean setLeAudioCommunicationDevice() { 637 Log.i(this, "setLeAudioCommunicationDevice"); 638 639 if (mLeAudioSetAsCommunicationDevice) { 640 Log.i(this, "setLeAudioCommunicationDevice already set"); 641 return true; 642 } 643 644 if (mAudioManager == null) { 645 Log.w(this, " mAudioManager is null"); 646 return false; 647 } 648 649 AudioDeviceInfo bleHeadset = null; 650 List<AudioDeviceInfo> devices = mAudioManager.getAvailableCommunicationDevices(); 651 if (devices.size() == 0) { 652 Log.w(this, " No communication devices available."); 653 return false; 654 } 655 656 for (AudioDeviceInfo device : devices) { 657 Log.i(this, " Available device type: " + device.getType()); 658 if (device.getType() == AudioDeviceInfo.TYPE_BLE_HEADSET) { 659 bleHeadset = device; 660 break; 661 } 662 } 663 664 if (bleHeadset == null) { 665 Log.w(this, " No bleHeadset device available"); 666 return false; 667 } 668 669 // clear hearing aid communication device if set 670 clearHearingAidCommunicationDevice(); 671 672 // Turn BLE_OUT_HEADSET ON. 673 boolean result = mAudioManager.setCommunicationDevice(bleHeadset); 674 if (!result) { 675 Log.w(this, " Could not set bleHeadset device"); 676 } else { 677 Log.i(this, " bleHeadset device set"); 678 mBluetoothRouteManager.onAudioOn(bleHeadset.getAddress()); 679 mLeAudioSetAsCommunicationDevice = true; 680 mLeAudioDevice = bleHeadset.getAddress(); 681 } 682 return result; 683 } 684 setHearingAidCommunicationDevice()685 public boolean setHearingAidCommunicationDevice() { 686 Log.i(this, "setHearingAidCommunicationDevice"); 687 688 if (mHearingAidSetAsCommunicationDevice) { 689 Log.i(this, "mHearingAidSetAsCommunicationDevice already set"); 690 return true; 691 } 692 693 if (mAudioManager == null) { 694 Log.w(this, " mAudioManager is null"); 695 return false; 696 } 697 698 AudioDeviceInfo hearingAid = null; 699 List<AudioDeviceInfo> devices = mAudioManager.getAvailableCommunicationDevices(); 700 if (devices.size() == 0) { 701 Log.w(this, " No communication devices available."); 702 return false; 703 } 704 705 for (AudioDeviceInfo device : devices) { 706 Log.i(this, " Available device type: " + device.getType()); 707 if (device.getType() == AudioDeviceInfo.TYPE_HEARING_AID) { 708 hearingAid = device; 709 break; 710 } 711 } 712 713 if (hearingAid == null) { 714 Log.w(this, " No hearingAid device available"); 715 return false; 716 } 717 718 // clear LE audio communication device if set 719 clearLeAudioCommunicationDevice(); 720 721 // Turn hearing aid ON. 722 boolean result = mAudioManager.setCommunicationDevice(hearingAid); 723 if (!result) { 724 Log.w(this, " Could not set hearingAid device"); 725 } else { 726 Log.i(this, " hearingAid device set"); 727 mHearingAidDevice = hearingAid.getAddress(); 728 mHearingAidSetAsCommunicationDevice = true; 729 } 730 return result; 731 } 732 setCommunicationDeviceForAddress(String address)733 public boolean setCommunicationDeviceForAddress(String address) { 734 AudioDeviceInfo deviceInfo = null; 735 List<AudioDeviceInfo> devices = mAudioManager.getAvailableCommunicationDevices(); 736 if (devices.size() == 0) { 737 Log.w(this, " No communication devices available."); 738 return false; 739 } 740 741 for (AudioDeviceInfo device : devices) { 742 Log.i(this, " Available device type: " + device.getType()); 743 if (device.getAddress().equals(address)) { 744 deviceInfo = device; 745 break; 746 } 747 } 748 749 if (!mAudioManager.getCommunicationDevice().equals(deviceInfo)) { 750 return mAudioManager.setCommunicationDevice(deviceInfo); 751 } 752 return true; 753 } 754 755 // Connect audio to the bluetooth device at address, checking to see whether it's 756 // le audio, hearing aid or a HFP device, and using the proper BT API. connectAudio(String address, boolean switchingBtDevices)757 public boolean connectAudio(String address, boolean switchingBtDevices) { 758 int callProfile = BluetoothProfile.LE_AUDIO; 759 Log.i(this, "Telecomm connecting audio to device: " + address); 760 BluetoothDevice device = null; 761 if (mLeAudioDevicesByAddress.containsKey(address)) { 762 Log.i(this, "Telecomm found LE Audio device for address: " + address); 763 if (mBluetoothLeAudioService == null) { 764 Log.w(this, "Attempting to turn on audio when the le audio service is null"); 765 return false; 766 } 767 device = mLeAudioDevicesByAddress.get(address); 768 callProfile = BluetoothProfile.LE_AUDIO; 769 } else if (mHearingAidDevicesByAddress.containsKey(address)) { 770 Log.i(this, "Telecomm found hearing aid device for address: " + address); 771 if (mBluetoothHearingAid == null) { 772 Log.w(this, "Attempting to turn on audio when the hearing aid service is null"); 773 return false; 774 } 775 device = mHearingAidDevicesByAddress.get(address); 776 callProfile = BluetoothProfile.HEARING_AID; 777 } else if (mHfpDevicesByAddress.containsKey(address)) { 778 Log.i(this, "Telecomm found HFP device for address: " + address); 779 if (getBluetoothHeadset() == null) { 780 Log.w(this, "Attempting to turn on audio when the headset service is null"); 781 return false; 782 } 783 device = mHfpDevicesByAddress.get(address); 784 callProfile = BluetoothProfile.HEADSET; 785 } 786 787 if (device == null) { 788 Log.w(this, "No active profiles for Bluetooth address=" + address); 789 return false; 790 } 791 792 Bundle preferredAudioProfiles = mBluetoothAdapter.getPreferredAudioProfiles(device); 793 if (preferredAudioProfiles != null && !preferredAudioProfiles.isEmpty() 794 && preferredAudioProfiles.getInt(BluetoothAdapter.AUDIO_MODE_DUPLEX) != 0) { 795 Log.i(this, "Preferred duplex profile for device=" + address + " is " 796 + preferredAudioProfiles.getInt(BluetoothAdapter.AUDIO_MODE_DUPLEX)); 797 callProfile = preferredAudioProfiles.getInt(BluetoothAdapter.AUDIO_MODE_DUPLEX); 798 } 799 800 if (callProfile == BluetoothProfile.LE_AUDIO) { 801 if (mBluetoothAdapter.setActiveDevice( 802 device, BluetoothAdapter.ACTIVE_DEVICE_ALL)) { 803 /* ACTION_ACTIVE_DEVICE_CHANGED intent will trigger setting communication device. 804 * Only after receiving ACTION_ACTIVE_DEVICE_CHANGED it is known that device that 805 * will be audio switched to is available to be choose as communication device */ 806 if (!switchingBtDevices) { 807 return mFeatureFlags.callAudioCommunicationDeviceRefactor() ? 808 mCommunicationDeviceTracker.setCommunicationDevice( 809 AudioDeviceInfo.TYPE_BLE_HEADSET, device) 810 : setLeAudioCommunicationDevice(); 811 } 812 return true; 813 } 814 return false; 815 } else if (callProfile == BluetoothProfile.HEARING_AID) { 816 if (mBluetoothAdapter.setActiveDevice(device, BluetoothAdapter.ACTIVE_DEVICE_ALL)) { 817 /* ACTION_ACTIVE_DEVICE_CHANGED intent will trigger setting communication device. 818 * Only after receiving ACTION_ACTIVE_DEVICE_CHANGED it is known that device that 819 * will be audio switched to is available to be choose as communication device */ 820 if (!switchingBtDevices) { 821 return mFeatureFlags.callAudioCommunicationDeviceRefactor() ? 822 mCommunicationDeviceTracker.setCommunicationDevice( 823 AudioDeviceInfo.TYPE_HEARING_AID, null) 824 : setHearingAidCommunicationDevice(); 825 } 826 return true; 827 } 828 return false; 829 } else if (callProfile == BluetoothProfile.HEADSET) { 830 boolean success = mBluetoothAdapter.setActiveDevice(device, 831 BluetoothAdapter.ACTIVE_DEVICE_PHONE_CALL); 832 if (!success) { 833 Log.w(this, "Couldn't set active device to %s", address); 834 return false; 835 } 836 if (getBluetoothHeadset() != null) { 837 int scoConnectionRequest = mBluetoothHeadset.connectAudio(); 838 return scoConnectionRequest == BluetoothStatusCodes.SUCCESS || 839 scoConnectionRequest 840 == BluetoothStatusCodes.ERROR_AUDIO_DEVICE_ALREADY_CONNECTED; 841 } else { 842 Log.w(this, "Couldn't find bluetooth headset service"); 843 return false; 844 } 845 } else { 846 Log.w(this, "Attempting to turn on audio for a disconnected device"); 847 return false; 848 } 849 } 850 851 /** 852 * Used by CallAudioRouteController in order to connect the BT device. 853 * @param device {@link BluetoothDevice} to connect to. 854 * @param type {@link AudioRoute.AudioRouteType} associated with the device. 855 * @return {@code true} if device was successfully connected, {@code false} otherwise. 856 */ connectAudio(BluetoothDevice device, @AudioRoute.AudioRouteType int type)857 public boolean connectAudio(BluetoothDevice device, @AudioRoute.AudioRouteType int type) { 858 String address = device.getAddress(); 859 int callProfile = BluetoothProfile.LE_AUDIO; 860 if (type == TYPE_BLUETOOTH_SCO) { 861 callProfile = BluetoothProfile.HEADSET; 862 } else if (type == TYPE_BLUETOOTH_HA) { 863 callProfile = BluetoothProfile.HEARING_AID; 864 } 865 866 Bundle preferredAudioProfiles = mBluetoothAdapter.getPreferredAudioProfiles(device); 867 if (preferredAudioProfiles != null && !preferredAudioProfiles.isEmpty() 868 && preferredAudioProfiles.getInt(BluetoothAdapter.AUDIO_MODE_DUPLEX) != 0) { 869 Log.i(this, "Preferred duplex profile for device=" + address + " is " 870 + preferredAudioProfiles.getInt(BluetoothAdapter.AUDIO_MODE_DUPLEX)); 871 callProfile = preferredAudioProfiles.getInt(BluetoothAdapter.AUDIO_MODE_DUPLEX); 872 } 873 874 if (callProfile == BluetoothProfile.LE_AUDIO 875 || callProfile == BluetoothProfile.HEARING_AID) { 876 return mBluetoothAdapter.setActiveDevice(device, BluetoothAdapter.ACTIVE_DEVICE_ALL); 877 } else if (callProfile == BluetoothProfile.HEADSET) { 878 boolean success = mBluetoothAdapter.setActiveDevice(device, 879 BluetoothAdapter.ACTIVE_DEVICE_PHONE_CALL); 880 if (!success) { 881 Log.w(this, "Couldn't set active device to %s", address); 882 return false; 883 } 884 if (getBluetoothHeadset() != null) { 885 int scoConnectionRequest = mBluetoothHeadset.connectAudio(); 886 return scoConnectionRequest == BluetoothStatusCodes.SUCCESS || 887 scoConnectionRequest 888 == BluetoothStatusCodes.ERROR_AUDIO_DEVICE_ALREADY_CONNECTED; 889 } else { 890 Log.w(this, "Couldn't find bluetooth headset service"); 891 return false; 892 } 893 } else { 894 Log.w(this, "Attempting to turn on audio for a disconnected device"); 895 return false; 896 } 897 } 898 cacheHearingAidDevice()899 public void cacheHearingAidDevice() { 900 if (mBluetoothAdapter != null) { 901 for (BluetoothDevice device : mBluetoothAdapter.getActiveDevices( 902 BluetoothProfile.HEARING_AID)) { 903 if (device != null) { 904 mBluetoothHearingAidActiveDeviceCache = device; 905 } 906 } 907 } 908 } 909 restoreHearingAidDevice()910 public void restoreHearingAidDevice() { 911 if (mBluetoothHearingAidActiveDeviceCache != null) { 912 mBluetoothAdapter.setActiveDevice(mBluetoothHearingAidActiveDeviceCache, 913 BluetoothAdapter.ACTIVE_DEVICE_ALL); 914 mBluetoothHearingAidActiveDeviceCache = null; 915 } 916 } 917 isInbandRingingEnabled()918 public boolean isInbandRingingEnabled() { 919 // Get the inband ringing enabled status of expected BT device to route call audio instead 920 // of using the address of currently connected device. 921 BluetoothDevice activeDevice = mBluetoothRouteManager.getMostRecentlyReportedActiveDevice(); 922 return isInbandRingEnabled(activeDevice); 923 } 924 isInbandRingEnabled(BluetoothDevice bluetoothDevice)925 public boolean isInbandRingEnabled(BluetoothDevice bluetoothDevice) { 926 Log.i(this, "isInbandRingEnabled: device: " + bluetoothDevice); 927 if (mBluetoothRouteManager.isCachedLeAudioDevice(bluetoothDevice)) { 928 if (mBluetoothLeAudioService == null) { 929 Log.i(this, "isInbandRingingEnabled: no leaudio service available."); 930 return false; 931 } 932 int groupId = mBluetoothLeAudioService.getGroupId(bluetoothDevice); 933 return mBluetoothLeAudioService.isInbandRingtoneEnabled(groupId); 934 } else { 935 if (getBluetoothHeadset() == null) { 936 Log.i(this, "isInbandRingingEnabled: no headset service available."); 937 return false; 938 } 939 return mBluetoothHeadset.isInbandRingingEnabled(); 940 } 941 } 942 setCallAudioRouteAdapter(CallAudioRouteAdapter adapter)943 public void setCallAudioRouteAdapter(CallAudioRouteAdapter adapter) { 944 mCallAudioRouteAdapter = adapter; 945 } 946 dump(IndentingPrintWriter pw)947 public void dump(IndentingPrintWriter pw) { 948 mLocalLog.dump(pw); 949 } 950 } 951