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