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