1 /* 2 * Copyright (C) 2010 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 android.bluetooth; 18 19 import android.content.BroadcastReceiver; 20 import android.content.Context; 21 import android.content.Intent; 22 import android.content.IntentFilter; 23 import android.os.Message; 24 import android.os.PowerManager; 25 import android.server.BluetoothA2dpService; 26 import android.server.BluetoothService; 27 import android.util.Log; 28 import android.util.Pair; 29 30 import com.android.internal.util.HierarchicalState; 31 import com.android.internal.util.HierarchicalStateMachine; 32 33 /** 34 * This class is the Profile connection state machine associated with a remote 35 * device. When the device bonds an instance of this class is created. 36 * This tracks incoming and outgoing connections of all the profiles. Incoming 37 * connections are preferred over outgoing connections and HFP preferred over 38 * A2DP. When the device is unbonded, the instance is removed. 39 * 40 * States: 41 * {@link BondedDevice}: This state represents a bonded device. When in this 42 * state none of the profiles are in transition states. 43 * 44 * {@link OutgoingHandsfree}: Handsfree profile connection is in a transition 45 * state because of a outgoing Connect or Disconnect. 46 * 47 * {@link IncomingHandsfree}: Handsfree profile connection is in a transition 48 * state because of a incoming Connect or Disconnect. 49 * 50 * {@link IncomingA2dp}: A2dp profile connection is in a transition 51 * state because of a incoming Connect or Disconnect. 52 * 53 * {@link OutgoingA2dp}: A2dp profile connection is in a transition 54 * state because of a outgoing Connect or Disconnect. 55 * 56 * Todo(): Write tests for this class, when the Android Mock support is completed. 57 * @hide 58 */ 59 public final class BluetoothDeviceProfileState extends HierarchicalStateMachine { 60 private static final String TAG = "BluetoothDeviceProfileState"; 61 private static final boolean DBG = false; 62 63 public static final int CONNECT_HFP_OUTGOING = 1; 64 public static final int CONNECT_HFP_INCOMING = 2; 65 public static final int CONNECT_A2DP_OUTGOING = 3; 66 public static final int CONNECT_A2DP_INCOMING = 4; 67 68 public static final int DISCONNECT_HFP_OUTGOING = 5; 69 private static final int DISCONNECT_HFP_INCOMING = 6; 70 public static final int DISCONNECT_A2DP_OUTGOING = 7; 71 public static final int DISCONNECT_A2DP_INCOMING = 8; 72 public static final int DISCONNECT_PBAP_OUTGOING = 9; 73 74 public static final int UNPAIR = 100; 75 public static final int AUTO_CONNECT_PROFILES = 101; 76 public static final int TRANSITION_TO_STABLE = 102; 77 public static final int CONNECT_OTHER_PROFILES = 103; 78 private static final int CONNECTION_ACCESS_REQUEST_REPLY = 104; 79 private static final int CONNECTION_ACCESS_REQUEST_EXPIRY = 105; 80 81 private static final int AUTO_CONNECT_DELAY = 6000; // 6 secs 82 private static final int CONNECT_OTHER_PROFILES_DELAY = 4000; // 4 secs 83 private static final int CONNECTION_ACCESS_REQUEST_EXPIRY_TIMEOUT = 7000; // 7 secs 84 private static final int CONNECTION_ACCESS_UNDEFINED = -1; 85 private static final long INIT_INCOMING_REJECT_TIMER = 1000; // 1 sec 86 private static final long MAX_INCOMING_REJECT_TIMER = 3600 * 1000 * 4; // 4 hours 87 88 private static final String PREFS_NAME = "ConnectionAccess"; 89 90 private BondedDevice mBondedDevice = new BondedDevice(); 91 private OutgoingHandsfree mOutgoingHandsfree = new OutgoingHandsfree(); 92 private IncomingHandsfree mIncomingHandsfree = new IncomingHandsfree(); 93 private IncomingA2dp mIncomingA2dp = new IncomingA2dp(); 94 private OutgoingA2dp mOutgoingA2dp = new OutgoingA2dp(); 95 96 private Context mContext; 97 private BluetoothService mService; 98 private BluetoothA2dpService mA2dpService; 99 private BluetoothHeadset mHeadsetService; 100 private BluetoothPbap mPbapService; 101 private boolean mHeadsetServiceConnected; 102 private boolean mPbapServiceConnected; 103 private static final String BLUETOOTH_ADMIN_PERM = android.Manifest.permission.BLUETOOTH_ADMIN; 104 105 private BluetoothDevice mDevice; 106 private int mHeadsetState; 107 private int mA2dpState; 108 private long mIncomingRejectTimer; 109 private boolean mConnectionAccessReplyReceived = false; 110 private Pair<Integer, String> mIncomingConnections; 111 private PowerManager.WakeLock mWakeLock; 112 private PowerManager mPowerManager; 113 114 private BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() { 115 @Override 116 public void onReceive(Context context, Intent intent) { 117 String action = intent.getAction(); 118 BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE); 119 if (!device.equals(mDevice)) return; 120 121 if (action.equals(BluetoothHeadset.ACTION_STATE_CHANGED)) { 122 int newState = intent.getIntExtra(BluetoothHeadset.EXTRA_STATE, 0); 123 int oldState = intent.getIntExtra(BluetoothHeadset.EXTRA_PREVIOUS_STATE, 0); 124 int initiator = intent.getIntExtra( 125 BluetoothHeadset.EXTRA_DISCONNECT_INITIATOR, 126 BluetoothHeadset.LOCAL_DISCONNECT); 127 // We trust this device now 128 if (newState == BluetoothHeadset.STATE_CONNECTED) { 129 setTrust(BluetoothDevice.CONNECTION_ACCESS_YES); 130 } 131 mHeadsetState = newState; 132 if (newState == BluetoothHeadset.STATE_DISCONNECTED && 133 initiator == BluetoothHeadset.REMOTE_DISCONNECT) { 134 sendMessage(DISCONNECT_HFP_INCOMING); 135 } 136 if (newState == BluetoothHeadset.STATE_CONNECTED || 137 newState == BluetoothHeadset.STATE_DISCONNECTED) { 138 sendMessage(TRANSITION_TO_STABLE); 139 } 140 } else if (action.equals(BluetoothA2dp.ACTION_SINK_STATE_CHANGED)) { 141 int newState = intent.getIntExtra(BluetoothA2dp.EXTRA_SINK_STATE, 0); 142 int oldState = intent.getIntExtra(BluetoothA2dp.EXTRA_PREVIOUS_SINK_STATE, 0); 143 mA2dpState = newState; 144 // We trust this device now 145 if (newState == BluetoothA2dp.STATE_CONNECTED) { 146 setTrust(BluetoothDevice.CONNECTION_ACCESS_YES); 147 } 148 if ((oldState == BluetoothA2dp.STATE_CONNECTED || 149 oldState == BluetoothA2dp.STATE_PLAYING) && 150 newState == BluetoothA2dp.STATE_DISCONNECTED) { 151 sendMessage(DISCONNECT_A2DP_INCOMING); 152 } 153 if (newState == BluetoothA2dp.STATE_CONNECTED || 154 newState == BluetoothA2dp.STATE_DISCONNECTED) { 155 sendMessage(TRANSITION_TO_STABLE); 156 } 157 } else if (action.equals(BluetoothDevice.ACTION_ACL_DISCONNECTED)) { 158 // This is technically not needed, but we can get stuck sometimes. 159 // For example, if incoming A2DP fails, we are not informed by Bluez 160 sendMessage(TRANSITION_TO_STABLE); 161 } else if (action.equals(BluetoothDevice.ACTION_CONNECTION_ACCESS_REPLY)) { 162 mWakeLock.release(); 163 int val = intent.getIntExtra(BluetoothDevice.EXTRA_CONNECTION_ACCESS_RESULT, 164 BluetoothDevice.CONNECTION_ACCESS_NO); 165 Message msg = obtainMessage(CONNECTION_ACCESS_REQUEST_REPLY); 166 msg.arg1 = val; 167 sendMessage(msg); 168 } 169 } 170 }; 171 172 private boolean isPhoneDocked(BluetoothDevice autoConnectDevice) { 173 // This works only because these broadcast intents are "sticky" 174 Intent i = mContext.registerReceiver(null, new IntentFilter(Intent.ACTION_DOCK_EVENT)); 175 if (i != null) { 176 int state = i.getIntExtra(Intent.EXTRA_DOCK_STATE, Intent.EXTRA_DOCK_STATE_UNDOCKED); 177 if (state != Intent.EXTRA_DOCK_STATE_UNDOCKED) { 178 BluetoothDevice device = i.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE); 179 if (device != null && autoConnectDevice.equals(device)) { 180 return true; 181 } 182 } 183 } 184 return false; 185 } 186 187 public BluetoothDeviceProfileState(Context context, String address, 188 BluetoothService service, BluetoothA2dpService a2dpService) { 189 super(address); 190 mContext = context; 191 mDevice = new BluetoothDevice(address); 192 mService = service; 193 mA2dpService = a2dpService; 194 195 addState(mBondedDevice); 196 addState(mOutgoingHandsfree); 197 addState(mIncomingHandsfree); 198 addState(mIncomingA2dp); 199 addState(mOutgoingA2dp); 200 setInitialState(mBondedDevice); 201 202 IntentFilter filter = new IntentFilter(); 203 // Fine-grained state broadcasts 204 filter.addAction(BluetoothA2dp.ACTION_SINK_STATE_CHANGED); 205 filter.addAction(BluetoothHeadset.ACTION_STATE_CHANGED); 206 filter.addAction(BluetoothDevice.ACTION_ACL_CONNECTED); 207 filter.addAction(BluetoothDevice.ACTION_ACL_DISCONNECTED); 208 filter.addAction(BluetoothDevice.ACTION_CONNECTION_ACCESS_REPLY); 209 210 mContext.registerReceiver(mBroadcastReceiver, filter); 211 212 HeadsetServiceListener l = new HeadsetServiceListener(); 213 PbapServiceListener p = new PbapServiceListener(); 214 215 mIncomingConnections = mService.getIncomingState(address); 216 mIncomingRejectTimer = readTimerValue(); 217 mPowerManager = (PowerManager)mContext.getSystemService(Context.POWER_SERVICE); 218 mWakeLock = mPowerManager.newWakeLock(PowerManager.SCREEN_DIM_WAKE_LOCK | 219 PowerManager.ACQUIRE_CAUSES_WAKEUP | 220 PowerManager.ON_AFTER_RELEASE, TAG); 221 mWakeLock.setReferenceCounted(false); 222 } 223 224 private class HeadsetServiceListener implements BluetoothHeadset.ServiceListener { 225 public HeadsetServiceListener() { 226 mHeadsetService = new BluetoothHeadset(mContext, this); 227 } 228 public void onServiceConnected() { 229 synchronized(BluetoothDeviceProfileState.this) { 230 mHeadsetServiceConnected = true; 231 } 232 } 233 public void onServiceDisconnected() { 234 synchronized(BluetoothDeviceProfileState.this) { 235 mHeadsetServiceConnected = false; 236 } 237 } 238 } 239 240 private class PbapServiceListener implements BluetoothPbap.ServiceListener { 241 public PbapServiceListener() { 242 mPbapService = new BluetoothPbap(mContext, this); 243 } 244 public void onServiceConnected() { 245 synchronized(BluetoothDeviceProfileState.this) { 246 mPbapServiceConnected = true; 247 } 248 } 249 public void onServiceDisconnected() { 250 synchronized(BluetoothDeviceProfileState.this) { 251 mPbapServiceConnected = false; 252 } 253 } 254 } 255 256 private class BondedDevice extends HierarchicalState { 257 @Override 258 protected void enter() { 259 Log.i(TAG, "Entering ACL Connected state with: " + getCurrentMessage().what); 260 Message m = new Message(); 261 m.copyFrom(getCurrentMessage()); 262 sendMessageAtFrontOfQueue(m); 263 } 264 @Override 265 protected boolean processMessage(Message message) { 266 log("ACL Connected State -> Processing Message: " + message.what); 267 switch(message.what) { 268 case CONNECT_HFP_OUTGOING: 269 case DISCONNECT_HFP_OUTGOING: 270 transitionTo(mOutgoingHandsfree); 271 break; 272 case CONNECT_HFP_INCOMING: 273 transitionTo(mIncomingHandsfree); 274 break; 275 case DISCONNECT_HFP_INCOMING: 276 transitionTo(mIncomingHandsfree); 277 break; 278 case CONNECT_A2DP_OUTGOING: 279 case DISCONNECT_A2DP_OUTGOING: 280 transitionTo(mOutgoingA2dp); 281 break; 282 case CONNECT_A2DP_INCOMING: 283 case DISCONNECT_A2DP_INCOMING: 284 transitionTo(mIncomingA2dp); 285 break; 286 case DISCONNECT_PBAP_OUTGOING: 287 processCommand(DISCONNECT_PBAP_OUTGOING); 288 break; 289 case UNPAIR: 290 if (mHeadsetState != BluetoothHeadset.STATE_DISCONNECTED) { 291 sendMessage(DISCONNECT_HFP_OUTGOING); 292 deferMessage(message); 293 break; 294 } else if (mA2dpState != BluetoothA2dp.STATE_DISCONNECTED) { 295 sendMessage(DISCONNECT_A2DP_OUTGOING); 296 deferMessage(message); 297 break; 298 } 299 processCommand(UNPAIR); 300 break; 301 case AUTO_CONNECT_PROFILES: 302 if (isPhoneDocked(mDevice)) { 303 // Don't auto connect to docks. 304 break; 305 } else if (!mHeadsetServiceConnected) { 306 deferMessage(message); 307 } else { 308 if (mHeadsetService.getPriority(mDevice) == 309 BluetoothHeadset.PRIORITY_AUTO_CONNECT && 310 !mHeadsetService.isConnected(mDevice)) { 311 Log.i(TAG, "Headset:Auto Connect Profiles"); 312 mHeadsetService.connectHeadset(mDevice); 313 } 314 if (mA2dpService != null && 315 mA2dpService.getSinkPriority(mDevice) == 316 BluetoothA2dp.PRIORITY_AUTO_CONNECT && 317 mA2dpService.getConnectedSinks().length == 0) { 318 Log.i(TAG, "A2dp:Auto Connect Profiles"); 319 mA2dpService.connectSink(mDevice); 320 } 321 } 322 break; 323 case CONNECT_OTHER_PROFILES: 324 if (isPhoneDocked(mDevice)) { 325 break; 326 } 327 if (message.arg1 == CONNECT_A2DP_OUTGOING) { 328 if (mA2dpService != null && 329 mA2dpService.getConnectedSinks().length == 0) { 330 Log.i(TAG, "A2dp:Connect Other Profiles"); 331 mA2dpService.connectSink(mDevice); 332 } 333 } else if (message.arg1 == CONNECT_HFP_OUTGOING) { 334 if (!mHeadsetServiceConnected) { 335 deferMessage(message); 336 } else { 337 if (!mHeadsetService.isConnected(mDevice)) { 338 Log.i(TAG, "Headset:Connect Other Profiles"); 339 mHeadsetService.connectHeadset(mDevice); 340 } 341 } 342 } 343 break; 344 case TRANSITION_TO_STABLE: 345 // ignore. 346 break; 347 default: 348 return NOT_HANDLED; 349 } 350 return HANDLED; 351 } 352 } 353 354 private class OutgoingHandsfree extends HierarchicalState { 355 private boolean mStatus = false; 356 private int mCommand; 357 358 @Override 359 protected void enter() { 360 Log.i(TAG, "Entering OutgoingHandsfree state with: " + getCurrentMessage().what); 361 mCommand = getCurrentMessage().what; 362 if (mCommand != CONNECT_HFP_OUTGOING && 363 mCommand != DISCONNECT_HFP_OUTGOING) { 364 Log.e(TAG, "Error: OutgoingHandsfree state with command:" + mCommand); 365 } 366 mStatus = processCommand(mCommand); 367 if (!mStatus) { 368 sendMessage(TRANSITION_TO_STABLE); 369 mService.sendProfileStateMessage(BluetoothProfileState.HFP, 370 BluetoothProfileState.TRANSITION_TO_STABLE); 371 } 372 } 373 374 @Override 375 protected boolean processMessage(Message message) { 376 log("OutgoingHandsfree State -> Processing Message: " + message.what); 377 Message deferMsg = new Message(); 378 int command = message.what; 379 switch(command) { 380 case CONNECT_HFP_OUTGOING: 381 if (command != mCommand) { 382 // Disconnect followed by a connect - defer 383 deferMessage(message); 384 } 385 break; 386 case CONNECT_HFP_INCOMING: 387 if (mCommand == CONNECT_HFP_OUTGOING) { 388 // Cancel outgoing connect, accept incoming 389 cancelCommand(CONNECT_HFP_OUTGOING); 390 transitionTo(mIncomingHandsfree); 391 } else { 392 // We have done the disconnect but we are not 393 // sure which state we are in at this point. 394 deferMessage(message); 395 } 396 break; 397 case CONNECT_A2DP_INCOMING: 398 // accept incoming A2DP, retry HFP_OUTGOING 399 transitionTo(mIncomingA2dp); 400 401 if (mStatus) { 402 deferMsg.what = mCommand; 403 deferMessage(deferMsg); 404 } 405 break; 406 case CONNECT_A2DP_OUTGOING: 407 deferMessage(message); 408 break; 409 case DISCONNECT_HFP_OUTGOING: 410 if (mCommand == CONNECT_HFP_OUTGOING) { 411 // Cancel outgoing connect 412 cancelCommand(CONNECT_HFP_OUTGOING); 413 processCommand(DISCONNECT_HFP_OUTGOING); 414 } 415 // else ignore 416 break; 417 case DISCONNECT_HFP_INCOMING: 418 // When this happens the socket would be closed and the headset 419 // state moved to DISCONNECTED, cancel the outgoing thread. 420 // if it still is in CONNECTING state 421 cancelCommand(CONNECT_HFP_OUTGOING); 422 break; 423 case DISCONNECT_A2DP_OUTGOING: 424 deferMessage(message); 425 break; 426 case DISCONNECT_A2DP_INCOMING: 427 // Bluez will handle the disconnect. If because of this the outgoing 428 // handsfree connection has failed, then retry. 429 if (mStatus) { 430 deferMsg.what = mCommand; 431 deferMessage(deferMsg); 432 } 433 break; 434 case DISCONNECT_PBAP_OUTGOING: 435 case UNPAIR: 436 case AUTO_CONNECT_PROFILES: 437 case CONNECT_OTHER_PROFILES: 438 deferMessage(message); 439 break; 440 case TRANSITION_TO_STABLE: 441 transitionTo(mBondedDevice); 442 break; 443 default: 444 return NOT_HANDLED; 445 } 446 return HANDLED; 447 } 448 } 449 450 private class IncomingHandsfree extends HierarchicalState { 451 private boolean mStatus = false; 452 private int mCommand; 453 454 @Override 455 protected void enter() { 456 Log.i(TAG, "Entering IncomingHandsfree state with: " + getCurrentMessage().what); 457 mCommand = getCurrentMessage().what; 458 if (mCommand != CONNECT_HFP_INCOMING && 459 mCommand != DISCONNECT_HFP_INCOMING) { 460 Log.e(TAG, "Error: IncomingHandsfree state with command:" + mCommand); 461 } 462 mStatus = processCommand(mCommand); 463 if (!mStatus) { 464 sendMessage(TRANSITION_TO_STABLE); 465 mService.sendProfileStateMessage(BluetoothProfileState.HFP, 466 BluetoothProfileState.TRANSITION_TO_STABLE); 467 } 468 } 469 470 @Override 471 protected boolean processMessage(Message message) { 472 log("IncomingHandsfree State -> Processing Message: " + message.what); 473 switch(message.what) { 474 case CONNECT_HFP_OUTGOING: 475 deferMessage(message); 476 break; 477 case CONNECT_HFP_INCOMING: 478 // Ignore 479 Log.e(TAG, "Error: Incoming connection with a pending incoming connection"); 480 break; 481 case CONNECTION_ACCESS_REQUEST_REPLY: 482 int val = message.arg1; 483 mConnectionAccessReplyReceived = true; 484 boolean value = false; 485 if (val == BluetoothDevice.CONNECTION_ACCESS_YES) { 486 value = true; 487 } 488 setTrust(val); 489 490 handleIncomingConnection(CONNECT_HFP_INCOMING, value); 491 break; 492 case CONNECTION_ACCESS_REQUEST_EXPIRY: 493 if (!mConnectionAccessReplyReceived) { 494 handleIncomingConnection(CONNECT_HFP_INCOMING, false); 495 sendConnectionAccessRemovalIntent(); 496 sendMessage(TRANSITION_TO_STABLE); 497 } 498 break; 499 case CONNECT_A2DP_INCOMING: 500 // Serialize the commands. 501 deferMessage(message); 502 break; 503 case CONNECT_A2DP_OUTGOING: 504 deferMessage(message); 505 break; 506 case DISCONNECT_HFP_OUTGOING: 507 // We don't know at what state we are in the incoming HFP connection state. 508 // We can be changing from DISCONNECTED to CONNECTING, or 509 // from CONNECTING to CONNECTED, so serializing this command is 510 // the safest option. 511 deferMessage(message); 512 break; 513 case DISCONNECT_HFP_INCOMING: 514 // Nothing to do here, we will already be DISCONNECTED 515 // by this point. 516 break; 517 case DISCONNECT_A2DP_OUTGOING: 518 deferMessage(message); 519 break; 520 case DISCONNECT_A2DP_INCOMING: 521 // Bluez handles incoming A2DP disconnect. 522 // If this causes incoming HFP to fail, it is more of a headset problem 523 // since both connections are incoming ones. 524 break; 525 case DISCONNECT_PBAP_OUTGOING: 526 case UNPAIR: 527 case AUTO_CONNECT_PROFILES: 528 case CONNECT_OTHER_PROFILES: 529 deferMessage(message); 530 break; 531 case TRANSITION_TO_STABLE: 532 transitionTo(mBondedDevice); 533 break; 534 default: 535 return NOT_HANDLED; 536 } 537 return HANDLED; 538 } 539 } 540 541 private class OutgoingA2dp extends HierarchicalState { 542 private boolean mStatus = false; 543 private int mCommand; 544 545 @Override 546 protected void enter() { 547 Log.i(TAG, "Entering OutgoingA2dp state with: " + getCurrentMessage().what); 548 mCommand = getCurrentMessage().what; 549 if (mCommand != CONNECT_A2DP_OUTGOING && 550 mCommand != DISCONNECT_A2DP_OUTGOING) { 551 Log.e(TAG, "Error: OutgoingA2DP state with command:" + mCommand); 552 } 553 mStatus = processCommand(mCommand); 554 if (!mStatus) { 555 sendMessage(TRANSITION_TO_STABLE); 556 mService.sendProfileStateMessage(BluetoothProfileState.A2DP, 557 BluetoothProfileState.TRANSITION_TO_STABLE); 558 } 559 } 560 561 @Override 562 protected boolean processMessage(Message message) { 563 log("OutgoingA2dp State->Processing Message: " + message.what); 564 Message deferMsg = new Message(); 565 switch(message.what) { 566 case CONNECT_HFP_OUTGOING: 567 processCommand(CONNECT_HFP_OUTGOING); 568 569 // Don't cancel A2DP outgoing as there is no guarantee it 570 // will get canceled. 571 // It might already be connected but we might not have got the 572 // A2DP_SINK_STATE_CHANGE. Hence, no point disconnecting here. 573 // The worst case, the connection will fail, retry. 574 // The same applies to Disconnecting an A2DP connection. 575 if (mStatus) { 576 deferMsg.what = mCommand; 577 deferMessage(deferMsg); 578 } 579 break; 580 case CONNECT_HFP_INCOMING: 581 processCommand(CONNECT_HFP_INCOMING); 582 583 // Don't cancel A2DP outgoing as there is no guarantee 584 // it will get canceled. 585 // The worst case, the connection will fail, retry. 586 if (mStatus) { 587 deferMsg.what = mCommand; 588 deferMessage(deferMsg); 589 } 590 break; 591 case CONNECT_A2DP_INCOMING: 592 // Bluez will take care of conflicts between incoming and outgoing 593 // connections. 594 transitionTo(mIncomingA2dp); 595 break; 596 case CONNECT_A2DP_OUTGOING: 597 // Ignore 598 break; 599 case DISCONNECT_HFP_OUTGOING: 600 deferMessage(message); 601 break; 602 case DISCONNECT_HFP_INCOMING: 603 // At this point, we are already disconnected 604 // with HFP. Sometimes A2DP connection can 605 // fail due to the disconnection of HFP. So add a retry 606 // for the A2DP. 607 if (mStatus) { 608 deferMsg.what = mCommand; 609 deferMessage(deferMsg); 610 } 611 break; 612 case DISCONNECT_A2DP_OUTGOING: 613 deferMessage(message); 614 break; 615 case DISCONNECT_A2DP_INCOMING: 616 // Ignore, will be handled by Bluez 617 break; 618 case DISCONNECT_PBAP_OUTGOING: 619 case UNPAIR: 620 case AUTO_CONNECT_PROFILES: 621 case CONNECT_OTHER_PROFILES: 622 deferMessage(message); 623 break; 624 case TRANSITION_TO_STABLE: 625 transitionTo(mBondedDevice); 626 break; 627 default: 628 return NOT_HANDLED; 629 } 630 return HANDLED; 631 } 632 } 633 634 private class IncomingA2dp extends HierarchicalState { 635 private boolean mStatus = false; 636 private int mCommand; 637 638 @Override 639 protected void enter() { 640 Log.i(TAG, "Entering IncomingA2dp state with: " + getCurrentMessage().what); 641 mCommand = getCurrentMessage().what; 642 if (mCommand != CONNECT_A2DP_INCOMING && 643 mCommand != DISCONNECT_A2DP_INCOMING) { 644 Log.e(TAG, "Error: IncomingA2DP state with command:" + mCommand); 645 } 646 mStatus = processCommand(mCommand); 647 if (!mStatus) { 648 sendMessage(TRANSITION_TO_STABLE); 649 mService.sendProfileStateMessage(BluetoothProfileState.A2DP, 650 BluetoothProfileState.TRANSITION_TO_STABLE); 651 } 652 } 653 654 @Override 655 protected boolean processMessage(Message message) { 656 log("IncomingA2dp State->Processing Message: " + message.what); 657 Message deferMsg = new Message(); 658 switch(message.what) { 659 case CONNECT_HFP_OUTGOING: 660 deferMessage(message); 661 break; 662 case CONNECT_HFP_INCOMING: 663 // Shouldn't happen, but serialize the commands. 664 deferMessage(message); 665 break; 666 case CONNECT_A2DP_INCOMING: 667 // ignore 668 break; 669 case CONNECTION_ACCESS_REQUEST_REPLY: 670 int val = message.arg1; 671 mConnectionAccessReplyReceived = true; 672 boolean value = false; 673 if (val == BluetoothDevice.CONNECTION_ACCESS_YES) { 674 value = true; 675 } 676 setTrust(val); 677 handleIncomingConnection(CONNECT_A2DP_INCOMING, value); 678 break; 679 case CONNECTION_ACCESS_REQUEST_EXPIRY: 680 // The check protects the race condition between REQUEST_REPLY 681 // and the timer expiry. 682 if (!mConnectionAccessReplyReceived) { 683 handleIncomingConnection(CONNECT_A2DP_INCOMING, false); 684 sendConnectionAccessRemovalIntent(); 685 sendMessage(TRANSITION_TO_STABLE); 686 } 687 break; 688 case CONNECT_A2DP_OUTGOING: 689 // Defer message and retry 690 deferMessage(message); 691 break; 692 case DISCONNECT_HFP_OUTGOING: 693 deferMessage(message); 694 break; 695 case DISCONNECT_HFP_INCOMING: 696 // Shouldn't happen but if does, we can handle it. 697 // Depends if the headset can handle it. 698 // Incoming A2DP will be handled by Bluez, Disconnect HFP 699 // the socket would have already been closed. 700 // ignore 701 break; 702 case DISCONNECT_A2DP_OUTGOING: 703 deferMessage(message); 704 break; 705 case DISCONNECT_A2DP_INCOMING: 706 // Ignore, will be handled by Bluez 707 break; 708 case DISCONNECT_PBAP_OUTGOING: 709 case UNPAIR: 710 case AUTO_CONNECT_PROFILES: 711 case CONNECT_OTHER_PROFILES: 712 deferMessage(message); 713 break; 714 case TRANSITION_TO_STABLE: 715 transitionTo(mBondedDevice); 716 break; 717 default: 718 return NOT_HANDLED; 719 } 720 return HANDLED; 721 } 722 } 723 724 725 726 synchronized void cancelCommand(int command) { 727 if (command == CONNECT_HFP_OUTGOING ) { 728 // Cancel the outgoing thread. 729 if (mHeadsetServiceConnected) { 730 mHeadsetService.cancelConnectThread(); 731 } 732 // HeadsetService is down. Phone process most likely crashed. 733 // The thread would have got killed. 734 } 735 } 736 737 synchronized void deferProfileServiceMessage(int command) { 738 Message msg = new Message(); 739 msg.what = command; 740 deferMessage(msg); 741 } 742 743 private void updateIncomingAllowedTimer() { 744 // Not doing a perfect exponential backoff because 745 // we want two different rates. For all practical 746 // purposes, this is good enough. 747 if (mIncomingRejectTimer == 0) mIncomingRejectTimer = INIT_INCOMING_REJECT_TIMER; 748 749 mIncomingRejectTimer *= 5; 750 if (mIncomingRejectTimer > MAX_INCOMING_REJECT_TIMER) { 751 mIncomingRejectTimer = MAX_INCOMING_REJECT_TIMER; 752 } 753 writeTimerValue(mIncomingRejectTimer); 754 } 755 756 private boolean handleIncomingConnection(int command, boolean accept) { 757 boolean ret = false; 758 Log.i(TAG, "handleIncomingConnection:" + command + ":" + accept); 759 switch (command) { 760 case CONNECT_HFP_INCOMING: 761 if (!accept) { 762 ret = mHeadsetService.rejectIncomingConnect(mDevice); 763 sendMessage(TRANSITION_TO_STABLE); 764 updateIncomingAllowedTimer(); 765 } else if (mHeadsetState == BluetoothHeadset.STATE_CONNECTING) { 766 writeTimerValue(0); 767 ret = mHeadsetService.acceptIncomingConnect(mDevice); 768 } else if (mHeadsetState == BluetoothHeadset.STATE_DISCONNECTED) { 769 writeTimerValue(0); 770 handleConnectionOfOtherProfiles(command); 771 ret = mHeadsetService.createIncomingConnect(mDevice); 772 } 773 break; 774 case CONNECT_A2DP_INCOMING: 775 if (!accept) { 776 ret = mA2dpService.allowIncomingConnect(mDevice, false); 777 sendMessage(TRANSITION_TO_STABLE); 778 updateIncomingAllowedTimer(); 779 } else { 780 writeTimerValue(0); 781 ret = mA2dpService.allowIncomingConnect(mDevice, true); 782 handleConnectionOfOtherProfiles(command); 783 } 784 break; 785 default: 786 Log.e(TAG, "Waiting for incoming connection but state changed to:" + command); 787 break; 788 } 789 return ret; 790 } 791 792 private void sendConnectionAccessIntent() { 793 mConnectionAccessReplyReceived = false; 794 795 if (!mPowerManager.isScreenOn()) mWakeLock.acquire(); 796 797 Intent intent = new Intent(BluetoothDevice.ACTION_CONNECTION_ACCESS_REQUEST); 798 intent.putExtra(BluetoothDevice.EXTRA_DEVICE, mDevice); 799 mContext.sendBroadcast(intent, BLUETOOTH_ADMIN_PERM); 800 } 801 802 private void sendConnectionAccessRemovalIntent() { 803 mWakeLock.release(); 804 Intent intent = new Intent(BluetoothDevice.ACTION_CONNECTION_ACCESS_CANCEL); 805 intent.putExtra(BluetoothDevice.EXTRA_DEVICE, mDevice); 806 mContext.sendBroadcast(intent, BLUETOOTH_ADMIN_PERM); 807 } 808 809 private int getTrust() { 810 String address = mDevice.getAddress(); 811 if (mIncomingConnections != null) return mIncomingConnections.first; 812 return CONNECTION_ACCESS_UNDEFINED; 813 } 814 815 816 private String getStringValue(long value) { 817 StringBuilder sbr = new StringBuilder(); 818 sbr.append(Long.toString(System.currentTimeMillis())); 819 sbr.append("-"); 820 sbr.append(Long.toString(value)); 821 return sbr.toString(); 822 } 823 824 private void setTrust(int value) { 825 String second; 826 if (mIncomingConnections == null) { 827 second = getStringValue(INIT_INCOMING_REJECT_TIMER); 828 } else { 829 second = mIncomingConnections.second; 830 } 831 832 mIncomingConnections = new Pair(value, second); 833 mService.writeIncomingConnectionState(mDevice.getAddress(), mIncomingConnections); 834 } 835 836 private void writeTimerValue(long value) { 837 Integer first; 838 if (mIncomingConnections == null) { 839 first = CONNECTION_ACCESS_UNDEFINED; 840 } else { 841 first = mIncomingConnections.first; 842 } 843 mIncomingConnections = new Pair(first, getStringValue(value)); 844 mService.writeIncomingConnectionState(mDevice.getAddress(), mIncomingConnections); 845 } 846 847 private long readTimerValue() { 848 if (mIncomingConnections == null) 849 return 0; 850 String value = mIncomingConnections.second; 851 String[] splits = value.split("-"); 852 if (splits != null && splits.length == 2) { 853 return Long.parseLong(splits[1]); 854 } 855 return 0; 856 } 857 858 private boolean readIncomingAllowedValue() { 859 if (readTimerValue() == 0) return true; 860 String value = mIncomingConnections.second; 861 String[] splits = value.split("-"); 862 if (splits != null && splits.length == 2) { 863 long val1 = Long.parseLong(splits[0]); 864 long val2 = Long.parseLong(splits[1]); 865 if (val1 + val2 <= System.currentTimeMillis()) { 866 return true; 867 } 868 } 869 return false; 870 } 871 872 synchronized boolean processCommand(int command) { 873 Log.e(TAG, "Processing command:" + command); 874 Message msg; 875 switch(command) { 876 case CONNECT_HFP_OUTGOING: 877 if (mHeadsetService != null) { 878 return mHeadsetService.connectHeadsetInternal(mDevice); 879 } 880 break; 881 case CONNECT_HFP_INCOMING: 882 if (!mHeadsetServiceConnected) { 883 deferProfileServiceMessage(command); 884 } else { 885 // Check if device is already trusted 886 int access = getTrust(); 887 if (access == BluetoothDevice.CONNECTION_ACCESS_YES) { 888 handleIncomingConnection(command, true); 889 } else if (access == BluetoothDevice.CONNECTION_ACCESS_NO && 890 !readIncomingAllowedValue()) { 891 handleIncomingConnection(command, false); 892 } else { 893 sendConnectionAccessIntent(); 894 msg = obtainMessage(CONNECTION_ACCESS_REQUEST_EXPIRY); 895 sendMessageDelayed(msg, 896 CONNECTION_ACCESS_REQUEST_EXPIRY_TIMEOUT); 897 } 898 return true; 899 } 900 break; 901 case CONNECT_A2DP_OUTGOING: 902 if (mA2dpService != null) { 903 return mA2dpService.connectSinkInternal(mDevice); 904 } 905 break; 906 case CONNECT_A2DP_INCOMING: 907 // Check if device is already trusted 908 int access = getTrust(); 909 if (access == BluetoothDevice.CONNECTION_ACCESS_YES) { 910 handleIncomingConnection(command, true); 911 } else if (access == BluetoothDevice.CONNECTION_ACCESS_NO && 912 !readIncomingAllowedValue()) { 913 handleIncomingConnection(command, false); 914 } else { 915 sendConnectionAccessIntent(); 916 msg = obtainMessage(CONNECTION_ACCESS_REQUEST_EXPIRY); 917 sendMessageDelayed(msg, 918 CONNECTION_ACCESS_REQUEST_EXPIRY_TIMEOUT); 919 } 920 return true; 921 case DISCONNECT_HFP_OUTGOING: 922 if (!mHeadsetServiceConnected) { 923 deferProfileServiceMessage(command); 924 } else { 925 // Disconnect PBAP 926 // TODO(): Add PBAP to the state machine. 927 Message m = new Message(); 928 m.what = DISCONNECT_PBAP_OUTGOING; 929 deferMessage(m); 930 if (mHeadsetService.getPriority(mDevice) == 931 BluetoothHeadset.PRIORITY_AUTO_CONNECT) { 932 mHeadsetService.setPriority(mDevice, BluetoothHeadset.PRIORITY_ON); 933 } 934 return mHeadsetService.disconnectHeadsetInternal(mDevice); 935 } 936 break; 937 case DISCONNECT_HFP_INCOMING: 938 // ignore 939 return true; 940 case DISCONNECT_A2DP_INCOMING: 941 // ignore 942 return true; 943 case DISCONNECT_A2DP_OUTGOING: 944 if (mA2dpService != null) { 945 if (mA2dpService.getSinkPriority(mDevice) == 946 BluetoothA2dp.PRIORITY_AUTO_CONNECT) { 947 mA2dpService.setSinkPriority(mDevice, BluetoothHeadset.PRIORITY_ON); 948 } 949 return mA2dpService.disconnectSinkInternal(mDevice); 950 } 951 break; 952 case DISCONNECT_PBAP_OUTGOING: 953 if (!mPbapServiceConnected) { 954 deferProfileServiceMessage(command); 955 } else { 956 return mPbapService.disconnect(); 957 } 958 break; 959 case UNPAIR: 960 writeTimerValue(INIT_INCOMING_REJECT_TIMER); 961 setTrust(CONNECTION_ACCESS_UNDEFINED); 962 return mService.removeBondInternal(mDevice.getAddress()); 963 default: 964 Log.e(TAG, "Error: Unknown Command"); 965 } 966 return false; 967 } 968 969 private void handleConnectionOfOtherProfiles(int command) { 970 // The white paper recommendations mentions that when there is a 971 // link loss, it is the responsibility of the remote device to connect. 972 // Many connect only 1 profile - and they connect the second profile on 973 // some user action (like play being pressed) and so we need this code. 974 // Auto Connect code only connects to the last connected device - which 975 // is useful in cases like when the phone reboots. But consider the 976 // following case: 977 // User is connected to the car's phone and A2DP profile. 978 // User comes to the desk and places the phone in the dock 979 // (or any speaker or music system or even another headset) and thus 980 // gets connected to the A2DP profile. User goes back to the car. 981 // Ideally the car's system is supposed to send incoming connections 982 // from both Handsfree and A2DP profile. But they don't. The Auto 983 // connect code, will not work here because we only auto connect to the 984 // last connected device for that profile which in this case is the dock. 985 // Now suppose a user is using 2 headsets simultaneously, one for the 986 // phone profile one for the A2DP profile. If this is the use case, we 987 // expect the user to use the preference to turn off the A2DP profile in 988 // the Settings screen for the first headset. Else, after link loss, 989 // there can be an incoming connection from the first headset which 990 // might result in the connection of the A2DP profile (if the second 991 // headset is slower) and thus the A2DP profile on the second headset 992 // will never get connected. 993 // 994 // TODO(): Handle other profiles here. 995 switch (command) { 996 case CONNECT_HFP_INCOMING: 997 // Connect A2DP if there is no incoming connection 998 // If the priority is OFF - don't auto connect. 999 if (mA2dpService.getSinkPriority(mDevice) == BluetoothA2dp.PRIORITY_ON || 1000 mA2dpService.getSinkPriority(mDevice) == 1001 BluetoothA2dp.PRIORITY_AUTO_CONNECT) { 1002 Message msg = new Message(); 1003 msg.what = CONNECT_OTHER_PROFILES; 1004 msg.arg1 = CONNECT_A2DP_OUTGOING; 1005 sendMessageDelayed(msg, CONNECT_OTHER_PROFILES_DELAY); 1006 } 1007 break; 1008 case CONNECT_A2DP_INCOMING: 1009 // This is again against spec. HFP incoming connections should be made 1010 // before A2DP, so we should not hit this case. But many devices 1011 // don't follow this. 1012 if (mHeadsetService.getPriority(mDevice) == BluetoothHeadset.PRIORITY_ON 1013 || mHeadsetService.getPriority(mDevice) == 1014 BluetoothHeadset.PRIORITY_AUTO_CONNECT) { 1015 Message msg = new Message(); 1016 msg.what = CONNECT_OTHER_PROFILES; 1017 msg.arg1 = CONNECT_HFP_OUTGOING; 1018 sendMessageDelayed(msg, CONNECT_OTHER_PROFILES_DELAY); 1019 } 1020 break; 1021 default: 1022 break; 1023 } 1024 1025 } 1026 1027 /*package*/ BluetoothDevice getDevice() { 1028 return mDevice; 1029 } 1030 1031 private void log(String message) { 1032 if (DBG) { 1033 Log.i(TAG, "Device:" + mDevice + " Message:" + message); 1034 } 1035 } 1036 } 1037