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.BluetoothClass; 21 import android.bluetooth.BluetoothDevice; 22 import android.bluetooth.BluetoothHeadset; 23 import android.bluetooth.BluetoothHearingAid; 24 import android.bluetooth.BluetoothProfile; 25 import android.bluetooth.BluetoothLeAudio; 26 import android.content.Context; 27 import android.media.AudioDeviceInfo; 28 import android.os.Message; 29 import android.telecom.Log; 30 import android.telecom.Logging.Session; 31 import android.util.Pair; 32 import android.util.SparseArray; 33 34 import com.android.internal.annotations.VisibleForTesting; 35 import com.android.internal.os.SomeArgs; 36 import com.android.internal.util.IState; 37 import com.android.internal.util.State; 38 import com.android.internal.util.StateMachine; 39 import com.android.server.telecom.CallAudioCommunicationDeviceTracker; 40 import com.android.server.telecom.TelecomSystem; 41 import com.android.server.telecom.Timeouts; 42 import com.android.server.telecom.flags.FeatureFlags; 43 44 import java.util.Collection; 45 import java.util.HashMap; 46 import java.util.HashSet; 47 import java.util.LinkedHashSet; 48 import java.util.List; 49 import java.util.Map; 50 import java.util.Objects; 51 import java.util.Optional; 52 import java.util.Set; 53 import java.util.concurrent.BlockingQueue; 54 import java.util.concurrent.LinkedBlockingQueue; 55 import java.util.concurrent.TimeUnit; 56 57 public class BluetoothRouteManager extends StateMachine { 58 private static final String LOG_TAG = BluetoothRouteManager.class.getSimpleName(); 59 60 private static final SparseArray<String> MESSAGE_CODE_TO_NAME = new SparseArray<String>() {{ 61 put(NEW_DEVICE_CONNECTED, "NEW_DEVICE_CONNECTED"); 62 put(LOST_DEVICE, "LOST_DEVICE"); 63 put(CONNECT_BT, "CONNECT_BT"); 64 put(DISCONNECT_BT, "DISCONNECT_BT"); 65 put(RETRY_BT_CONNECTION, "RETRY_BT_CONNECTION"); 66 put(BT_AUDIO_IS_ON, "BT_AUDIO_IS_ON"); 67 put(BT_AUDIO_LOST, "BT_AUDIO_LOST"); 68 put(CONNECTION_TIMEOUT, "CONNECTION_TIMEOUT"); 69 put(GET_CURRENT_STATE, "GET_CURRENT_STATE"); 70 put(RUN_RUNNABLE, "RUN_RUNNABLE"); 71 }}; 72 73 public static final String AUDIO_OFF_STATE_NAME = "AudioOff"; 74 public static final String AUDIO_CONNECTING_STATE_NAME_PREFIX = "Connecting"; 75 public static final String AUDIO_CONNECTED_STATE_NAME_PREFIX = "Connected"; 76 77 // Timeout for querying the current state from the state machine handler. 78 private static final int GET_STATE_TIMEOUT = 1000; 79 80 public interface BluetoothStateListener { onBluetoothDeviceListChanged()81 void onBluetoothDeviceListChanged(); onBluetoothActiveDevicePresent()82 void onBluetoothActiveDevicePresent(); onBluetoothActiveDeviceGone()83 void onBluetoothActiveDeviceGone(); onBluetoothAudioConnected()84 void onBluetoothAudioConnected(); onBluetoothAudioConnecting()85 void onBluetoothAudioConnecting(); onBluetoothAudioDisconnected()86 void onBluetoothAudioDisconnected(); 87 /** 88 * This gets called when we get an unexpected state change from Bluetooth. Their stack does 89 * weird things sometimes, so this is really a signal for the listener to refresh their 90 * internal state and make sure it matches up with what the BT stack is doing. 91 */ onUnexpectedBluetoothStateChange()92 void onUnexpectedBluetoothStateChange(); 93 } 94 95 /** 96 * Constants representing messages sent to the state machine. 97 * Messages are expected to be sent with {@link SomeArgs} as the obj. 98 * In all cases, arg1 will be the log session. 99 */ 100 // arg2: Address of the new device 101 public static final int NEW_DEVICE_CONNECTED = 1; 102 // arg2: Address of the lost device 103 public static final int LOST_DEVICE = 2; 104 105 // arg2 (optional): the address of the specific device to connect to. 106 public static final int CONNECT_BT = 100; 107 // No args. 108 public static final int DISCONNECT_BT = 101; 109 // arg2: the address of the device to connect to. 110 public static final int RETRY_BT_CONNECTION = 102; 111 112 // arg2: the address of the device that is on 113 public static final int BT_AUDIO_IS_ON = 200; 114 // arg2: the address of the device that lost BT audio 115 public static final int BT_AUDIO_LOST = 201; 116 117 // No args; only used internally 118 public static final int CONNECTION_TIMEOUT = 300; 119 120 // Get the current state and send it through the BlockingQueue<IState> provided as the object 121 // arg. 122 public static final int GET_CURRENT_STATE = 400; 123 124 // arg2: Runnable 125 public static final int RUN_RUNNABLE = 9001; 126 127 private static final int MAX_CONNECTION_RETRIES = 2; 128 129 // States 130 private final class AudioOffState extends State { 131 @Override getName()132 public String getName() { 133 return AUDIO_OFF_STATE_NAME; 134 } 135 136 @Override enter()137 public void enter() { 138 BluetoothDevice erroneouslyConnectedDevice = getBluetoothAudioConnectedDevice(); 139 if (erroneouslyConnectedDevice != null && 140 !erroneouslyConnectedDevice.equals(mHearingAidActiveDeviceCache)) { 141 Log.w(LOG_TAG, "Entering AudioOff state but device %s appears to be connected. " + 142 "Switching to audio-on state for that device.", erroneouslyConnectedDevice); 143 // change this to just transition to the new audio on state 144 transitionToActualState(); 145 } 146 cleanupStatesForDisconnectedDevices(); 147 if (mListener != null) { 148 mListener.onBluetoothAudioDisconnected(); 149 } 150 } 151 152 @Override processMessage(Message msg)153 public boolean processMessage(Message msg) { 154 if (msg.what == RUN_RUNNABLE) { 155 ((Runnable) msg.obj).run(); 156 return HANDLED; 157 } 158 159 SomeArgs args = (SomeArgs) msg.obj; 160 try { 161 switch (msg.what) { 162 case NEW_DEVICE_CONNECTED: 163 addDevice((String) args.arg2); 164 break; 165 case LOST_DEVICE: 166 removeDevice((String) args.arg2); 167 break; 168 case CONNECT_BT: 169 String actualAddress; 170 boolean connected; 171 if (mFeatureFlags.resolveSwitchingBtDevicesComputation()) { 172 Pair<String, Boolean> addressInfo = computeAddressToConnectTo( 173 (String) args.arg2, false, null); 174 // See if we need to transition route if the device is already 175 // connected. If connected, another connection will not occur. 176 addressInfo = handleDeviceAlreadyConnected(addressInfo); 177 actualAddress = addressInfo.first; 178 connected = connectBtAudio(actualAddress, 0, 179 false /* switchingBtDevices*/); 180 } else { 181 actualAddress = connectBtAudioLegacy((String) args.arg2, false); 182 connected = actualAddress != null; 183 } 184 185 if (connected) { 186 transitionTo(getConnectingStateForAddress(actualAddress, 187 "AudioOff/CONNECT_BT")); 188 } else { 189 Log.w(LOG_TAG, "Tried to connect to %s but failed to connect to" + 190 " any BT device.", (String) args.arg2); 191 } 192 break; 193 case DISCONNECT_BT: 194 // Ignore. 195 break; 196 case RETRY_BT_CONNECTION: 197 Log.i(LOG_TAG, "Retrying BT connection to %s", (String) args.arg2); 198 String retryAddress; 199 boolean retrySuccessful; 200 if (mFeatureFlags.resolveSwitchingBtDevicesComputation()) { 201 Pair<String, Boolean> retryAddressInfo = computeAddressToConnectTo( 202 (String) args.arg2, false, null); 203 // See if we need to transition route if the device is already 204 // connected. If connected, another connection will not occur. 205 retryAddressInfo = handleDeviceAlreadyConnected(retryAddressInfo); 206 retryAddress = retryAddressInfo.first; 207 retrySuccessful = connectBtAudio(retryAddress, args.argi1, 208 false /* switchingBtDevices*/); 209 } else { 210 retryAddress = connectBtAudioLegacy((String) args.arg2, args.argi1, 211 false /* switchingBtDevices*/); 212 retrySuccessful = retryAddress != null; 213 } 214 215 if (retrySuccessful) { 216 transitionTo(getConnectingStateForAddress(retryAddress, 217 "AudioOff/RETRY_BT_CONNECTION")); 218 } else { 219 Log.i(LOG_TAG, "Retry failed."); 220 } 221 break; 222 case CONNECTION_TIMEOUT: 223 // Ignore. 224 break; 225 case BT_AUDIO_IS_ON: 226 String address = (String) args.arg2; 227 Log.w(LOG_TAG, "BT audio unexpectedly turned on from device %s", address); 228 transitionTo(getConnectedStateForAddress(address, 229 "AudioOff/BT_AUDIO_IS_ON")); 230 break; 231 case BT_AUDIO_LOST: 232 Log.i(LOG_TAG, "Received BT off for device %s while BT off.", 233 (String) args.arg2); 234 mListener.onUnexpectedBluetoothStateChange(); 235 break; 236 case GET_CURRENT_STATE: 237 BlockingQueue<IState> sink = (BlockingQueue<IState>) args.arg3; 238 sink.offer(this); 239 break; 240 } 241 } finally { 242 args.recycle(); 243 } 244 return HANDLED; 245 } 246 } 247 248 private final class AudioConnectingState extends State { 249 private final String mDeviceAddress; 250 AudioConnectingState(String address)251 AudioConnectingState(String address) { 252 mDeviceAddress = address; 253 } 254 255 @Override getName()256 public String getName() { 257 return AUDIO_CONNECTING_STATE_NAME_PREFIX + ":" + mDeviceAddress; 258 } 259 260 @Override enter()261 public void enter() { 262 SomeArgs args = SomeArgs.obtain(); 263 args.arg1 = Log.createSubsession(); 264 sendMessageDelayed(CONNECTION_TIMEOUT, args, 265 mTimeoutsAdapter.getBluetoothPendingTimeoutMillis( 266 mContext.getContentResolver())); 267 mListener.onBluetoothAudioConnecting(); 268 } 269 270 @Override exit()271 public void exit() { 272 removeMessages(CONNECTION_TIMEOUT); 273 } 274 275 @Override processMessage(Message msg)276 public boolean processMessage(Message msg) { 277 if (msg.what == RUN_RUNNABLE) { 278 ((Runnable) msg.obj).run(); 279 return HANDLED; 280 } 281 282 SomeArgs args = (SomeArgs) msg.obj; 283 String address = (String) args.arg2; 284 boolean switchingBtDevices = !Objects.equals(mDeviceAddress, address); 285 286 if (switchingBtDevices) { // check if it is an hearing aid pair 287 BluetoothAdapter bluetoothAdapter = mDeviceManager.getBluetoothAdapter(); 288 if (bluetoothAdapter != null) { 289 List<BluetoothDevice> activeHearingAids = 290 bluetoothAdapter.getActiveDevices(BluetoothProfile.HEARING_AID); 291 for (BluetoothDevice hearingAid : activeHearingAids) { 292 if (hearingAid != null) { 293 String hearingAidAddress = hearingAid.getAddress(); 294 if (hearingAidAddress != null) { 295 if (hearingAidAddress.equals(address) || 296 hearingAidAddress.equals(mDeviceAddress)) { 297 switchingBtDevices = false; 298 break; 299 } 300 } 301 } 302 } 303 } 304 if (mFeatureFlags.resolveSwitchingBtDevicesComputation()) { 305 switchingBtDevices &= (mDeviceAddress != null); 306 } 307 } 308 try { 309 switch (msg.what) { 310 case NEW_DEVICE_CONNECTED: 311 // If the device isn't new, don't bother passing it up. 312 addDevice(address); 313 break; 314 case LOST_DEVICE: 315 removeDevice((String) args.arg2); 316 if (Objects.equals(address, mDeviceAddress)) { 317 transitionToActualState(); 318 } 319 break; 320 case CONNECT_BT: 321 String actualAddress = null; 322 if (mFeatureFlags.resolveSwitchingBtDevicesComputation()) { 323 Pair<String, Boolean> addressInfo = computeAddressToConnectTo(address, 324 switchingBtDevices, mDeviceAddress); 325 // See if we need to transition route if the device is already 326 // connected. If connected, another connection will not occur. 327 addressInfo = handleDeviceAlreadyConnected(addressInfo); 328 actualAddress = addressInfo.first; 329 switchingBtDevices = addressInfo.second; 330 } 331 332 if (!switchingBtDevices) { 333 // Ignore repeated connection attempts to the same device 334 break; 335 } 336 337 if (!mFeatureFlags.resolveSwitchingBtDevicesComputation()) { 338 actualAddress = connectBtAudioLegacy(address, 339 true /* switchingBtDevices*/); 340 } 341 boolean connected = mFeatureFlags.resolveSwitchingBtDevicesComputation() 342 ? connectBtAudio(actualAddress, 0, true /* switchingBtDevices*/) 343 : actualAddress != null; 344 if (connected) { 345 transitionTo(getConnectingStateForAddress(actualAddress, 346 "AudioConnecting/CONNECT_BT")); 347 } else { 348 Log.w(LOG_TAG, "Tried to connect to %s but failed" + 349 " to connect to any BT device.", (String) args.arg2); 350 } 351 break; 352 case DISCONNECT_BT: 353 mDeviceManager.disconnectAudio(); 354 break; 355 case RETRY_BT_CONNECTION: 356 String retryAddress = null; 357 if (mFeatureFlags.resolveSwitchingBtDevicesComputation()) { 358 Pair<String, Boolean> retryAddressInfo = computeAddressToConnectTo( 359 address, switchingBtDevices, mDeviceAddress); 360 // See if we need to transition route if the device is already 361 // connected. If connected, another connection will not occur. 362 retryAddressInfo = handleDeviceAlreadyConnected(retryAddressInfo); 363 retryAddress = retryAddressInfo.first; 364 switchingBtDevices = retryAddressInfo.second; 365 } 366 367 if (!switchingBtDevices) { 368 Log.d(LOG_TAG, "Retry message came through while connecting."); 369 break; 370 } 371 372 if (!mFeatureFlags.resolveSwitchingBtDevicesComputation()) { 373 retryAddress = connectBtAudioLegacy(address, args.argi1, 374 true /* switchingBtDevices*/); 375 } 376 boolean retrySuccessful = mFeatureFlags 377 .resolveSwitchingBtDevicesComputation() 378 ? connectBtAudio(retryAddress, args.argi1, 379 true /* switchingBtDevices*/) 380 : retryAddress != null; 381 if (retrySuccessful) { 382 transitionTo(getConnectingStateForAddress(retryAddress, 383 "AudioConnecting/RETRY_BT_CONNECTION")); 384 } else { 385 Log.i(LOG_TAG, "Retry failed."); 386 } 387 break; 388 case CONNECTION_TIMEOUT: 389 Log.i(LOG_TAG, "Connection with device %s timed out.", 390 mDeviceAddress); 391 transitionToActualState(); 392 break; 393 case BT_AUDIO_IS_ON: 394 if (Objects.equals(mDeviceAddress, address)) { 395 Log.i(LOG_TAG, "BT connection success for device %s.", mDeviceAddress); 396 transitionTo(mAudioConnectedStates.get(mDeviceAddress)); 397 } else { 398 Log.w(LOG_TAG, "In connecting state for device %s but %s" + 399 " is now connected", mDeviceAddress, address); 400 transitionTo(getConnectedStateForAddress(address, 401 "AudioConnecting/BT_AUDIO_IS_ON")); 402 } 403 break; 404 case BT_AUDIO_LOST: 405 if (Objects.equals(mDeviceAddress, address) || address == null) { 406 Log.i(LOG_TAG, "Connection with device %s failed.", 407 mDeviceAddress); 408 transitionToActualState(); 409 } else { 410 Log.w(LOG_TAG, "Got BT lost message for device %s while" + 411 " connecting to %s.", address, mDeviceAddress); 412 mListener.onUnexpectedBluetoothStateChange(); 413 } 414 break; 415 case GET_CURRENT_STATE: 416 BlockingQueue<IState> sink = (BlockingQueue<IState>) args.arg3; 417 sink.offer(this); 418 break; 419 } 420 } finally { 421 args.recycle(); 422 } 423 return HANDLED; 424 } 425 } 426 427 private final class AudioConnectedState extends State { 428 private final String mDeviceAddress; 429 AudioConnectedState(String address)430 AudioConnectedState(String address) { 431 mDeviceAddress = address; 432 } 433 434 @Override getName()435 public String getName() { 436 return AUDIO_CONNECTED_STATE_NAME_PREFIX + ":" + mDeviceAddress; 437 } 438 439 @Override enter()440 public void enter() { 441 // Remove any of the retries that are still in the queue once any device becomes 442 // connected. 443 removeMessages(RETRY_BT_CONNECTION); 444 // Remove and add to ensure that the device is at the top. 445 mMostRecentlyUsedDevices.remove(mDeviceAddress); 446 mMostRecentlyUsedDevices.add(mDeviceAddress); 447 mListener.onBluetoothAudioConnected(); 448 } 449 450 @Override processMessage(Message msg)451 public boolean processMessage(Message msg) { 452 if (msg.what == RUN_RUNNABLE) { 453 ((Runnable) msg.obj).run(); 454 return HANDLED; 455 } 456 457 SomeArgs args = (SomeArgs) msg.obj; 458 String address = (String) args.arg2; 459 boolean switchingBtDevices = !Objects.equals(mDeviceAddress, address); 460 if (mFeatureFlags.resolveSwitchingBtDevicesComputation()) { 461 switchingBtDevices &= (mDeviceAddress != null); 462 } 463 464 try { 465 switch (msg.what) { 466 case NEW_DEVICE_CONNECTED: 467 addDevice(address); 468 break; 469 case LOST_DEVICE: 470 removeDevice((String) args.arg2); 471 if (Objects.equals(address, mDeviceAddress)) { 472 transitionToActualState(); 473 } 474 break; 475 case CONNECT_BT: 476 String actualAddress = null; 477 if (mFeatureFlags.resolveSwitchingBtDevicesComputation()) { 478 Pair<String, Boolean> addressInfo = computeAddressToConnectTo(address, 479 switchingBtDevices, mDeviceAddress); 480 // See if we need to transition route if the device is already 481 // connected. If connected, another connection will not occur. 482 addressInfo = handleDeviceAlreadyConnected(addressInfo); 483 actualAddress = addressInfo.first; 484 switchingBtDevices = addressInfo.second; 485 } 486 487 if (!switchingBtDevices) { 488 // Ignore connection to already connected device but still notify 489 // CallAudioRouteStateMachine since this might be a switch from other 490 // to this already connected BT audio 491 mListener.onBluetoothAudioConnected(); 492 break; 493 } 494 495 if (!mFeatureFlags.resolveSwitchingBtDevicesComputation()) { 496 actualAddress = connectBtAudioLegacy(address, 497 true /* switchingBtDevices*/); 498 } 499 boolean connected = mFeatureFlags.resolveSwitchingBtDevicesComputation() 500 ? connectBtAudio(actualAddress, 0, true /* switchingBtDevices*/) 501 : actualAddress != null; 502 if (connected) { 503 if (mFeatureFlags.useActualAddressToEnterConnectingState()) { 504 transitionTo(getConnectingStateForAddress(actualAddress, 505 "AudioConnected/CONNECT_BT")); 506 } else { 507 transitionTo(getConnectingStateForAddress(address, 508 "AudioConnected/CONNECT_BT")); 509 } 510 } else { 511 Log.w(LOG_TAG, "Tried to connect to %s but failed" + 512 " to connect to any BT device.", (String) args.arg2); 513 } 514 break; 515 case DISCONNECT_BT: 516 mDeviceManager.disconnectAudio(); 517 break; 518 case RETRY_BT_CONNECTION: 519 String retryAddress = null; 520 if (mFeatureFlags.resolveSwitchingBtDevicesComputation()) { 521 Pair<String, Boolean> retryAddressInfo = computeAddressToConnectTo( 522 address, switchingBtDevices, mDeviceAddress); 523 // See if we need to transition route if the device is already 524 // connected. If connected, another connection will not occur. 525 retryAddressInfo = handleDeviceAlreadyConnected(retryAddressInfo); 526 retryAddress = retryAddressInfo.first; 527 switchingBtDevices = retryAddressInfo.second; 528 } 529 530 if (!switchingBtDevices) { 531 Log.d(LOG_TAG, "Retry message came through while connected."); 532 break; 533 } 534 535 if (!mFeatureFlags.resolveSwitchingBtDevicesComputation()) { 536 retryAddress = connectBtAudioLegacy(address, args.argi1, 537 true /* switchingBtDevices*/); 538 } 539 boolean retrySuccessful = mFeatureFlags 540 .resolveSwitchingBtDevicesComputation() 541 ? connectBtAudio(retryAddress, args.argi1, 542 true /* switchingBtDevices*/) 543 : retryAddress != null; 544 if (retrySuccessful) { 545 transitionTo(getConnectingStateForAddress(retryAddress, 546 "AudioConnected/RETRY_BT_CONNECTION")); 547 } else { 548 Log.i(LOG_TAG, "Retry failed."); 549 } 550 break; 551 case CONNECTION_TIMEOUT: 552 Log.w(LOG_TAG, "Received CONNECTION_TIMEOUT while connected."); 553 break; 554 case BT_AUDIO_IS_ON: 555 if (Objects.equals(mDeviceAddress, address)) { 556 Log.i(LOG_TAG, 557 "Received redundant BT_AUDIO_IS_ON for %s", mDeviceAddress); 558 } else { 559 Log.w(LOG_TAG, "In connected state for device %s but %s" + 560 " is now connected", mDeviceAddress, address); 561 transitionTo(getConnectedStateForAddress(address, 562 "AudioConnected/BT_AUDIO_IS_ON")); 563 } 564 break; 565 case BT_AUDIO_LOST: 566 if (Objects.equals(mDeviceAddress, address) || address == null) { 567 Log.i(LOG_TAG, "BT connection with device %s lost.", mDeviceAddress); 568 transitionToActualState(); 569 } else { 570 Log.w(LOG_TAG, "Got BT lost message for device %s while" + 571 " connected to %s.", address, mDeviceAddress); 572 mListener.onUnexpectedBluetoothStateChange(); 573 } 574 break; 575 case GET_CURRENT_STATE: 576 BlockingQueue<IState> sink = (BlockingQueue<IState>) args.arg3; 577 sink.offer(this); 578 break; 579 } 580 } finally { 581 args.recycle(); 582 } 583 return HANDLED; 584 } 585 } 586 587 private final State mAudioOffState; 588 private final Map<String, AudioConnectingState> mAudioConnectingStates = new HashMap<>(); 589 private final Map<String, AudioConnectedState> mAudioConnectedStates = new HashMap<>(); 590 private final Set<State> statesToCleanUp = new HashSet<>(); 591 private final LinkedHashSet<String> mMostRecentlyUsedDevices = new LinkedHashSet<>(); 592 593 private final TelecomSystem.SyncRoot mLock; 594 private final Context mContext; 595 private final Timeouts.Adapter mTimeoutsAdapter; 596 597 private BluetoothStateListener mListener; 598 private BluetoothDeviceManager mDeviceManager; 599 // Tracks the active devices in the BT stack (HFP or hearing aid or le audio). 600 private BluetoothDevice mHfpActiveDeviceCache = null; 601 private BluetoothDevice mHearingAidActiveDeviceCache = null; 602 private BluetoothDevice mLeAudioActiveDeviceCache = null; 603 private BluetoothDevice mMostRecentlyReportedActiveDevice = null; 604 private CallAudioCommunicationDeviceTracker mCommunicationDeviceTracker; 605 private FeatureFlags mFeatureFlags; 606 BluetoothRouteManager(Context context, TelecomSystem.SyncRoot lock, BluetoothDeviceManager deviceManager, Timeouts.Adapter timeoutsAdapter, CallAudioCommunicationDeviceTracker communicationDeviceTracker, FeatureFlags featureFlags)607 public BluetoothRouteManager(Context context, TelecomSystem.SyncRoot lock, 608 BluetoothDeviceManager deviceManager, Timeouts.Adapter timeoutsAdapter, 609 CallAudioCommunicationDeviceTracker communicationDeviceTracker, 610 FeatureFlags featureFlags) { 611 super(BluetoothRouteManager.class.getSimpleName()); 612 mContext = context; 613 mLock = lock; 614 mDeviceManager = deviceManager; 615 mDeviceManager.setBluetoothRouteManager(this); 616 mTimeoutsAdapter = timeoutsAdapter; 617 mCommunicationDeviceTracker = communicationDeviceTracker; 618 mFeatureFlags = featureFlags; 619 620 mAudioOffState = new AudioOffState(); 621 addState(mAudioOffState); 622 setInitialState(mAudioOffState); 623 start(); 624 } 625 626 @Override onPreHandleMessage(Message msg)627 protected void onPreHandleMessage(Message msg) { 628 if (msg.obj != null && msg.obj instanceof SomeArgs) { 629 SomeArgs args = (SomeArgs) msg.obj; 630 631 Log.continueSession(((Session) args.arg1), "BRM.pM_" + msg.what); 632 Log.i(LOG_TAG, "%s received message: %s.", this, 633 MESSAGE_CODE_TO_NAME.get(msg.what)); 634 } else if (msg.what == RUN_RUNNABLE && msg.obj instanceof Runnable) { 635 Log.i(LOG_TAG, "Running runnable for testing"); 636 } else { 637 Log.w(LOG_TAG, "Message sent must be of type nonnull SomeArgs, but got " + 638 (msg.obj == null ? "null" : msg.obj.getClass().getSimpleName())); 639 Log.w(LOG_TAG, "The message was of code %d = %s", 640 msg.what, MESSAGE_CODE_TO_NAME.get(msg.what)); 641 } 642 } 643 644 @Override onPostHandleMessage(Message msg)645 protected void onPostHandleMessage(Message msg) { 646 Log.endSession(); 647 } 648 649 /** 650 * Returns whether there is a BT device available to route audio to. 651 * @return true if there is a device, false otherwise. 652 */ isBluetoothAvailable()653 public boolean isBluetoothAvailable() { 654 return mDeviceManager.getNumConnectedDevices() > 0; 655 } 656 657 /** 658 * This method needs be synchronized with the local looper because getCurrentState() depends 659 * on the internal state of the state machine being consistent. Therefore, there may be a 660 * delay when calling this method. 661 * @return 662 */ isBluetoothAudioConnectedOrPending()663 public boolean isBluetoothAudioConnectedOrPending() { 664 SomeArgs args = SomeArgs.obtain(); 665 args.arg1 = Log.createSubsession(); 666 BlockingQueue<IState> stateQueue = new LinkedBlockingQueue<>(); 667 // Use arg3 because arg2 is reserved for the device address 668 args.arg3 = stateQueue; 669 sendMessage(GET_CURRENT_STATE, args); 670 671 try { 672 IState currentState = stateQueue.poll(GET_STATE_TIMEOUT, TimeUnit.MILLISECONDS); 673 if (currentState == null) { 674 Log.w(LOG_TAG, "Failed to get a state from the state machine in time -- Handler " + 675 "stuck?"); 676 return false; 677 } 678 return currentState != mAudioOffState; 679 } catch (InterruptedException e) { 680 Log.w(LOG_TAG, "isBluetoothAudioConnectedOrPending -- interrupted getting state"); 681 return false; 682 } 683 } 684 685 /** 686 * Attempts to connect to Bluetooth audio. If the first connection attempt synchronously 687 * fails, schedules a retry at a later time. 688 * @param address The MAC address of the bluetooth device to connect to. If null, the most 689 * recently used device will be used. 690 */ connectBluetoothAudio(String address)691 public void connectBluetoothAudio(String address) { 692 SomeArgs args = SomeArgs.obtain(); 693 args.arg1 = Log.createSubsession(); 694 args.arg2 = address; 695 sendMessage(CONNECT_BT, args); 696 } 697 698 /** 699 * Disconnects Bluetooth audio. 700 */ disconnectBluetoothAudio()701 public void disconnectBluetoothAudio() { 702 SomeArgs args = SomeArgs.obtain(); 703 args.arg1 = Log.createSubsession(); 704 sendMessage(DISCONNECT_BT, args); 705 } 706 disconnectAudio()707 public void disconnectAudio() { 708 mDeviceManager.disconnectAudio(); 709 } 710 cacheHearingAidDevice()711 public void cacheHearingAidDevice() { 712 mDeviceManager.cacheHearingAidDevice(); 713 } 714 restoreHearingAidDevice()715 public void restoreHearingAidDevice() { 716 mDeviceManager.restoreHearingAidDevice(); 717 } 718 setListener(BluetoothStateListener listener)719 public void setListener(BluetoothStateListener listener) { 720 mListener = listener; 721 } 722 onDeviceAdded(String newDeviceAddress)723 public void onDeviceAdded(String newDeviceAddress) { 724 SomeArgs args = SomeArgs.obtain(); 725 args.arg1 = Log.createSubsession(); 726 args.arg2 = newDeviceAddress; 727 sendMessage(NEW_DEVICE_CONNECTED, args); 728 729 mListener.onBluetoothDeviceListChanged(); 730 } 731 onDeviceLost(String lostDeviceAddress)732 public void onDeviceLost(String lostDeviceAddress) { 733 SomeArgs args = SomeArgs.obtain(); 734 args.arg1 = Log.createSubsession(); 735 args.arg2 = lostDeviceAddress; 736 sendMessage(LOST_DEVICE, args); 737 738 mListener.onBluetoothDeviceListChanged(); 739 } 740 onAudioOn(String address)741 public void onAudioOn(String address) { 742 Session session = Log.createSubsession(); 743 SomeArgs args = SomeArgs.obtain(); 744 args.arg1 = session; 745 args.arg2 = address; 746 sendMessage(BT_AUDIO_IS_ON, args); 747 } 748 onAudioLost(String address)749 public void onAudioLost(String address) { 750 Session session = Log.createSubsession(); 751 SomeArgs args = SomeArgs.obtain(); 752 args.arg1 = session; 753 args.arg2 = address; 754 sendMessage(BT_AUDIO_LOST, args); 755 } 756 onActiveDeviceChanged(BluetoothDevice device, int deviceType)757 public void onActiveDeviceChanged(BluetoothDevice device, int deviceType) { 758 boolean wasActiveDevicePresent = hasBtActiveDevice(); 759 if (deviceType == BluetoothDeviceManager.DEVICE_TYPE_LE_AUDIO) { 760 mLeAudioActiveDeviceCache = device; 761 if (device == null) { 762 if (mFeatureFlags.callAudioCommunicationDeviceRefactor()) { 763 mCommunicationDeviceTracker.clearCommunicationDevice( 764 AudioDeviceInfo.TYPE_BLE_HEADSET); 765 } else { 766 mDeviceManager.clearLeAudioCommunicationDevice(); 767 } 768 } 769 } else if (deviceType == BluetoothDeviceManager.DEVICE_TYPE_HEARING_AID) { 770 mHearingAidActiveDeviceCache = device; 771 if (device == null) { 772 if (mFeatureFlags.callAudioCommunicationDeviceRefactor()) { 773 mCommunicationDeviceTracker.clearCommunicationDevice( 774 AudioDeviceInfo.TYPE_HEARING_AID); 775 } else { 776 mDeviceManager.clearHearingAidCommunicationDevice(); 777 } 778 } 779 } else if (deviceType == BluetoothDeviceManager.DEVICE_TYPE_HEADSET) { 780 mHfpActiveDeviceCache = device; 781 } else { 782 return; 783 } 784 785 if (device != null) mMostRecentlyReportedActiveDevice = device; 786 787 boolean isActiveDevicePresent = hasBtActiveDevice(); 788 789 if (wasActiveDevicePresent && !isActiveDevicePresent) { 790 mListener.onBluetoothActiveDeviceGone(); 791 } else if (!wasActiveDevicePresent && isActiveDevicePresent) { 792 mListener.onBluetoothActiveDevicePresent(); 793 } 794 } 795 getMostRecentlyReportedActiveDevice()796 public BluetoothDevice getMostRecentlyReportedActiveDevice() { 797 return mMostRecentlyReportedActiveDevice; 798 } 799 hasBtActiveDevice()800 public boolean hasBtActiveDevice() { 801 return mLeAudioActiveDeviceCache != null || 802 mHearingAidActiveDeviceCache != null || 803 mHfpActiveDeviceCache != null; 804 } 805 isCachedLeAudioDevice(BluetoothDevice device)806 public boolean isCachedLeAudioDevice(BluetoothDevice device) { 807 return mLeAudioActiveDeviceCache != null && mLeAudioActiveDeviceCache.equals(device); 808 } 809 isCachedHearingAidDevice(BluetoothDevice device)810 public boolean isCachedHearingAidDevice(BluetoothDevice device) { 811 return mHearingAidActiveDeviceCache != null && mHearingAidActiveDeviceCache.equals(device); 812 } 813 getConnectedDevices()814 public Collection<BluetoothDevice> getConnectedDevices() { 815 return mDeviceManager.getUniqueConnectedDevices(); 816 } 817 isWatch(BluetoothDevice device)818 public boolean isWatch(BluetoothDevice device) { 819 if (device == null) { 820 Log.i(this, "isWatch: device is null. Returning false"); 821 return false; 822 } 823 824 BluetoothClass deviceClass = device.getBluetoothClass(); 825 if (deviceClass != null && deviceClass.getDeviceClass() 826 == BluetoothClass.Device.WEARABLE_WRIST_WATCH) { 827 Log.i(this, "isWatch: bluetooth class component is a WEARABLE_WRIST_WATCH."); 828 return true; 829 } 830 831 // Check metadata 832 byte[] deviceType = device.getMetadata(BluetoothDevice.METADATA_DEVICE_TYPE); 833 if (deviceType == null) { 834 return false; 835 } 836 String deviceTypeStr = new String(deviceType); 837 if (deviceTypeStr.equals(BluetoothDevice.DEVICE_TYPE_WATCH)) { 838 Log.i(this, "isWatch: bluetooth device type is DEVICE_TYPE_WATCH."); 839 return true; 840 } 841 842 return false; 843 } 844 845 /** 846 * Determines the address that should be used for the connection attempt. In the case that the 847 * specified address to be used is null, Telecom will try to find an arbitrary address to 848 * connect instead. 849 * 850 * @param address The address that should be prioritized for the connection attempt 851 * @param switchingBtDevices Used when there is existing audio connection to other Bt device. 852 * @param stateAddress The address stored in the state that indicates the connecting/connected 853 * device. 854 * @return {@link Pair} containing the address to connect to and whether an existing BT audio 855 * connection for a different device exists. 856 */ computeAddressToConnectTo( String address, boolean switchingBtDevices, String stateAddress)857 private Pair<String, Boolean> computeAddressToConnectTo( 858 String address, boolean switchingBtDevices, String stateAddress) { 859 Collection<BluetoothDevice> deviceList = mDeviceManager.getConnectedDevices(); 860 Optional<BluetoothDevice> matchingDevice = deviceList.stream() 861 .filter(d -> Objects.equals(d.getAddress(), address)) 862 .findAny(); 863 864 String actualAddress = matchingDevice.isPresent() 865 ? address : getActiveDeviceAddress(); 866 if (actualAddress == null) { 867 Log.i(this, "No device specified and BT stack has no active device." 868 + " Using arbitrary device - except watch"); 869 if (deviceList.size() > 0) { 870 for (BluetoothDevice device : deviceList) { 871 if (mFeatureFlags.ignoreAutoRouteToWatchDevice() && isWatch(device)) { 872 Log.i(this, "Skipping a watch device: " + device); 873 continue; 874 } 875 actualAddress = device.getAddress(); 876 break; 877 } 878 } 879 880 if (actualAddress == null) { 881 Log.i(this, "No devices available at all. Not connecting."); 882 return new Pair<>(null, false); 883 } 884 if (switchingBtDevices && actualAddress.equals(stateAddress)) { 885 switchingBtDevices = false; 886 } 887 } 888 if (!matchingDevice.isPresent()) { 889 Log.i(this, "No device with address %s available. Using %s instead.", 890 address, actualAddress); 891 } 892 return new Pair<>(actualAddress, switchingBtDevices); 893 } 894 895 /** 896 * Handles route switching to the connected state for a device. This currently handles the case 897 * for hearing aids when the route manager reports AudioOff since Telecom doesn't treat HA as 898 * the active device outside of a call. 899 * 900 * @param addressInfo A {@link Pair} containing the BT address to connect to as well as if we're 901 * handling a switch of BT devices. 902 * @return {@link Pair} indicating the address to connect to as well as if we're handling a 903 * switch of BT devices. If the device is already connected, then the 904 * return value will be {null, false} to indicate that a connection attempt 905 * is not required. 906 */ handleDeviceAlreadyConnected(Pair<String, Boolean> addressInfo)907 private Pair<String, Boolean> handleDeviceAlreadyConnected(Pair<String, Boolean> addressInfo) { 908 String address = addressInfo.first; 909 BluetoothDevice alreadyConnectedDevice = getBluetoothAudioConnectedDevice(); 910 if (alreadyConnectedDevice != null && alreadyConnectedDevice.getAddress().equals( 911 address)) { 912 Log.i(this, "trying to connect to already connected device -- skipping connection" 913 + " and going into the actual connected state."); 914 transitionToActualState(); 915 return new Pair<>(null, false); 916 } 917 return addressInfo; 918 } 919 920 /** 921 * Initiates a connection to the BT address specified. 922 * Note: This method is not synchronized on the Telecom lock, so don't try and call back into 923 * Telecom from within it. 924 * @param address The address that should be tried first. May be null. 925 * @param retryCount The number of times this connection attempt has been retried. 926 * @param switchingBtDevices Used when there is existing audio connection to other Bt device. 927 * @return {@code true} if the connection to the address was successful, otherwise {@code false} 928 * if the connection fails. 929 * 930 * Note: This should only be used in par with the resolveSwitchingBtDevicesComputation flag. 931 */ connectBtAudio(String address, int retryCount, boolean switchingBtDevices)932 private boolean connectBtAudio(String address, int retryCount, boolean switchingBtDevices) { 933 if (address == null) { 934 return false; 935 } 936 937 if (switchingBtDevices) { 938 /* When new Bluetooth connects audio, make sure previous one has disconnected audio. */ 939 mDeviceManager.disconnectAudio(); 940 } 941 942 if (!mDeviceManager.connectAudio(address, switchingBtDevices)) { 943 boolean shouldRetry = retryCount < MAX_CONNECTION_RETRIES; 944 Log.w(LOG_TAG, "Could not connect to %s. Will %s", address, 945 shouldRetry ? "retry" : "not retry"); 946 if (shouldRetry) { 947 SomeArgs args = SomeArgs.obtain(); 948 args.arg1 = Log.createSubsession(); 949 args.arg2 = address; 950 args.argi1 = retryCount + 1; 951 sendMessageDelayed(RETRY_BT_CONNECTION, args, 952 mTimeoutsAdapter.getRetryBluetoothConnectAudioBackoffMillis( 953 mContext.getContentResolver())); 954 } 955 return false; 956 } 957 958 return true; 959 } 960 961 private String connectBtAudioLegacy(String address, boolean switchingBtDevices) { 962 return connectBtAudioLegacy(address, 0, switchingBtDevices); 963 } 964 965 /** 966 * Initiates a connection to the BT address specified. 967 * Note: This method is not synchronized on the Telecom lock, so don't try and call back into 968 * Telecom from within it. 969 * @param address The address that should be tried first. May be null. 970 * @param retryCount The number of times this connection attempt has been retried. 971 * @param switchingBtDevices Used when there is existing audio connection to other Bt device. 972 * @return The address of the device that's actually being connected to, or null if no 973 * connection was successful. 974 */ 975 private String connectBtAudioLegacy(String address, int retryCount, 976 boolean switchingBtDevices) { 977 Collection<BluetoothDevice> deviceList = mDeviceManager.getConnectedDevices(); 978 Optional<BluetoothDevice> matchingDevice = deviceList.stream() 979 .filter(d -> Objects.equals(d.getAddress(), address)) 980 .findAny(); 981 982 if (switchingBtDevices) { 983 /* When new Bluetooth connects audio, make sure previous one has disconnected audio. */ 984 mDeviceManager.disconnectAudio(); 985 } 986 987 String actualAddress = matchingDevice.isPresent() 988 ? address : getActiveDeviceAddress(); 989 if (actualAddress == null) { 990 Log.i(this, "No device specified and BT stack has no active device." 991 + " Using arbitrary device - except watch"); 992 if (deviceList.size() > 0) { 993 for (BluetoothDevice device : deviceList) { 994 if (mFeatureFlags.ignoreAutoRouteToWatchDevice() && isWatch(device)) { 995 Log.i(this, "Skipping a watch device: " + device); 996 continue; 997 } 998 actualAddress = device.getAddress(); 999 break; 1000 } 1001 } 1002 1003 if (actualAddress == null) { 1004 Log.i(this, "No devices available at all. Not connecting."); 1005 return null; 1006 } 1007 } 1008 if (!matchingDevice.isPresent()) { 1009 Log.i(this, "No device with address %s available. Using %s instead.", 1010 address, actualAddress); 1011 } 1012 1013 BluetoothDevice alreadyConnectedDevice = getBluetoothAudioConnectedDevice(); 1014 if (alreadyConnectedDevice != null && alreadyConnectedDevice.getAddress().equals( 1015 actualAddress)) { 1016 Log.i(this, "trying to connect to already connected device -- skipping connection" 1017 + " and going into the actual connected state."); 1018 transitionToActualState(); 1019 return null; 1020 } 1021 1022 if (!mDeviceManager.connectAudio(actualAddress, switchingBtDevices)) { 1023 boolean shouldRetry = retryCount < MAX_CONNECTION_RETRIES; 1024 Log.w(LOG_TAG, "Could not connect to %s. Will %s", actualAddress, 1025 shouldRetry ? "retry" : "not retry"); 1026 if (shouldRetry) { 1027 SomeArgs args = SomeArgs.obtain(); 1028 args.arg1 = Log.createSubsession(); 1029 args.arg2 = actualAddress; 1030 args.argi1 = retryCount + 1; 1031 sendMessageDelayed(RETRY_BT_CONNECTION, args, 1032 mTimeoutsAdapter.getRetryBluetoothConnectAudioBackoffMillis( 1033 mContext.getContentResolver())); 1034 } 1035 return null; 1036 } 1037 1038 return actualAddress; 1039 } 1040 1041 private String getActiveDeviceAddress() { 1042 if (mHfpActiveDeviceCache != null) { 1043 return mHfpActiveDeviceCache.getAddress(); 1044 } 1045 if (mHearingAidActiveDeviceCache != null) { 1046 return mHearingAidActiveDeviceCache.getAddress(); 1047 } 1048 if (mLeAudioActiveDeviceCache != null) { 1049 return mLeAudioActiveDeviceCache.getAddress(); 1050 } 1051 return null; 1052 } 1053 1054 private void transitionToActualState() { 1055 BluetoothDevice possiblyAlreadyConnectedDevice = getBluetoothAudioConnectedDevice(); 1056 if (possiblyAlreadyConnectedDevice != null) { 1057 Log.i(LOG_TAG, "Device %s is already connected; going to AudioConnected.", 1058 possiblyAlreadyConnectedDevice); 1059 transitionTo(getConnectedStateForAddress( 1060 possiblyAlreadyConnectedDevice.getAddress(), "transitionToActualState")); 1061 } else { 1062 transitionTo(mAudioOffState); 1063 } 1064 } 1065 1066 /** 1067 * @return The BluetoothDevice that is connected to BT audio, null if none are connected. 1068 */ 1069 @VisibleForTesting 1070 public BluetoothDevice getBluetoothAudioConnectedDevice() { 1071 BluetoothAdapter bluetoothAdapter = mDeviceManager.getBluetoothAdapter(); 1072 BluetoothHeadset bluetoothHeadset = mDeviceManager.getBluetoothHeadset(); 1073 BluetoothHearingAid bluetoothHearingAid = mDeviceManager.getBluetoothHearingAid(); 1074 BluetoothLeAudio bluetoothLeAudio = mDeviceManager.getLeAudioService(); 1075 1076 BluetoothDevice hfpAudioOnDevice = null; 1077 BluetoothDevice hearingAidActiveDevice = null; 1078 BluetoothDevice leAudioActiveDevice = null; 1079 1080 if (bluetoothAdapter == null) { 1081 Log.i(this, "getBluetoothAudioConnectedDevice: no adapter available."); 1082 return null; 1083 } 1084 if (bluetoothHeadset == null && bluetoothHearingAid == null && bluetoothLeAudio == null) { 1085 Log.i(this, "getBluetoothAudioConnectedDevice: no service available."); 1086 return null; 1087 } 1088 1089 int activeDevices = 0; 1090 if (bluetoothHeadset != null) { 1091 for (BluetoothDevice device : bluetoothAdapter.getActiveDevices( 1092 BluetoothProfile.HEADSET)) { 1093 hfpAudioOnDevice = device; 1094 break; 1095 } 1096 1097 if (hfpAudioOnDevice != null && bluetoothHeadset.getAudioState(hfpAudioOnDevice) 1098 == BluetoothHeadset.STATE_AUDIO_DISCONNECTED) { 1099 hfpAudioOnDevice = null; 1100 } else { 1101 activeDevices++; 1102 } 1103 } 1104 1105 boolean isHearingAidSetForCommunication = 1106 mFeatureFlags.callAudioCommunicationDeviceRefactor() 1107 ? mCommunicationDeviceTracker.isAudioDeviceSetForType( 1108 AudioDeviceInfo.TYPE_HEARING_AID) 1109 : mDeviceManager.isHearingAidSetAsCommunicationDevice(); 1110 if (bluetoothHearingAid != null) { 1111 if (isHearingAidSetForCommunication) { 1112 List<BluetoothDevice> hearingAidsActiveDevices = bluetoothAdapter.getActiveDevices( 1113 BluetoothProfile.HEARING_AID); 1114 if (hearingAidsActiveDevices.contains(mHearingAidActiveDeviceCache)) { 1115 hearingAidActiveDevice = mHearingAidActiveDeviceCache; 1116 activeDevices++; 1117 } else { 1118 for (BluetoothDevice device : hearingAidsActiveDevices) { 1119 if (device != null) { 1120 hearingAidActiveDevice = device; 1121 activeDevices++; 1122 break; 1123 } 1124 } 1125 } 1126 } 1127 } 1128 1129 boolean isLeAudioSetForCommunication = 1130 mFeatureFlags.callAudioCommunicationDeviceRefactor() 1131 ? mCommunicationDeviceTracker.isAudioDeviceSetForType( 1132 AudioDeviceInfo.TYPE_BLE_HEADSET) 1133 : mDeviceManager.isLeAudioCommunicationDevice(); 1134 if (bluetoothLeAudio != null) { 1135 if (isLeAudioSetForCommunication) { 1136 for (BluetoothDevice device : bluetoothAdapter.getActiveDevices( 1137 BluetoothProfile.LE_AUDIO)) { 1138 if (device != null) { 1139 leAudioActiveDevice = device; 1140 activeDevices++; 1141 break; 1142 } 1143 } 1144 } 1145 } 1146 1147 // Return the active device reported by either HFP, hearing aid or le audio. If more than 1148 // one is reporting active devices, go with the most recent one as reported by the receiver. 1149 if (activeDevices > 1) { 1150 Log.i(this, "More than one profile reporting active devices. Going with the most" 1151 + " recently reported active device: %s", mMostRecentlyReportedActiveDevice); 1152 return mMostRecentlyReportedActiveDevice; 1153 } 1154 1155 if (leAudioActiveDevice != null) { 1156 return leAudioActiveDevice; 1157 } 1158 1159 if (hearingAidActiveDevice != null) { 1160 return hearingAidActiveDevice; 1161 } 1162 1163 return hfpAudioOnDevice; 1164 } 1165 1166 /** 1167 * Check if in-band ringing is currently enabled. In-band ringing could be disabled during an 1168 * active connection. 1169 * 1170 * @return true if in-band ringing is enabled, false if in-band ringing is disabled 1171 */ 1172 @VisibleForTesting isInbandRingingEnabled()1173 public boolean isInbandRingingEnabled() { 1174 return mDeviceManager.isInbandRingingEnabled(); 1175 } 1176 1177 @VisibleForTesting isInbandRingEnabled(BluetoothDevice bluetoothDevice)1178 public boolean isInbandRingEnabled(BluetoothDevice bluetoothDevice) { 1179 return mDeviceManager.isInbandRingEnabled(bluetoothDevice); 1180 } 1181 addDevice(String address)1182 private boolean addDevice(String address) { 1183 if (mAudioConnectingStates.containsKey(address)) { 1184 Log.i(this, "Attempting to add device %s twice.", address); 1185 return false; 1186 } 1187 AudioConnectedState audioConnectedState = new AudioConnectedState(address); 1188 AudioConnectingState audioConnectingState = new AudioConnectingState(address); 1189 mAudioConnectingStates.put(address, audioConnectingState); 1190 mAudioConnectedStates.put(address, audioConnectedState); 1191 addState(audioConnectedState); 1192 addState(audioConnectingState); 1193 return true; 1194 } 1195 removeDevice(String address)1196 private boolean removeDevice(String address) { 1197 if (!mAudioConnectingStates.containsKey(address)) { 1198 Log.i(this, "Attempting to remove already-removed device %s", address); 1199 return false; 1200 } 1201 statesToCleanUp.add(mAudioConnectingStates.remove(address)); 1202 statesToCleanUp.add(mAudioConnectedStates.remove(address)); 1203 mMostRecentlyUsedDevices.remove(address); 1204 return true; 1205 } 1206 getConnectingStateForAddress(String address, String error)1207 private AudioConnectingState getConnectingStateForAddress(String address, String error) { 1208 if (!mAudioConnectingStates.containsKey(address)) { 1209 Log.w(LOG_TAG, "Device being connected to does not have a corresponding state: %s", 1210 error); 1211 addDevice(address); 1212 } 1213 return mAudioConnectingStates.get(address); 1214 } 1215 getConnectedStateForAddress(String address, String error)1216 private AudioConnectedState getConnectedStateForAddress(String address, String error) { 1217 if (!mAudioConnectedStates.containsKey(address)) { 1218 Log.w(LOG_TAG, "Device already connected to does" + 1219 " not have a corresponding state: %s", error); 1220 addDevice(address); 1221 } 1222 return mAudioConnectedStates.get(address); 1223 } 1224 1225 /** 1226 * Removes the states for disconnected devices from the state machine. Called when entering 1227 * AudioOff so that none of the states-to-be-removed are active. 1228 */ cleanupStatesForDisconnectedDevices()1229 private void cleanupStatesForDisconnectedDevices() { 1230 for (State state : statesToCleanUp) { 1231 if (state != null) { 1232 removeState(state); 1233 } 1234 } 1235 statesToCleanUp.clear(); 1236 } 1237 1238 @VisibleForTesting setInitialStateForTesting(String stateName, BluetoothDevice device)1239 public void setInitialStateForTesting(String stateName, BluetoothDevice device) { 1240 sendMessage(RUN_RUNNABLE, (Runnable) () -> { 1241 switch (stateName) { 1242 case AUDIO_OFF_STATE_NAME: 1243 transitionTo(mAudioOffState); 1244 break; 1245 case AUDIO_CONNECTING_STATE_NAME_PREFIX: 1246 transitionTo(getConnectingStateForAddress(device.getAddress(), 1247 "setInitialStateForTesting")); 1248 break; 1249 case AUDIO_CONNECTED_STATE_NAME_PREFIX: 1250 transitionTo(getConnectedStateForAddress(device.getAddress(), 1251 "setInitialStateForTesting")); 1252 break; 1253 } 1254 Log.i(LOG_TAG, "transition for testing done: %s", stateName); 1255 }); 1256 } 1257 1258 @VisibleForTesting setActiveDeviceCacheForTesting(BluetoothDevice device, int deviceType)1259 public void setActiveDeviceCacheForTesting(BluetoothDevice device, int deviceType) { 1260 if (deviceType == BluetoothDeviceManager.DEVICE_TYPE_LE_AUDIO) { 1261 mLeAudioActiveDeviceCache = device; 1262 } else if (deviceType == BluetoothDeviceManager.DEVICE_TYPE_HEARING_AID) { 1263 mHearingAidActiveDeviceCache = device; 1264 } else if (deviceType == BluetoothDeviceManager.DEVICE_TYPE_HEADSET) { 1265 mHfpActiveDeviceCache = device; 1266 } 1267 } 1268 getDeviceManager()1269 public BluetoothDeviceManager getDeviceManager() { 1270 return mDeviceManager; 1271 } 1272 } 1273