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 android.bluetooth.BluetoothAdapter; 20 import android.bluetooth.BluetoothDevice; 21 import android.bluetooth.BluetoothHeadset; 22 import android.bluetooth.BluetoothHearingAid; 23 import android.bluetooth.BluetoothLeAudio; 24 import android.bluetooth.BluetoothLeAudioCodecStatus; 25 import android.bluetooth.BluetoothProfile; 26 import android.bluetooth.BluetoothStatusCodes; 27 import android.content.Context; 28 import android.media.AudioManager; 29 import android.media.AudioDeviceInfo; 30 import android.media.audio.common.AudioDevice; 31 import android.os.Bundle; 32 import android.telecom.Log; 33 import android.util.ArraySet; 34 import android.util.LocalLog; 35 36 import com.android.internal.util.IndentingPrintWriter; 37 38 import java.util.ArrayList; 39 import java.util.Collection; 40 import java.util.Collections; 41 import java.util.concurrent.Executor; 42 import java.util.LinkedHashMap; 43 import java.util.LinkedHashSet; 44 import java.util.LinkedList; 45 import java.util.List; 46 import java.util.Objects; 47 import java.util.Set; 48 49 public class BluetoothDeviceManager { 50 51 public static final int DEVICE_TYPE_HEADSET = 0; 52 public static final int DEVICE_TYPE_HEARING_AID = 1; 53 public static final int DEVICE_TYPE_LE_AUDIO = 2; 54 55 private BluetoothLeAudio.Callback mLeAudioCallbacks = 56 new BluetoothLeAudio.Callback() { 57 @Override 58 public void onCodecConfigChanged(int groupId, BluetoothLeAudioCodecStatus status) {} 59 @Override 60 public void onGroupStatusChanged(int groupId, int groupStatus) {} 61 @Override 62 public void onGroupNodeAdded(BluetoothDevice device, int groupId) { 63 Log.i(this, device.getAddress() + " group added " + groupId); 64 if (device == null || groupId == BluetoothLeAudio.GROUP_ID_INVALID) { 65 Log.w(this, "invalid parameter"); 66 return; 67 } 68 69 synchronized (mLock) { 70 mGroupsByDevice.put(device, groupId); 71 } 72 } 73 @Override 74 public void onGroupNodeRemoved(BluetoothDevice device, int groupId) { 75 if (device == null || groupId == BluetoothLeAudio.GROUP_ID_INVALID) { 76 Log.w(this, "invalid parameter"); 77 return; 78 } 79 80 synchronized (mLock) { 81 mGroupsByDevice.remove(device); 82 } 83 } 84 }; 85 86 private final BluetoothProfile.ServiceListener mBluetoothProfileServiceListener = 87 new BluetoothProfile.ServiceListener() { 88 @Override 89 public void onServiceConnected(int profile, BluetoothProfile proxy) { 90 Log.startSession("BMSL.oSC"); 91 try { 92 synchronized (mLock) { 93 String logString; 94 if (profile == BluetoothProfile.HEADSET) { 95 mBluetoothHeadset = (BluetoothHeadset) proxy; 96 logString = "Got BluetoothHeadset: " + mBluetoothHeadset; 97 } else if (profile == BluetoothProfile.HEARING_AID) { 98 mBluetoothHearingAid = (BluetoothHearingAid) proxy; 99 logString = "Got BluetoothHearingAid: " 100 + mBluetoothHearingAid; 101 } else if (profile == BluetoothProfile.LE_AUDIO) { 102 mBluetoothLeAudioService = (BluetoothLeAudio) proxy; 103 logString = "Got BluetoothLeAudio: " 104 + mBluetoothLeAudioService; 105 if (!mLeAudioCallbackRegistered) { 106 try { 107 mBluetoothLeAudioService.registerCallback( 108 mExecutor, mLeAudioCallbacks); 109 mLeAudioCallbackRegistered = true; 110 } catch (IllegalStateException e) { 111 logString += ", but Bluetooth is down"; 112 } 113 } 114 } else { 115 logString = "Connected to non-requested bluetooth service." + 116 " Not changing bluetooth headset."; 117 } 118 Log.i(BluetoothDeviceManager.this, logString); 119 mLocalLog.log(logString); 120 } 121 } finally { 122 Log.endSession(); 123 } 124 } 125 126 @Override 127 public void onServiceDisconnected(int profile) { 128 Log.startSession("BMSL.oSD"); 129 try { 130 synchronized (mLock) { 131 LinkedHashMap<String, BluetoothDevice> lostServiceDevices; 132 String logString; 133 if (profile == BluetoothProfile.HEADSET) { 134 mBluetoothHeadset = null; 135 lostServiceDevices = mHfpDevicesByAddress; 136 mBluetoothRouteManager.onActiveDeviceChanged(null, 137 DEVICE_TYPE_HEADSET); 138 logString = "Lost BluetoothHeadset service. " + 139 "Removing all tracked devices"; 140 } else if (profile == BluetoothProfile.HEARING_AID) { 141 mBluetoothHearingAid = null; 142 logString = "Lost BluetoothHearingAid service. " + 143 "Removing all tracked devices."; 144 lostServiceDevices = mHearingAidDevicesByAddress; 145 mBluetoothRouteManager.onActiveDeviceChanged(null, 146 DEVICE_TYPE_HEARING_AID); 147 } else if (profile == BluetoothProfile.LE_AUDIO) { 148 mBluetoothLeAudioService = null; 149 logString = "Lost BluetoothLeAudio service. " + 150 "Removing all tracked devices."; 151 lostServiceDevices = mLeAudioDevicesByAddress; 152 mBluetoothRouteManager.onActiveDeviceChanged(null, 153 DEVICE_TYPE_LE_AUDIO); 154 } else { 155 return; 156 } 157 Log.i(BluetoothDeviceManager.this, logString); 158 mLocalLog.log(logString); 159 160 List<BluetoothDevice> devicesToRemove = new LinkedList<>( 161 lostServiceDevices.values()); 162 lostServiceDevices.clear(); 163 for (BluetoothDevice device : devicesToRemove) { 164 mBluetoothRouteManager.onDeviceLost(device.getAddress()); 165 } 166 } 167 } finally { 168 Log.endSession(); 169 } 170 } 171 }; 172 173 private final LinkedHashMap<String, BluetoothDevice> mHfpDevicesByAddress = 174 new LinkedHashMap<>(); 175 private final LinkedHashMap<String, BluetoothDevice> mHearingAidDevicesByAddress = 176 new LinkedHashMap<>(); 177 private final LinkedHashMap<BluetoothDevice, Long> mHearingAidDeviceSyncIds = 178 new LinkedHashMap<>(); 179 private final LinkedHashMap<String, BluetoothDevice> mLeAudioDevicesByAddress = 180 new LinkedHashMap<>(); 181 private final LinkedHashMap<BluetoothDevice, Integer> mGroupsByDevice = 182 new LinkedHashMap<>(); 183 private int mGroupIdActive = BluetoothLeAudio.GROUP_ID_INVALID; 184 private int mGroupIdPending = BluetoothLeAudio.GROUP_ID_INVALID; 185 private final LocalLog mLocalLog = new LocalLog(20); 186 187 // This lock only protects internal state -- it doesn't lock on anything going into Telecom. 188 private final Object mLock = new Object(); 189 190 private BluetoothRouteManager mBluetoothRouteManager; 191 private BluetoothHeadset mBluetoothHeadset; 192 private BluetoothHearingAid mBluetoothHearingAid; 193 private boolean mLeAudioCallbackRegistered = false; 194 private BluetoothLeAudio mBluetoothLeAudioService; 195 private boolean mLeAudioSetAsCommunicationDevice = false; 196 private String mLeAudioDevice; 197 private String mHearingAidDevice; 198 private boolean mHearingAidSetAsCommunicationDevice = false; 199 private BluetoothDevice mBluetoothHearingAidActiveDeviceCache; 200 private BluetoothAdapter mBluetoothAdapter; 201 private AudioManager mAudioManager; 202 private Executor mExecutor; 203 BluetoothDeviceManager(Context context, BluetoothAdapter bluetoothAdapter)204 public BluetoothDeviceManager(Context context, BluetoothAdapter bluetoothAdapter) { 205 if (bluetoothAdapter != null) { 206 mBluetoothAdapter = bluetoothAdapter; 207 bluetoothAdapter.getProfileProxy(context, mBluetoothProfileServiceListener, 208 BluetoothProfile.HEADSET); 209 bluetoothAdapter.getProfileProxy(context, mBluetoothProfileServiceListener, 210 BluetoothProfile.HEARING_AID); 211 bluetoothAdapter.getProfileProxy(context, mBluetoothProfileServiceListener, 212 BluetoothProfile.LE_AUDIO); 213 mAudioManager = context.getSystemService(AudioManager.class); 214 mExecutor = context.getMainExecutor(); 215 } 216 } 217 setBluetoothRouteManager(BluetoothRouteManager brm)218 public void setBluetoothRouteManager(BluetoothRouteManager brm) { 219 mBluetoothRouteManager = brm; 220 } 221 getLeAudioConnectedDevices()222 private List<BluetoothDevice> getLeAudioConnectedDevices() { 223 synchronized (mLock) { 224 // Let's get devices which are a group leaders 225 ArrayList<BluetoothDevice> devices = new ArrayList<>(); 226 227 if (mGroupsByDevice.isEmpty() || mBluetoothLeAudioService == null) { 228 return devices; 229 } 230 231 for (LinkedHashMap.Entry<BluetoothDevice, Integer> entry : mGroupsByDevice.entrySet()) { 232 if (Objects.equals(entry.getKey(), 233 mBluetoothLeAudioService.getConnectedGroupLeadDevice(entry.getValue()))) { 234 devices.add(entry.getKey()); 235 } 236 } 237 devices.removeIf(device -> !mLeAudioDevicesByAddress.containsValue(device)); 238 return devices; 239 } 240 } 241 getNumConnectedDevices()242 public int getNumConnectedDevices() { 243 return getConnectedDevices().size(); 244 } 245 getConnectedDevices()246 public Collection<BluetoothDevice> getConnectedDevices() { 247 synchronized (mLock) { 248 ArraySet<BluetoothDevice> result = new ArraySet<>(); 249 250 // Set storing the group ids of all dual mode audio devices to de-dupe them 251 Set<Integer> dualModeGroupIds = new ArraySet<>(); 252 for (BluetoothDevice hfpDevice: mHfpDevicesByAddress.values()) { 253 result.add(hfpDevice); 254 if (mBluetoothLeAudioService == null) { 255 continue; 256 } 257 int groupId = mBluetoothLeAudioService.getGroupId(hfpDevice); 258 if (groupId != BluetoothLeAudio.GROUP_ID_INVALID) { 259 dualModeGroupIds.add(groupId); 260 } 261 } 262 263 result.addAll(mHearingAidDevicesByAddress.values()); 264 if (mBluetoothLeAudioService == null) { 265 return Collections.unmodifiableCollection(result); 266 } 267 for (BluetoothDevice leAudioDevice: getLeAudioConnectedDevices()) { 268 // Exclude dual mode audio devices included from the HFP devices list 269 int groupId = mBluetoothLeAudioService.getGroupId(leAudioDevice); 270 if (groupId != BluetoothLeAudio.GROUP_ID_INVALID 271 && !dualModeGroupIds.contains(groupId)) { 272 result.add(leAudioDevice); 273 } 274 } 275 return Collections.unmodifiableCollection(result); 276 } 277 } 278 279 // Same as getConnectedDevices except it filters out the hearing aid devices that are linked 280 // together by their hiSyncId. getUniqueConnectedDevices()281 public Collection<BluetoothDevice> getUniqueConnectedDevices() { 282 ArraySet<BluetoothDevice> result; 283 synchronized (mLock) { 284 result = new ArraySet<>(mHfpDevicesByAddress.values()); 285 } 286 Set<Long> seenHiSyncIds = new LinkedHashSet<>(); 287 // Add the left-most active device to the seen list so that we match up with the list 288 // generated in BluetoothRouteManager. 289 if (mBluetoothAdapter != null) { 290 for (BluetoothDevice device : mBluetoothAdapter.getActiveDevices( 291 BluetoothProfile.HEARING_AID)) { 292 if (device != null) { 293 result.add(device); 294 seenHiSyncIds.add(mHearingAidDeviceSyncIds.getOrDefault(device, -1L)); 295 break; 296 } 297 } 298 } 299 synchronized (mLock) { 300 for (BluetoothDevice d : mHearingAidDevicesByAddress.values()) { 301 long hiSyncId = mHearingAidDeviceSyncIds.getOrDefault(d, -1L); 302 if (seenHiSyncIds.contains(hiSyncId)) { 303 continue; 304 } 305 result.add(d); 306 seenHiSyncIds.add(hiSyncId); 307 } 308 } 309 310 if (mBluetoothLeAudioService != null) { 311 result.addAll(getLeAudioConnectedDevices()); 312 } 313 314 return Collections.unmodifiableCollection(result); 315 } 316 getBluetoothHeadset()317 public BluetoothHeadset getBluetoothHeadset() { 318 return mBluetoothHeadset; 319 } 320 getBluetoothAdapter()321 public BluetoothAdapter getBluetoothAdapter() { 322 return mBluetoothAdapter; 323 } 324 getBluetoothHearingAid()325 public BluetoothHearingAid getBluetoothHearingAid() { 326 return mBluetoothHearingAid; 327 } 328 getLeAudioService()329 public BluetoothLeAudio getLeAudioService() { 330 return mBluetoothLeAudioService; 331 } 332 setHeadsetServiceForTesting(BluetoothHeadset bluetoothHeadset)333 public void setHeadsetServiceForTesting(BluetoothHeadset bluetoothHeadset) { 334 mBluetoothHeadset = bluetoothHeadset; 335 } 336 setHearingAidServiceForTesting(BluetoothHearingAid bluetoothHearingAid)337 public void setHearingAidServiceForTesting(BluetoothHearingAid bluetoothHearingAid) { 338 mBluetoothHearingAid = bluetoothHearingAid; 339 } 340 setLeAudioServiceForTesting(BluetoothLeAudio bluetoothLeAudio)341 public void setLeAudioServiceForTesting(BluetoothLeAudio bluetoothLeAudio) { 342 mBluetoothLeAudioService = bluetoothLeAudio; 343 mBluetoothLeAudioService.registerCallback(mExecutor, mLeAudioCallbacks); 344 } 345 getDeviceTypeString(int deviceType)346 public static String getDeviceTypeString(int deviceType) { 347 switch (deviceType) { 348 case DEVICE_TYPE_LE_AUDIO: 349 return "LeAudio"; 350 case DEVICE_TYPE_HEARING_AID: 351 return "HearingAid"; 352 case DEVICE_TYPE_HEADSET: 353 return "HFP"; 354 default: 355 return "unknown type"; 356 } 357 } 358 onDeviceConnected(BluetoothDevice device, int deviceType)359 void onDeviceConnected(BluetoothDevice device, int deviceType) { 360 synchronized (mLock) { 361 LinkedHashMap<String, BluetoothDevice> targetDeviceMap; 362 if (deviceType == DEVICE_TYPE_LE_AUDIO) { 363 if (mBluetoothLeAudioService == null) { 364 Log.w(this, "LE audio service null when receiving device added broadcast"); 365 return; 366 } 367 /* Check if group is known. */ 368 if (!mGroupsByDevice.containsKey(device)) { 369 int groupId = mBluetoothLeAudioService.getGroupId(device); 370 /* If it is not yet assigned, then it will be provided in the callback */ 371 if (groupId != BluetoothLeAudio.GROUP_ID_INVALID) { 372 mGroupsByDevice.put(device, groupId); 373 } 374 } 375 targetDeviceMap = mLeAudioDevicesByAddress; 376 } else if (deviceType == DEVICE_TYPE_HEARING_AID) { 377 if (mBluetoothHearingAid == null) { 378 Log.w(this, "Hearing aid service null when receiving device added broadcast"); 379 return; 380 } 381 long hiSyncId = mBluetoothHearingAid.getHiSyncId(device); 382 mHearingAidDeviceSyncIds.put(device, hiSyncId); 383 targetDeviceMap = mHearingAidDevicesByAddress; 384 } else if (deviceType == DEVICE_TYPE_HEADSET) { 385 if (mBluetoothHeadset == null) { 386 Log.w(this, "Headset service null when receiving device added broadcast"); 387 return; 388 } 389 targetDeviceMap = mHfpDevicesByAddress; 390 } else { 391 Log.w(this, "Device: " + device.getAddress() + " with invalid type: " 392 + getDeviceTypeString(deviceType)); 393 return; 394 } 395 if (!targetDeviceMap.containsKey(device.getAddress())) { 396 Log.i(this, "Adding device with address: " + device + " and devicetype=" 397 + getDeviceTypeString(deviceType)); 398 targetDeviceMap.put(device.getAddress(), device); 399 mBluetoothRouteManager.onDeviceAdded(device.getAddress()); 400 } 401 } 402 } 403 onDeviceDisconnected(BluetoothDevice device, int deviceType)404 void onDeviceDisconnected(BluetoothDevice device, int deviceType) { 405 mLocalLog.log("Device disconnected -- address: " + device.getAddress() + " deviceType: " 406 + deviceType); 407 synchronized (mLock) { 408 LinkedHashMap<String, BluetoothDevice> targetDeviceMap; 409 if (deviceType == DEVICE_TYPE_LE_AUDIO) { 410 targetDeviceMap = mLeAudioDevicesByAddress; 411 } else if (deviceType == DEVICE_TYPE_HEARING_AID) { 412 mHearingAidDeviceSyncIds.remove(device); 413 targetDeviceMap = mHearingAidDevicesByAddress; 414 } else if (deviceType == DEVICE_TYPE_HEADSET) { 415 targetDeviceMap = mHfpDevicesByAddress; 416 } else { 417 Log.w(this, "Device: " + device.getAddress() + " with invalid type: " 418 + getDeviceTypeString(deviceType)); 419 return; 420 } 421 if (targetDeviceMap.containsKey(device.getAddress())) { 422 Log.i(this, "Removing device with address: " + device + " and devicetype=" 423 + getDeviceTypeString(deviceType)); 424 targetDeviceMap.remove(device.getAddress()); 425 mBluetoothRouteManager.onDeviceLost(device.getAddress()); 426 } 427 } 428 } 429 disconnectAudio()430 public void disconnectAudio() { 431 disconnectSco(); 432 clearLeAudioCommunicationDevice(); 433 clearHearingAidCommunicationDevice(); 434 } 435 disconnectSco()436 public void disconnectSco() { 437 if (mBluetoothHeadset == null) { 438 Log.w(this, "Trying to disconnect audio but no headset service exists."); 439 } else { 440 mBluetoothHeadset.disconnectAudio(); 441 } 442 } 443 isLeAudioCommunicationDevice()444 public boolean isLeAudioCommunicationDevice() { 445 return mLeAudioSetAsCommunicationDevice; 446 } 447 isHearingAidSetAsCommunicationDevice()448 public boolean isHearingAidSetAsCommunicationDevice() { 449 return mHearingAidSetAsCommunicationDevice; 450 } 451 clearLeAudioCommunicationDevice()452 public void clearLeAudioCommunicationDevice() { 453 Log.i(this, "clearLeAudioCommunicationDevice: mLeAudioSetAsCommunicationDevice = " + 454 mLeAudioSetAsCommunicationDevice + " device = " + mLeAudioDevice); 455 if (!mLeAudioSetAsCommunicationDevice) { 456 return; 457 } 458 mLeAudioSetAsCommunicationDevice = false; 459 if (mLeAudioDevice != null) { 460 mBluetoothRouteManager.onAudioLost(mLeAudioDevice); 461 mLeAudioDevice = null; 462 } 463 464 if (mAudioManager == null) { 465 Log.i(this, "clearLeAudioCommunicationDevice: mAudioManager is null"); 466 return; 467 } 468 469 AudioDeviceInfo audioDeviceInfo = mAudioManager.getCommunicationDevice(); 470 if (audioDeviceInfo != null && audioDeviceInfo.getType() 471 == AudioDeviceInfo.TYPE_BLE_HEADSET) { 472 mBluetoothRouteManager.onAudioLost(audioDeviceInfo.getAddress()); 473 mAudioManager.clearCommunicationDevice(); 474 } 475 } 476 clearHearingAidCommunicationDevice()477 public void clearHearingAidCommunicationDevice() { 478 Log.i(this, "clearHearingAidCommunicationDevice: mHearingAidSetAsCommunicationDevice = " 479 + mHearingAidSetAsCommunicationDevice); 480 if (!mHearingAidSetAsCommunicationDevice) { 481 return; 482 } 483 mHearingAidSetAsCommunicationDevice = false; 484 if (mHearingAidDevice != null) { 485 mBluetoothRouteManager.onAudioLost(mHearingAidDevice); 486 mHearingAidDevice = null; 487 } 488 489 if (mAudioManager == null) { 490 Log.i(this, "clearHearingAidCommunicationDevice: mAudioManager is null"); 491 return; 492 } 493 494 AudioDeviceInfo audioDeviceInfo = mAudioManager.getCommunicationDevice(); 495 if (audioDeviceInfo != null && audioDeviceInfo.getType() 496 == AudioDeviceInfo.TYPE_HEARING_AID) { 497 mAudioManager.clearCommunicationDevice(); 498 } 499 } 500 setLeAudioCommunicationDevice()501 public boolean setLeAudioCommunicationDevice() { 502 Log.i(this, "setLeAudioCommunicationDevice"); 503 504 if (mLeAudioSetAsCommunicationDevice) { 505 Log.i(this, "setLeAudioCommunicationDevice already set"); 506 return true; 507 } 508 509 if (mAudioManager == null) { 510 Log.w(this, " mAudioManager is null"); 511 return false; 512 } 513 514 AudioDeviceInfo bleHeadset = null; 515 List<AudioDeviceInfo> devices = mAudioManager.getAvailableCommunicationDevices(); 516 if (devices.size() == 0) { 517 Log.w(this, " No communication devices available."); 518 return false; 519 } 520 521 for (AudioDeviceInfo device : devices) { 522 Log.i(this, " Available device type: " + device.getType()); 523 if (device.getType() == AudioDeviceInfo.TYPE_BLE_HEADSET) { 524 bleHeadset = device; 525 break; 526 } 527 } 528 529 if (bleHeadset == null) { 530 Log.w(this, " No bleHeadset device available"); 531 return false; 532 } 533 534 // clear hearing aid communication device if set 535 clearHearingAidCommunicationDevice(); 536 537 // Turn BLE_OUT_HEADSET ON. 538 boolean result = mAudioManager.setCommunicationDevice(bleHeadset); 539 if (!result) { 540 Log.w(this, " Could not set bleHeadset device"); 541 } else { 542 Log.i(this, " bleHeadset device set"); 543 mBluetoothRouteManager.onAudioOn(bleHeadset.getAddress()); 544 mLeAudioSetAsCommunicationDevice = true; 545 mLeAudioDevice = bleHeadset.getAddress(); 546 } 547 return result; 548 } 549 setHearingAidCommunicationDevice()550 public boolean setHearingAidCommunicationDevice() { 551 Log.i(this, "setHearingAidCommunicationDevice"); 552 553 if (mHearingAidSetAsCommunicationDevice) { 554 Log.i(this, "mHearingAidSetAsCommunicationDevice already set"); 555 return true; 556 } 557 558 if (mAudioManager == null) { 559 Log.w(this, " mAudioManager is null"); 560 return false; 561 } 562 563 AudioDeviceInfo hearingAid = null; 564 List<AudioDeviceInfo> devices = mAudioManager.getAvailableCommunicationDevices(); 565 if (devices.size() == 0) { 566 Log.w(this, " No communication devices available."); 567 return false; 568 } 569 570 for (AudioDeviceInfo device : devices) { 571 Log.i(this, " Available device type: " + device.getType()); 572 if (device.getType() == AudioDeviceInfo.TYPE_HEARING_AID) { 573 hearingAid = device; 574 break; 575 } 576 } 577 578 if (hearingAid == null) { 579 Log.w(this, " No hearingAid device available"); 580 return false; 581 } 582 583 // clear LE audio communication device if set 584 clearLeAudioCommunicationDevice(); 585 586 // Turn hearing aid ON. 587 boolean result = mAudioManager.setCommunicationDevice(hearingAid); 588 if (!result) { 589 Log.w(this, " Could not set hearingAid device"); 590 } else { 591 Log.i(this, " hearingAid device set"); 592 mHearingAidDevice = hearingAid.getAddress(); 593 mHearingAidSetAsCommunicationDevice = true; 594 } 595 return result; 596 } 597 598 // Connect audio to the bluetooth device at address, checking to see whether it's 599 // le audio, hearing aid or a HFP device, and using the proper BT API. connectAudio(String address, boolean switchingBtDevices)600 public boolean connectAudio(String address, boolean switchingBtDevices) { 601 int callProfile = BluetoothProfile.LE_AUDIO; 602 Log.i(this, "Telecomm connecting audio to device: " + address); 603 BluetoothDevice device = null; 604 if (mLeAudioDevicesByAddress.containsKey(address)) { 605 Log.i(this, "Telecomm found LE Audio device for address: " + address); 606 if (mBluetoothLeAudioService == null) { 607 Log.w(this, "Attempting to turn on audio when the le audio service is null"); 608 return false; 609 } 610 device = mLeAudioDevicesByAddress.get(address); 611 callProfile = BluetoothProfile.LE_AUDIO; 612 } else if (mHearingAidDevicesByAddress.containsKey(address)) { 613 Log.i(this, "Telecomm found hearing aid device for address: " + address); 614 if (mBluetoothHearingAid == null) { 615 Log.w(this, "Attempting to turn on audio when the hearing aid service is null"); 616 return false; 617 } 618 device = mHearingAidDevicesByAddress.get(address); 619 callProfile = BluetoothProfile.HEARING_AID; 620 } else if (mHfpDevicesByAddress.containsKey(address)) { 621 Log.i(this, "Telecomm found HFP device for address: " + address); 622 if (mBluetoothHeadset == null) { 623 Log.w(this, "Attempting to turn on audio when the headset service is null"); 624 return false; 625 } 626 device = mHfpDevicesByAddress.get(address); 627 callProfile = BluetoothProfile.HEADSET; 628 } 629 630 if (device == null) { 631 Log.w(this, "No active profiles for Bluetooth address=" + address); 632 return false; 633 } 634 635 Bundle preferredAudioProfiles = mBluetoothAdapter.getPreferredAudioProfiles(device); 636 if (preferredAudioProfiles != null && !preferredAudioProfiles.isEmpty() 637 && preferredAudioProfiles.getInt(BluetoothAdapter.AUDIO_MODE_DUPLEX) != 0) { 638 Log.i(this, "Preferred duplex profile for device=" + address + " is " 639 + preferredAudioProfiles.getInt(BluetoothAdapter.AUDIO_MODE_DUPLEX)); 640 callProfile = preferredAudioProfiles.getInt(BluetoothAdapter.AUDIO_MODE_DUPLEX); 641 } 642 643 if (callProfile == BluetoothProfile.LE_AUDIO) { 644 if (mBluetoothAdapter.setActiveDevice( 645 device, BluetoothAdapter.ACTIVE_DEVICE_ALL)) { 646 /* ACTION_ACTIVE_DEVICE_CHANGED intent will trigger setting communication device. 647 * Only after receiving ACTION_ACTIVE_DEVICE_CHANGED it is known that device that 648 * will be audio switched to is available to be choose as communication device */ 649 if (!switchingBtDevices) { 650 return setLeAudioCommunicationDevice(); 651 } 652 return true; 653 } 654 return false; 655 } else if (callProfile == BluetoothProfile.HEARING_AID) { 656 if (mBluetoothAdapter.setActiveDevice(device, BluetoothAdapter.ACTIVE_DEVICE_ALL)) { 657 /* ACTION_ACTIVE_DEVICE_CHANGED intent will trigger setting communication device. 658 * Only after receiving ACTION_ACTIVE_DEVICE_CHANGED it is known that device that 659 * will be audio switched to is available to be choose as communication device */ 660 if (!switchingBtDevices) { 661 return setHearingAidCommunicationDevice(); 662 } 663 return true; 664 } 665 return false; 666 } else if (callProfile == BluetoothProfile.HEADSET) { 667 boolean success = mBluetoothAdapter.setActiveDevice(device, 668 BluetoothAdapter.ACTIVE_DEVICE_PHONE_CALL); 669 if (!success) { 670 Log.w(this, "Couldn't set active device to %s", address); 671 return false; 672 } 673 int scoConnectionRequest = mBluetoothHeadset.connectAudio(); 674 return scoConnectionRequest == BluetoothStatusCodes.SUCCESS || 675 scoConnectionRequest == BluetoothStatusCodes.ERROR_AUDIO_DEVICE_ALREADY_CONNECTED; 676 } else { 677 Log.w(this, "Attempting to turn on audio for a disconnected device"); 678 return false; 679 } 680 } 681 cacheHearingAidDevice()682 public void cacheHearingAidDevice() { 683 if (mBluetoothAdapter != null) { 684 for (BluetoothDevice device : mBluetoothAdapter.getActiveDevices( 685 BluetoothProfile.HEARING_AID)) { 686 if (device != null) { 687 mBluetoothHearingAidActiveDeviceCache = device; 688 } 689 } 690 } 691 } 692 restoreHearingAidDevice()693 public void restoreHearingAidDevice() { 694 if (mBluetoothHearingAidActiveDeviceCache != null) { 695 mBluetoothAdapter.setActiveDevice(mBluetoothHearingAidActiveDeviceCache, 696 BluetoothAdapter.ACTIVE_DEVICE_ALL); 697 mBluetoothHearingAidActiveDeviceCache = null; 698 } 699 } 700 isInbandRingingEnabled()701 public boolean isInbandRingingEnabled() { 702 BluetoothDevice activeDevice = mBluetoothRouteManager.getBluetoothAudioConnectedDevice(); 703 Log.i(this, "isInbandRingingEnabled: activeDevice: " + activeDevice); 704 if (mBluetoothRouteManager.isCachedLeAudioDevice(activeDevice)) { 705 if (mBluetoothLeAudioService == null) { 706 Log.i(this, "isInbandRingingEnabled: no leaudio service available."); 707 return false; 708 } 709 int groupId = mBluetoothLeAudioService.getGroupId(activeDevice); 710 return mBluetoothLeAudioService.isInbandRingtoneEnabled(groupId); 711 } else { 712 if (mBluetoothHeadset == null) { 713 Log.i(this, "isInbandRingingEnabled: no headset service available."); 714 return false; 715 } 716 return mBluetoothHeadset.isInbandRingingEnabled(); 717 } 718 } 719 dump(IndentingPrintWriter pw)720 public void dump(IndentingPrintWriter pw) { 721 mLocalLog.dump(pw); 722 } 723 } 724