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.bluetooth.BluetoothAdapter; 25 import android.os.PowerManager; 26 import android.server.BluetoothA2dpService; 27 import android.server.BluetoothService; 28 import android.util.Log; 29 import android.util.Pair; 30 31 import com.android.internal.util.State; 32 import com.android.internal.util.StateMachine; 33 34 import java.util.Set; 35 36 /** 37 * This class is the Profile connection state machine associated with a remote 38 * device. When the device bonds an instance of this class is created. 39 * This tracks incoming and outgoing connections of all the profiles. Incoming 40 * connections are preferred over outgoing connections and HFP preferred over 41 * A2DP. When the device is unbonded, the instance is removed. 42 * 43 * States: 44 * {@link BondedDevice}: This state represents a bonded device. When in this 45 * state none of the profiles are in transition states. 46 * 47 * {@link OutgoingHandsfree}: Handsfree profile connection is in a transition 48 * state because of a outgoing Connect or Disconnect. 49 * 50 * {@link IncomingHandsfree}: Handsfree profile connection is in a transition 51 * state because of a incoming Connect or Disconnect. 52 * 53 * {@link IncomingA2dp}: A2dp profile connection is in a transition 54 * state because of a incoming Connect or Disconnect. 55 * 56 * {@link OutgoingA2dp}: A2dp profile connection is in a transition 57 * state because of a outgoing Connect or Disconnect. 58 * 59 * Todo(): Write tests for this class, when the Android Mock support is completed. 60 * @hide 61 */ 62 public final class BluetoothDeviceProfileState extends StateMachine { 63 private static final String TAG = "BluetoothDeviceProfileState"; 64 private static final boolean DBG = false; 65 66 // TODO(): Restructure the state machine to make it scalable with regard to profiles. 67 public static final int CONNECT_HFP_OUTGOING = 1; 68 public static final int CONNECT_HFP_INCOMING = 2; 69 public static final int CONNECT_A2DP_OUTGOING = 3; 70 public static final int CONNECT_A2DP_INCOMING = 4; 71 public static final int CONNECT_HID_OUTGOING = 5; 72 public static final int CONNECT_HID_INCOMING = 6; 73 74 public static final int DISCONNECT_HFP_OUTGOING = 50; 75 private static final int DISCONNECT_HFP_INCOMING = 51; 76 public static final int DISCONNECT_A2DP_OUTGOING = 52; 77 public static final int DISCONNECT_A2DP_INCOMING = 53; 78 public static final int DISCONNECT_HID_OUTGOING = 54; 79 public static final int DISCONNECT_HID_INCOMING = 55; 80 public static final int DISCONNECT_PBAP_OUTGOING = 56; 81 82 public static final int UNPAIR = 100; 83 public static final int AUTO_CONNECT_PROFILES = 101; 84 public static final int TRANSITION_TO_STABLE = 102; 85 public static final int CONNECT_OTHER_PROFILES = 103; 86 private static final int CONNECTION_ACCESS_REQUEST_REPLY = 104; 87 private static final int CONNECTION_ACCESS_REQUEST_EXPIRY = 105; 88 89 public static final int CONNECT_OTHER_PROFILES_DELAY = 4000; // 4 secs 90 private static final int CONNECTION_ACCESS_REQUEST_EXPIRY_TIMEOUT = 7000; // 7 secs 91 private static final int CONNECTION_ACCESS_UNDEFINED = -1; 92 private static final long INIT_INCOMING_REJECT_TIMER = 1000; // 1 sec 93 private static final long MAX_INCOMING_REJECT_TIMER = 3600 * 1000 * 4; // 4 hours 94 95 private static final String ACCESS_AUTHORITY_PACKAGE = "com.android.settings"; 96 private static final String ACCESS_AUTHORITY_CLASS = 97 "com.android.settings.bluetooth.BluetoothPermissionRequest"; 98 99 private BondedDevice mBondedDevice = new BondedDevice(); 100 private OutgoingHandsfree mOutgoingHandsfree = new OutgoingHandsfree(); 101 private IncomingHandsfree mIncomingHandsfree = new IncomingHandsfree(); 102 private IncomingA2dp mIncomingA2dp = new IncomingA2dp(); 103 private OutgoingA2dp mOutgoingA2dp = new OutgoingA2dp(); 104 private OutgoingHid mOutgoingHid = new OutgoingHid(); 105 private IncomingHid mIncomingHid = new IncomingHid(); 106 107 private Context mContext; 108 private BluetoothService mService; 109 private BluetoothA2dpService mA2dpService; 110 private BluetoothHeadset mHeadsetService; 111 private BluetoothPbap mPbapService; 112 private PbapServiceListener mPbap; 113 private BluetoothAdapter mAdapter; 114 private boolean mPbapServiceConnected; 115 private boolean mAutoConnectionPending; 116 private static final String BLUETOOTH_ADMIN_PERM = android.Manifest.permission.BLUETOOTH_ADMIN; 117 118 private BluetoothDevice mDevice; 119 private int mHeadsetState = BluetoothProfile.STATE_DISCONNECTED; 120 private int mA2dpState = BluetoothProfile.STATE_DISCONNECTED; 121 private long mIncomingRejectTimer; 122 private boolean mConnectionAccessReplyReceived = false; 123 private Pair<Integer, String> mIncomingConnections; 124 private PowerManager.WakeLock mWakeLock; 125 private PowerManager mPowerManager; 126 private boolean mPairingRequestRcvd = false; 127 128 private BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() { 129 @Override 130 public void onReceive(Context context, Intent intent) { 131 String action = intent.getAction(); 132 BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE); 133 if (device == null || !device.equals(mDevice)) return; 134 135 if (action.equals(BluetoothA2dp.ACTION_CONNECTION_STATE_CHANGED)) { 136 int newState = intent.getIntExtra(BluetoothProfile.EXTRA_STATE, 0); 137 int oldState = intent.getIntExtra(BluetoothProfile.EXTRA_PREVIOUS_STATE, 0); 138 // We trust this device now 139 if (newState == BluetoothHeadset.STATE_CONNECTED) { 140 setTrust(BluetoothDevice.CONNECTION_ACCESS_YES); 141 } 142 mA2dpState = newState; 143 if (oldState == BluetoothA2dp.STATE_CONNECTED && 144 newState == BluetoothA2dp.STATE_DISCONNECTED) { 145 sendMessage(DISCONNECT_A2DP_INCOMING); 146 } 147 if (newState == BluetoothProfile.STATE_CONNECTED || 148 newState == BluetoothProfile.STATE_DISCONNECTED) { 149 sendMessage(TRANSITION_TO_STABLE); 150 } 151 } else if (action.equals(BluetoothHeadset.ACTION_CONNECTION_STATE_CHANGED)) { 152 int newState = intent.getIntExtra(BluetoothProfile.EXTRA_STATE, 0); 153 int oldState = intent.getIntExtra(BluetoothProfile.EXTRA_PREVIOUS_STATE, 0); 154 // We trust this device now 155 if (newState == BluetoothHeadset.STATE_CONNECTED) { 156 setTrust(BluetoothDevice.CONNECTION_ACCESS_YES); 157 } 158 mHeadsetState = newState; 159 if (oldState == BluetoothHeadset.STATE_CONNECTED && 160 newState == BluetoothHeadset.STATE_DISCONNECTED) { 161 sendMessage(DISCONNECT_HFP_INCOMING); 162 } 163 if (newState == BluetoothProfile.STATE_CONNECTED || 164 newState == BluetoothProfile.STATE_DISCONNECTED) { 165 sendMessage(TRANSITION_TO_STABLE); 166 } 167 } else if (action.equals(BluetoothInputDevice.ACTION_CONNECTION_STATE_CHANGED)) { 168 int newState = intent.getIntExtra(BluetoothProfile.EXTRA_STATE, 0); 169 int oldState = 170 intent.getIntExtra(BluetoothProfile.EXTRA_PREVIOUS_STATE, 0); 171 // We trust this device now 172 if (newState == BluetoothHeadset.STATE_CONNECTED) { 173 setTrust(BluetoothDevice.CONNECTION_ACCESS_YES); 174 } 175 if (oldState == BluetoothProfile.STATE_CONNECTED && 176 newState == BluetoothProfile.STATE_DISCONNECTED) { 177 sendMessage(DISCONNECT_HID_INCOMING); 178 } 179 if (newState == BluetoothProfile.STATE_CONNECTED || 180 newState == BluetoothProfile.STATE_DISCONNECTED) { 181 sendMessage(TRANSITION_TO_STABLE); 182 } 183 } else if (action.equals(BluetoothDevice.ACTION_ACL_DISCONNECTED)) { 184 // This is technically not needed, but we can get stuck sometimes. 185 // For example, if incoming A2DP fails, we are not informed by Bluez 186 sendMessage(TRANSITION_TO_STABLE); 187 } else if (action.equals(BluetoothDevice.ACTION_CONNECTION_ACCESS_REPLY)) { 188 mWakeLock.release(); 189 int val = intent.getIntExtra(BluetoothDevice.EXTRA_CONNECTION_ACCESS_RESULT, 190 BluetoothDevice.CONNECTION_ACCESS_NO); 191 Message msg = obtainMessage(CONNECTION_ACCESS_REQUEST_REPLY); 192 msg.arg1 = val; 193 sendMessage(msg); 194 } else if (action.equals(BluetoothDevice.ACTION_PAIRING_REQUEST)) { 195 mPairingRequestRcvd = true; 196 } else if (action.equals(BluetoothDevice.ACTION_BOND_STATE_CHANGED)) { 197 int state = intent.getIntExtra(BluetoothDevice.EXTRA_BOND_STATE, 198 BluetoothDevice.ERROR); 199 if (state == BluetoothDevice.BOND_BONDED && mPairingRequestRcvd) { 200 setTrust(BluetoothDevice.CONNECTION_ACCESS_YES); 201 mPairingRequestRcvd = false; 202 } else if (state == BluetoothDevice.BOND_NONE) { 203 mPairingRequestRcvd = false; 204 } 205 } 206 } 207 }; 208 isPhoneDocked(BluetoothDevice autoConnectDevice)209 private boolean isPhoneDocked(BluetoothDevice autoConnectDevice) { 210 // This works only because these broadcast intents are "sticky" 211 Intent i = mContext.registerReceiver(null, new IntentFilter(Intent.ACTION_DOCK_EVENT)); 212 if (i != null) { 213 int state = i.getIntExtra(Intent.EXTRA_DOCK_STATE, Intent.EXTRA_DOCK_STATE_UNDOCKED); 214 if (state != Intent.EXTRA_DOCK_STATE_UNDOCKED) { 215 BluetoothDevice device = i.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE); 216 if (device != null && autoConnectDevice.equals(device)) { 217 return true; 218 } 219 } 220 } 221 return false; 222 } 223 BluetoothDeviceProfileState(Context context, String address, BluetoothService service, BluetoothA2dpService a2dpService, boolean setTrust)224 public BluetoothDeviceProfileState(Context context, String address, 225 BluetoothService service, BluetoothA2dpService a2dpService, boolean setTrust) { 226 super(address); 227 mContext = context; 228 mDevice = new BluetoothDevice(address); 229 mService = service; 230 mA2dpService = a2dpService; 231 232 addState(mBondedDevice); 233 addState(mOutgoingHandsfree); 234 addState(mIncomingHandsfree); 235 addState(mIncomingA2dp); 236 addState(mOutgoingA2dp); 237 addState(mOutgoingHid); 238 addState(mIncomingHid); 239 setInitialState(mBondedDevice); 240 241 IntentFilter filter = new IntentFilter(); 242 // Fine-grained state broadcasts 243 filter.addAction(BluetoothDevice.ACTION_ACL_CONNECTED); 244 filter.addAction(BluetoothHeadset.ACTION_CONNECTION_STATE_CHANGED); 245 filter.addAction(BluetoothA2dp.ACTION_CONNECTION_STATE_CHANGED); 246 filter.addAction(BluetoothInputDevice.ACTION_CONNECTION_STATE_CHANGED); 247 filter.addAction(BluetoothDevice.ACTION_ACL_DISCONNECTED); 248 filter.addAction(BluetoothDevice.ACTION_CONNECTION_ACCESS_REPLY); 249 filter.addAction(BluetoothDevice.ACTION_PAIRING_REQUEST); 250 filter.addAction(BluetoothDevice.ACTION_BOND_STATE_CHANGED); 251 252 mContext.registerReceiver(mBroadcastReceiver, filter); 253 254 mAdapter = BluetoothAdapter.getDefaultAdapter(); 255 mAdapter.getProfileProxy(mContext, mBluetoothProfileServiceListener, 256 BluetoothProfile.HEADSET); 257 // TODO(): Convert PBAP to the new Profile APIs. 258 mPbap = new PbapServiceListener(); 259 260 mIncomingConnections = mService.getIncomingState(address); 261 mIncomingRejectTimer = readTimerValue(); 262 mPowerManager = (PowerManager)mContext.getSystemService(Context.POWER_SERVICE); 263 mWakeLock = mPowerManager.newWakeLock(PowerManager.SCREEN_DIM_WAKE_LOCK | 264 PowerManager.ACQUIRE_CAUSES_WAKEUP | 265 PowerManager.ON_AFTER_RELEASE, TAG); 266 mWakeLock.setReferenceCounted(false); 267 268 if (setTrust) { 269 setTrust(BluetoothDevice.CONNECTION_ACCESS_YES); 270 } 271 } 272 273 private BluetoothProfile.ServiceListener mBluetoothProfileServiceListener = 274 new BluetoothProfile.ServiceListener() { 275 public void onServiceConnected(int profile, BluetoothProfile proxy) { 276 synchronized(BluetoothDeviceProfileState.this) { 277 mHeadsetService = (BluetoothHeadset) proxy; 278 if (mAutoConnectionPending) { 279 sendMessage(AUTO_CONNECT_PROFILES); 280 mAutoConnectionPending = false; 281 } 282 } 283 } 284 public void onServiceDisconnected(int profile) { 285 synchronized(BluetoothDeviceProfileState.this) { 286 mHeadsetService = null; 287 } 288 } 289 }; 290 291 private class PbapServiceListener implements BluetoothPbap.ServiceListener { PbapServiceListener()292 public PbapServiceListener() { 293 mPbapService = new BluetoothPbap(mContext, this); 294 } onServiceConnected()295 public void onServiceConnected() { 296 synchronized(BluetoothDeviceProfileState.this) { 297 mPbapServiceConnected = true; 298 } 299 } onServiceDisconnected()300 public void onServiceDisconnected() { 301 synchronized(BluetoothDeviceProfileState.this) { 302 mPbapServiceConnected = false; 303 } 304 } 305 } 306 307 private class BondedDevice extends State { 308 @Override enter()309 public void enter() { 310 Log.i(TAG, "Entering ACL Connected state with: " + getCurrentMessage().what); 311 Message m = new Message(); 312 m.copyFrom(getCurrentMessage()); 313 sendMessageAtFrontOfQueue(m); 314 } 315 @Override processMessage(Message message)316 public boolean processMessage(Message message) { 317 log("ACL Connected State -> Processing Message: " + message.what); 318 switch(message.what) { 319 case CONNECT_HFP_OUTGOING: 320 case DISCONNECT_HFP_OUTGOING: 321 transitionTo(mOutgoingHandsfree); 322 break; 323 case CONNECT_HFP_INCOMING: 324 transitionTo(mIncomingHandsfree); 325 break; 326 case DISCONNECT_HFP_INCOMING: 327 transitionTo(mIncomingHandsfree); 328 break; 329 case CONNECT_A2DP_OUTGOING: 330 case DISCONNECT_A2DP_OUTGOING: 331 transitionTo(mOutgoingA2dp); 332 break; 333 case CONNECT_A2DP_INCOMING: 334 case DISCONNECT_A2DP_INCOMING: 335 transitionTo(mIncomingA2dp); 336 break; 337 case CONNECT_HID_OUTGOING: 338 case DISCONNECT_HID_OUTGOING: 339 transitionTo(mOutgoingHid); 340 break; 341 case CONNECT_HID_INCOMING: 342 case DISCONNECT_HID_INCOMING: 343 transitionTo(mIncomingHid); 344 break; 345 case DISCONNECT_PBAP_OUTGOING: 346 processCommand(DISCONNECT_PBAP_OUTGOING); 347 break; 348 case UNPAIR: 349 if (mHeadsetState != BluetoothHeadset.STATE_DISCONNECTED) { 350 sendMessage(DISCONNECT_HFP_OUTGOING); 351 deferMessage(message); 352 break; 353 } else if (mA2dpState != BluetoothA2dp.STATE_DISCONNECTED) { 354 sendMessage(DISCONNECT_A2DP_OUTGOING); 355 deferMessage(message); 356 break; 357 } else if (mService.getInputDeviceConnectionState(mDevice) != 358 BluetoothInputDevice.STATE_DISCONNECTED) { 359 sendMessage(DISCONNECT_HID_OUTGOING); 360 deferMessage(message); 361 break; 362 } 363 processCommand(UNPAIR); 364 break; 365 case AUTO_CONNECT_PROFILES: 366 if (isPhoneDocked(mDevice)) { 367 // Don't auto connect to docks. 368 break; 369 } else { 370 if (mHeadsetService == null) { 371 mAutoConnectionPending = true; 372 } else if (mHeadsetService.getPriority(mDevice) == 373 BluetoothHeadset.PRIORITY_AUTO_CONNECT && 374 mHeadsetService.getDevicesMatchingConnectionStates( 375 new int[] {BluetoothProfile.STATE_CONNECTED, 376 BluetoothProfile.STATE_CONNECTING, 377 BluetoothProfile.STATE_DISCONNECTING}).size() == 0) { 378 mHeadsetService.connect(mDevice); 379 } 380 if (mA2dpService != null && 381 mA2dpService.getPriority(mDevice) == 382 BluetoothA2dp.PRIORITY_AUTO_CONNECT && 383 mA2dpService.getDevicesMatchingConnectionStates( 384 new int[] {BluetoothA2dp.STATE_CONNECTED, 385 BluetoothProfile.STATE_CONNECTING, 386 BluetoothProfile.STATE_DISCONNECTING}).size() == 0) { 387 mA2dpService.connect(mDevice); 388 } 389 if (mService.getInputDevicePriority(mDevice) == 390 BluetoothInputDevice.PRIORITY_AUTO_CONNECT) { 391 mService.connectInputDevice(mDevice); 392 } 393 } 394 break; 395 case CONNECT_OTHER_PROFILES: 396 if (isPhoneDocked(mDevice)) { 397 break; 398 } 399 if (message.arg1 == CONNECT_A2DP_OUTGOING) { 400 if (mA2dpService != null && 401 mA2dpService.getConnectedDevices().size() == 0) { 402 Log.i(TAG, "A2dp:Connect Other Profiles"); 403 mA2dpService.connect(mDevice); 404 } 405 } else if (message.arg1 == CONNECT_HFP_OUTGOING) { 406 if (mHeadsetService == null) { 407 deferMessage(message); 408 } else { 409 if (mHeadsetService.getConnectedDevices().size() == 0) { 410 Log.i(TAG, "Headset:Connect Other Profiles"); 411 mHeadsetService.connect(mDevice); 412 } 413 } 414 } 415 break; 416 case TRANSITION_TO_STABLE: 417 // ignore. 418 break; 419 case SM_QUIT_CMD: 420 mContext.unregisterReceiver(mBroadcastReceiver); 421 mBroadcastReceiver = null; 422 mAdapter.closeProfileProxy(BluetoothProfile.HEADSET, mHeadsetService); 423 mBluetoothProfileServiceListener = null; 424 mOutgoingHandsfree = null; 425 mPbap = null; 426 mPbapService.close(); 427 mPbapService = null; 428 mIncomingHid = null; 429 mOutgoingHid = null; 430 mIncomingHandsfree = null; 431 mOutgoingHandsfree = null; 432 mIncomingA2dp = null; 433 mOutgoingA2dp = null; 434 mBondedDevice = null; 435 // There is a problem in the State Machine code 436 // where things are not cleaned up properly, when quit message 437 // is handled so return NOT_HANDLED as a workaround. 438 return NOT_HANDLED; 439 default: 440 return NOT_HANDLED; 441 } 442 return HANDLED; 443 } 444 } 445 446 private class OutgoingHandsfree extends State { 447 private boolean mStatus = false; 448 private int mCommand; 449 450 @Override enter()451 public void enter() { 452 Log.i(TAG, "Entering OutgoingHandsfree state with: " + getCurrentMessage().what); 453 mCommand = getCurrentMessage().what; 454 if (mCommand != CONNECT_HFP_OUTGOING && 455 mCommand != DISCONNECT_HFP_OUTGOING) { 456 Log.e(TAG, "Error: OutgoingHandsfree state with command:" + mCommand); 457 } 458 mStatus = processCommand(mCommand); 459 if (!mStatus) { 460 sendMessage(TRANSITION_TO_STABLE); 461 mService.sendProfileStateMessage(BluetoothProfileState.HFP, 462 BluetoothProfileState.TRANSITION_TO_STABLE); 463 } 464 } 465 466 @Override processMessage(Message message)467 public boolean processMessage(Message message) { 468 log("OutgoingHandsfree State -> Processing Message: " + message.what); 469 Message deferMsg = new Message(); 470 int command = message.what; 471 switch(command) { 472 case CONNECT_HFP_OUTGOING: 473 if (command != mCommand) { 474 // Disconnect followed by a connect - defer 475 deferMessage(message); 476 } 477 break; 478 case CONNECT_HFP_INCOMING: 479 if (mCommand == CONNECT_HFP_OUTGOING) { 480 // Cancel outgoing connect, accept incoming 481 cancelCommand(CONNECT_HFP_OUTGOING); 482 transitionTo(mIncomingHandsfree); 483 } else { 484 // We have done the disconnect but we are not 485 // sure which state we are in at this point. 486 deferMessage(message); 487 } 488 break; 489 case CONNECT_A2DP_INCOMING: 490 // accept incoming A2DP, retry HFP_OUTGOING 491 transitionTo(mIncomingA2dp); 492 493 if (mStatus) { 494 deferMsg.what = mCommand; 495 deferMessage(deferMsg); 496 } 497 break; 498 case CONNECT_A2DP_OUTGOING: 499 deferMessage(message); 500 break; 501 case DISCONNECT_HFP_OUTGOING: 502 if (mCommand == CONNECT_HFP_OUTGOING) { 503 // Cancel outgoing connect 504 cancelCommand(CONNECT_HFP_OUTGOING); 505 processCommand(DISCONNECT_HFP_OUTGOING); 506 } 507 // else ignore 508 break; 509 case DISCONNECT_HFP_INCOMING: 510 // When this happens the socket would be closed and the headset 511 // state moved to DISCONNECTED, cancel the outgoing thread. 512 // if it still is in CONNECTING state 513 cancelCommand(CONNECT_HFP_OUTGOING); 514 break; 515 case DISCONNECT_A2DP_OUTGOING: 516 deferMessage(message); 517 break; 518 case DISCONNECT_A2DP_INCOMING: 519 // Bluez will handle the disconnect. If because of this the outgoing 520 // handsfree connection has failed, then retry. 521 if (mStatus) { 522 deferMsg.what = mCommand; 523 deferMessage(deferMsg); 524 } 525 break; 526 case CONNECT_HID_OUTGOING: 527 case DISCONNECT_HID_OUTGOING: 528 deferMessage(message); 529 break; 530 case CONNECT_HID_INCOMING: 531 transitionTo(mIncomingHid); 532 if (mStatus) { 533 deferMsg.what = mCommand; 534 deferMessage(deferMsg); 535 } 536 break; 537 case DISCONNECT_HID_INCOMING: 538 if (mStatus) { 539 deferMsg.what = mCommand; 540 deferMessage(deferMsg); 541 } 542 break; // ignore 543 case DISCONNECT_PBAP_OUTGOING: 544 case UNPAIR: 545 case AUTO_CONNECT_PROFILES: 546 case CONNECT_OTHER_PROFILES: 547 deferMessage(message); 548 break; 549 case TRANSITION_TO_STABLE: 550 transitionTo(mBondedDevice); 551 break; 552 default: 553 return NOT_HANDLED; 554 } 555 return HANDLED; 556 } 557 } 558 559 private class IncomingHandsfree extends State { 560 private boolean mStatus = false; 561 private int mCommand; 562 563 @Override enter()564 public void enter() { 565 Log.i(TAG, "Entering IncomingHandsfree state with: " + getCurrentMessage().what); 566 mCommand = getCurrentMessage().what; 567 if (mCommand != CONNECT_HFP_INCOMING && 568 mCommand != DISCONNECT_HFP_INCOMING) { 569 Log.e(TAG, "Error: IncomingHandsfree state with command:" + mCommand); 570 } 571 mStatus = processCommand(mCommand); 572 if (!mStatus) { 573 sendMessage(TRANSITION_TO_STABLE); 574 mService.sendProfileStateMessage(BluetoothProfileState.HFP, 575 BluetoothProfileState.TRANSITION_TO_STABLE); 576 } 577 } 578 579 @Override processMessage(Message message)580 public boolean processMessage(Message message) { 581 log("IncomingHandsfree State -> Processing Message: " + message.what); 582 switch(message.what) { 583 case CONNECT_HFP_OUTGOING: 584 deferMessage(message); 585 break; 586 case CONNECT_HFP_INCOMING: 587 // Ignore 588 Log.e(TAG, "Error: Incoming connection with a pending incoming connection"); 589 break; 590 case CONNECTION_ACCESS_REQUEST_REPLY: 591 int val = message.arg1; 592 mConnectionAccessReplyReceived = true; 593 boolean value = false; 594 if (val == BluetoothDevice.CONNECTION_ACCESS_YES) { 595 value = true; 596 } 597 setTrust(val); 598 599 handleIncomingConnection(CONNECT_HFP_INCOMING, value); 600 break; 601 case CONNECTION_ACCESS_REQUEST_EXPIRY: 602 if (!mConnectionAccessReplyReceived) { 603 handleIncomingConnection(CONNECT_HFP_INCOMING, false); 604 sendConnectionAccessRemovalIntent(); 605 sendMessage(TRANSITION_TO_STABLE); 606 } 607 break; 608 case CONNECT_A2DP_INCOMING: 609 // Serialize the commands. 610 deferMessage(message); 611 break; 612 case CONNECT_A2DP_OUTGOING: 613 deferMessage(message); 614 break; 615 case DISCONNECT_HFP_OUTGOING: 616 // We don't know at what state we are in the incoming HFP connection state. 617 // We can be changing from DISCONNECTED to CONNECTING, or 618 // from CONNECTING to CONNECTED, so serializing this command is 619 // the safest option. 620 deferMessage(message); 621 break; 622 case DISCONNECT_HFP_INCOMING: 623 // Nothing to do here, we will already be DISCONNECTED 624 // by this point. 625 break; 626 case DISCONNECT_A2DP_OUTGOING: 627 deferMessage(message); 628 break; 629 case DISCONNECT_A2DP_INCOMING: 630 // Bluez handles incoming A2DP disconnect. 631 // If this causes incoming HFP to fail, it is more of a headset problem 632 // since both connections are incoming ones. 633 break; 634 case CONNECT_HID_OUTGOING: 635 case DISCONNECT_HID_OUTGOING: 636 deferMessage(message); 637 break; 638 case CONNECT_HID_INCOMING: 639 case DISCONNECT_HID_INCOMING: 640 break; // ignore 641 case DISCONNECT_PBAP_OUTGOING: 642 case UNPAIR: 643 case AUTO_CONNECT_PROFILES: 644 case CONNECT_OTHER_PROFILES: 645 deferMessage(message); 646 break; 647 case TRANSITION_TO_STABLE: 648 transitionTo(mBondedDevice); 649 break; 650 default: 651 return NOT_HANDLED; 652 } 653 return HANDLED; 654 } 655 } 656 657 private class OutgoingA2dp extends State { 658 private boolean mStatus = false; 659 private int mCommand; 660 661 @Override enter()662 public void enter() { 663 Log.i(TAG, "Entering OutgoingA2dp state with: " + getCurrentMessage().what); 664 mCommand = getCurrentMessage().what; 665 if (mCommand != CONNECT_A2DP_OUTGOING && 666 mCommand != DISCONNECT_A2DP_OUTGOING) { 667 Log.e(TAG, "Error: OutgoingA2DP state with command:" + mCommand); 668 } 669 mStatus = processCommand(mCommand); 670 if (!mStatus) { 671 sendMessage(TRANSITION_TO_STABLE); 672 mService.sendProfileStateMessage(BluetoothProfileState.A2DP, 673 BluetoothProfileState.TRANSITION_TO_STABLE); 674 } 675 } 676 677 @Override processMessage(Message message)678 public boolean processMessage(Message message) { 679 log("OutgoingA2dp State->Processing Message: " + message.what); 680 Message deferMsg = new Message(); 681 switch(message.what) { 682 case CONNECT_HFP_OUTGOING: 683 processCommand(CONNECT_HFP_OUTGOING); 684 685 // Don't cancel A2DP outgoing as there is no guarantee it 686 // will get canceled. 687 // It might already be connected but we might not have got the 688 // A2DP_SINK_STATE_CHANGE. Hence, no point disconnecting here. 689 // The worst case, the connection will fail, retry. 690 // The same applies to Disconnecting an A2DP connection. 691 if (mStatus) { 692 deferMsg.what = mCommand; 693 deferMessage(deferMsg); 694 } 695 break; 696 case CONNECT_HFP_INCOMING: 697 processCommand(CONNECT_HFP_INCOMING); 698 699 // Don't cancel A2DP outgoing as there is no guarantee 700 // it will get canceled. 701 // The worst case, the connection will fail, retry. 702 if (mStatus) { 703 deferMsg.what = mCommand; 704 deferMessage(deferMsg); 705 } 706 break; 707 case CONNECT_A2DP_INCOMING: 708 // Bluez will take care of conflicts between incoming and outgoing 709 // connections. 710 transitionTo(mIncomingA2dp); 711 break; 712 case CONNECT_A2DP_OUTGOING: 713 // Ignore 714 break; 715 case DISCONNECT_HFP_OUTGOING: 716 deferMessage(message); 717 break; 718 case DISCONNECT_HFP_INCOMING: 719 // At this point, we are already disconnected 720 // with HFP. Sometimes A2DP connection can 721 // fail due to the disconnection of HFP. So add a retry 722 // for the A2DP. 723 if (mStatus) { 724 deferMsg.what = mCommand; 725 deferMessage(deferMsg); 726 } 727 break; 728 case DISCONNECT_A2DP_OUTGOING: 729 deferMessage(message); 730 break; 731 case DISCONNECT_A2DP_INCOMING: 732 // Ignore, will be handled by Bluez 733 break; 734 case CONNECT_HID_OUTGOING: 735 case DISCONNECT_HID_OUTGOING: 736 deferMessage(message); 737 break; 738 case CONNECT_HID_INCOMING: 739 transitionTo(mIncomingHid); 740 if (mStatus) { 741 deferMsg.what = mCommand; 742 deferMessage(deferMsg); 743 } 744 break; 745 case DISCONNECT_HID_INCOMING: 746 if (mStatus) { 747 deferMsg.what = mCommand; 748 deferMessage(deferMsg); 749 } 750 break; // ignore 751 case DISCONNECT_PBAP_OUTGOING: 752 case UNPAIR: 753 case AUTO_CONNECT_PROFILES: 754 case CONNECT_OTHER_PROFILES: 755 deferMessage(message); 756 break; 757 case TRANSITION_TO_STABLE: 758 transitionTo(mBondedDevice); 759 break; 760 default: 761 return NOT_HANDLED; 762 } 763 return HANDLED; 764 } 765 } 766 767 private class IncomingA2dp extends State { 768 private boolean mStatus = false; 769 private int mCommand; 770 771 @Override enter()772 public void enter() { 773 Log.i(TAG, "Entering IncomingA2dp state with: " + getCurrentMessage().what); 774 mCommand = getCurrentMessage().what; 775 if (mCommand != CONNECT_A2DP_INCOMING && 776 mCommand != DISCONNECT_A2DP_INCOMING) { 777 Log.e(TAG, "Error: IncomingA2DP state with command:" + mCommand); 778 } 779 mStatus = processCommand(mCommand); 780 if (!mStatus) { 781 sendMessage(TRANSITION_TO_STABLE); 782 mService.sendProfileStateMessage(BluetoothProfileState.A2DP, 783 BluetoothProfileState.TRANSITION_TO_STABLE); 784 } 785 } 786 787 @Override processMessage(Message message)788 public boolean processMessage(Message message) { 789 log("IncomingA2dp State->Processing Message: " + message.what); 790 switch(message.what) { 791 case CONNECT_HFP_OUTGOING: 792 deferMessage(message); 793 break; 794 case CONNECT_HFP_INCOMING: 795 // Shouldn't happen, but serialize the commands. 796 deferMessage(message); 797 break; 798 case CONNECT_A2DP_INCOMING: 799 // ignore 800 break; 801 case CONNECTION_ACCESS_REQUEST_REPLY: 802 int val = message.arg1; 803 mConnectionAccessReplyReceived = true; 804 boolean value = false; 805 if (val == BluetoothDevice.CONNECTION_ACCESS_YES) { 806 value = true; 807 } 808 setTrust(val); 809 handleIncomingConnection(CONNECT_A2DP_INCOMING, value); 810 break; 811 case CONNECTION_ACCESS_REQUEST_EXPIRY: 812 // The check protects the race condition between REQUEST_REPLY 813 // and the timer expiry. 814 if (!mConnectionAccessReplyReceived) { 815 handleIncomingConnection(CONNECT_A2DP_INCOMING, false); 816 sendConnectionAccessRemovalIntent(); 817 sendMessage(TRANSITION_TO_STABLE); 818 } 819 break; 820 case CONNECT_A2DP_OUTGOING: 821 // Defer message and retry 822 deferMessage(message); 823 break; 824 case DISCONNECT_HFP_OUTGOING: 825 deferMessage(message); 826 break; 827 case DISCONNECT_HFP_INCOMING: 828 // Shouldn't happen but if does, we can handle it. 829 // Depends if the headset can handle it. 830 // Incoming A2DP will be handled by Bluez, Disconnect HFP 831 // the socket would have already been closed. 832 // ignore 833 break; 834 case DISCONNECT_A2DP_OUTGOING: 835 deferMessage(message); 836 break; 837 case DISCONNECT_A2DP_INCOMING: 838 // Ignore, will be handled by Bluez 839 break; 840 case CONNECT_HID_OUTGOING: 841 case DISCONNECT_HID_OUTGOING: 842 deferMessage(message); 843 break; 844 case CONNECT_HID_INCOMING: 845 case DISCONNECT_HID_INCOMING: 846 break; // ignore 847 case DISCONNECT_PBAP_OUTGOING: 848 case UNPAIR: 849 case AUTO_CONNECT_PROFILES: 850 case CONNECT_OTHER_PROFILES: 851 deferMessage(message); 852 break; 853 case TRANSITION_TO_STABLE: 854 transitionTo(mBondedDevice); 855 break; 856 default: 857 return NOT_HANDLED; 858 } 859 return HANDLED; 860 } 861 } 862 863 864 private class OutgoingHid extends State { 865 private boolean mStatus = false; 866 private int mCommand; 867 868 @Override enter()869 public void enter() { 870 log("Entering OutgoingHid state with: " + getCurrentMessage().what); 871 mCommand = getCurrentMessage().what; 872 if (mCommand != CONNECT_HID_OUTGOING && 873 mCommand != DISCONNECT_HID_OUTGOING) { 874 Log.e(TAG, "Error: OutgoingHid state with command:" + mCommand); 875 } 876 mStatus = processCommand(mCommand); 877 if (!mStatus) sendMessage(TRANSITION_TO_STABLE); 878 } 879 880 @Override processMessage(Message message)881 public boolean processMessage(Message message) { 882 log("OutgoingHid State->Processing Message: " + message.what); 883 Message deferMsg = new Message(); 884 switch(message.what) { 885 // defer all outgoing messages 886 case CONNECT_HFP_OUTGOING: 887 case CONNECT_A2DP_OUTGOING: 888 case CONNECT_HID_OUTGOING: 889 case DISCONNECT_HFP_OUTGOING: 890 case DISCONNECT_A2DP_OUTGOING: 891 case DISCONNECT_HID_OUTGOING: 892 deferMessage(message); 893 break; 894 895 case CONNECT_HFP_INCOMING: 896 transitionTo(mIncomingHandsfree); 897 break; 898 case CONNECT_A2DP_INCOMING: 899 transitionTo(mIncomingA2dp); 900 901 // Don't cancel HID outgoing as there is no guarantee it 902 // will get canceled. 903 // It might already be connected but we might not have got the 904 // INPUT_DEVICE_STATE_CHANGE. Hence, no point disconnecting here. 905 // The worst case, the connection will fail, retry. 906 if (mStatus) { 907 deferMsg.what = mCommand; 908 deferMessage(deferMsg); 909 } 910 break; 911 case CONNECT_HID_INCOMING: 912 // Bluez will take care of the conflicts 913 transitionTo(mIncomingHid); 914 break; 915 916 case DISCONNECT_HFP_INCOMING: 917 case DISCONNECT_A2DP_INCOMING: 918 // At this point, we are already disconnected 919 // with HFP. Sometimes HID connection can 920 // fail due to the disconnection of HFP. So add a retry 921 // for the HID. 922 if (mStatus) { 923 deferMsg.what = mCommand; 924 deferMessage(deferMsg); 925 } 926 break; 927 case DISCONNECT_HID_INCOMING: 928 // Ignore, will be handled by Bluez 929 break; 930 case DISCONNECT_PBAP_OUTGOING: 931 case UNPAIR: 932 case AUTO_CONNECT_PROFILES: 933 deferMessage(message); 934 break; 935 case TRANSITION_TO_STABLE: 936 transitionTo(mBondedDevice); 937 break; 938 default: 939 return NOT_HANDLED; 940 } 941 return HANDLED; 942 } 943 } 944 945 private class IncomingHid extends State { 946 private boolean mStatus = false; 947 private int mCommand; 948 949 @Override enter()950 public void enter() { 951 log("Entering IncomingHid state with: " + getCurrentMessage().what); 952 mCommand = getCurrentMessage().what; 953 if (mCommand != CONNECT_HID_INCOMING && 954 mCommand != DISCONNECT_HID_INCOMING) { 955 Log.e(TAG, "Error: IncomingHid state with command:" + mCommand); 956 } 957 mStatus = processCommand(mCommand); 958 if (!mStatus) sendMessage(TRANSITION_TO_STABLE); 959 } 960 961 @Override processMessage(Message message)962 public boolean processMessage(Message message) { 963 log("IncomingHid State->Processing Message: " + message.what); 964 Message deferMsg = new Message(); 965 switch(message.what) { 966 case CONNECT_HFP_OUTGOING: 967 case CONNECT_HFP_INCOMING: 968 case DISCONNECT_HFP_OUTGOING: 969 case CONNECT_A2DP_INCOMING: 970 case CONNECT_A2DP_OUTGOING: 971 case DISCONNECT_A2DP_OUTGOING: 972 case CONNECT_HID_OUTGOING: 973 case CONNECT_HID_INCOMING: 974 case DISCONNECT_HID_OUTGOING: 975 deferMessage(message); 976 break; 977 case CONNECTION_ACCESS_REQUEST_REPLY: 978 mConnectionAccessReplyReceived = true; 979 int val = message.arg1; 980 setTrust(val); 981 handleIncomingConnection(CONNECT_HID_INCOMING, 982 val == BluetoothDevice.CONNECTION_ACCESS_YES); 983 break; 984 case CONNECTION_ACCESS_REQUEST_EXPIRY: 985 if (!mConnectionAccessReplyReceived) { 986 handleIncomingConnection(CONNECT_HID_INCOMING, false); 987 sendConnectionAccessRemovalIntent(); 988 sendMessage(TRANSITION_TO_STABLE); 989 } 990 break; 991 case DISCONNECT_HFP_INCOMING: 992 // Shouldn't happen but if does, we can handle it. 993 // Depends if the headset can handle it. 994 // Incoming HID will be handled by Bluez, Disconnect HFP 995 // the socket would have already been closed. 996 // ignore 997 break; 998 case DISCONNECT_HID_INCOMING: 999 case DISCONNECT_A2DP_INCOMING: 1000 // Ignore, will be handled by Bluez 1001 break; 1002 case DISCONNECT_PBAP_OUTGOING: 1003 case UNPAIR: 1004 case AUTO_CONNECT_PROFILES: 1005 deferMessage(message); 1006 break; 1007 case TRANSITION_TO_STABLE: 1008 transitionTo(mBondedDevice); 1009 break; 1010 default: 1011 return NOT_HANDLED; 1012 } 1013 return HANDLED; 1014 } 1015 } 1016 1017 cancelCommand(int command)1018 synchronized void cancelCommand(int command) { 1019 if (command == CONNECT_HFP_OUTGOING ) { 1020 // Cancel the outgoing thread. 1021 if (mHeadsetService != null) { 1022 mHeadsetService.cancelConnectThread(); 1023 } 1024 // HeadsetService is down. Phone process most likely crashed. 1025 // The thread would have got killed. 1026 } 1027 } 1028 deferProfileServiceMessage(int command)1029 synchronized void deferProfileServiceMessage(int command) { 1030 Message msg = new Message(); 1031 msg.what = command; 1032 deferMessage(msg); 1033 } 1034 updateIncomingAllowedTimer()1035 private void updateIncomingAllowedTimer() { 1036 // Not doing a perfect exponential backoff because 1037 // we want two different rates. For all practical 1038 // purposes, this is good enough. 1039 if (mIncomingRejectTimer == 0) mIncomingRejectTimer = INIT_INCOMING_REJECT_TIMER; 1040 1041 mIncomingRejectTimer *= 5; 1042 if (mIncomingRejectTimer > MAX_INCOMING_REJECT_TIMER) { 1043 mIncomingRejectTimer = MAX_INCOMING_REJECT_TIMER; 1044 } 1045 writeTimerValue(mIncomingRejectTimer); 1046 } 1047 handleIncomingConnection(int command, boolean accept)1048 private boolean handleIncomingConnection(int command, boolean accept) { 1049 boolean ret = false; 1050 Log.i(TAG, "handleIncomingConnection:" + command + ":" + accept); 1051 switch (command) { 1052 case CONNECT_HFP_INCOMING: 1053 if (!accept) { 1054 ret = mHeadsetService.rejectIncomingConnect(mDevice); 1055 sendMessage(TRANSITION_TO_STABLE); 1056 updateIncomingAllowedTimer(); 1057 } else if (mHeadsetState == BluetoothHeadset.STATE_CONNECTING) { 1058 writeTimerValue(0); 1059 ret = mHeadsetService.acceptIncomingConnect(mDevice); 1060 } else if (mHeadsetState == BluetoothHeadset.STATE_DISCONNECTED) { 1061 writeTimerValue(0); 1062 handleConnectionOfOtherProfiles(command); 1063 ret = mHeadsetService.createIncomingConnect(mDevice); 1064 } 1065 break; 1066 case CONNECT_A2DP_INCOMING: 1067 if (!accept) { 1068 ret = mA2dpService.allowIncomingConnect(mDevice, false); 1069 sendMessage(TRANSITION_TO_STABLE); 1070 updateIncomingAllowedTimer(); 1071 } else { 1072 writeTimerValue(0); 1073 ret = mA2dpService.allowIncomingConnect(mDevice, true); 1074 handleConnectionOfOtherProfiles(command); 1075 } 1076 break; 1077 case CONNECT_HID_INCOMING: 1078 if (!accept) { 1079 ret = mService.allowIncomingProfileConnect(mDevice, false); 1080 sendMessage(TRANSITION_TO_STABLE); 1081 updateIncomingAllowedTimer(); 1082 } else { 1083 writeTimerValue(0); 1084 ret = mService.allowIncomingProfileConnect(mDevice, true); 1085 } 1086 break; 1087 default: 1088 Log.e(TAG, "Waiting for incoming connection but state changed to:" + command); 1089 break; 1090 } 1091 return ret; 1092 } 1093 sendConnectionAccessIntent()1094 private void sendConnectionAccessIntent() { 1095 mConnectionAccessReplyReceived = false; 1096 1097 if (!mPowerManager.isScreenOn()) mWakeLock.acquire(); 1098 1099 Intent intent = new Intent(BluetoothDevice.ACTION_CONNECTION_ACCESS_REQUEST); 1100 intent.setClassName(ACCESS_AUTHORITY_PACKAGE, ACCESS_AUTHORITY_CLASS); 1101 intent.putExtra(BluetoothDevice.EXTRA_ACCESS_REQUEST_TYPE, 1102 BluetoothDevice.REQUEST_TYPE_PROFILE_CONNECTION); 1103 intent.putExtra(BluetoothDevice.EXTRA_DEVICE, mDevice); 1104 mContext.sendBroadcast(intent, BLUETOOTH_ADMIN_PERM); 1105 } 1106 sendConnectionAccessRemovalIntent()1107 private void sendConnectionAccessRemovalIntent() { 1108 mWakeLock.release(); 1109 Intent intent = new Intent(BluetoothDevice.ACTION_CONNECTION_ACCESS_CANCEL); 1110 intent.putExtra(BluetoothDevice.EXTRA_DEVICE, mDevice); 1111 mContext.sendBroadcast(intent, BLUETOOTH_ADMIN_PERM); 1112 } 1113 getTrust()1114 private int getTrust() { 1115 String address = mDevice.getAddress(); 1116 if (mIncomingConnections != null) return mIncomingConnections.first; 1117 return CONNECTION_ACCESS_UNDEFINED; 1118 } 1119 1120 getStringValue(long value)1121 private String getStringValue(long value) { 1122 StringBuilder sbr = new StringBuilder(); 1123 sbr.append(Long.toString(System.currentTimeMillis())); 1124 sbr.append("-"); 1125 sbr.append(Long.toString(value)); 1126 return sbr.toString(); 1127 } 1128 setTrust(int value)1129 private void setTrust(int value) { 1130 String second; 1131 if (mIncomingConnections == null) { 1132 second = getStringValue(INIT_INCOMING_REJECT_TIMER); 1133 } else { 1134 second = mIncomingConnections.second; 1135 } 1136 1137 mIncomingConnections = new Pair(value, second); 1138 mService.writeIncomingConnectionState(mDevice.getAddress(), mIncomingConnections); 1139 } 1140 writeTimerValue(long value)1141 private void writeTimerValue(long value) { 1142 Integer first; 1143 if (mIncomingConnections == null) { 1144 first = CONNECTION_ACCESS_UNDEFINED; 1145 } else { 1146 first = mIncomingConnections.first; 1147 } 1148 mIncomingConnections = new Pair(first, getStringValue(value)); 1149 mService.writeIncomingConnectionState(mDevice.getAddress(), mIncomingConnections); 1150 } 1151 readTimerValue()1152 private long readTimerValue() { 1153 if (mIncomingConnections == null) 1154 return 0; 1155 String value = mIncomingConnections.second; 1156 String[] splits = value.split("-"); 1157 if (splits != null && splits.length == 2) { 1158 return Long.parseLong(splits[1]); 1159 } 1160 return 0; 1161 } 1162 readIncomingAllowedValue()1163 private boolean readIncomingAllowedValue() { 1164 if (readTimerValue() == 0) return true; 1165 String value = mIncomingConnections.second; 1166 String[] splits = value.split("-"); 1167 if (splits != null && splits.length == 2) { 1168 long val1 = Long.parseLong(splits[0]); 1169 long val2 = Long.parseLong(splits[1]); 1170 if (val1 + val2 <= System.currentTimeMillis()) { 1171 return true; 1172 } 1173 } 1174 return false; 1175 } 1176 processCommand(int command)1177 synchronized boolean processCommand(int command) { 1178 log("Processing command:" + command); 1179 switch(command) { 1180 case CONNECT_HFP_OUTGOING: 1181 if (mHeadsetService == null) { 1182 deferProfileServiceMessage(command); 1183 } else { 1184 return mHeadsetService.connectHeadsetInternal(mDevice); 1185 } 1186 break; 1187 case CONNECT_HFP_INCOMING: 1188 if (mHeadsetService == null) { 1189 deferProfileServiceMessage(command); 1190 } else { 1191 processIncomingConnectCommand(command); 1192 return true; 1193 } 1194 break; 1195 case CONNECT_A2DP_OUTGOING: 1196 if (mA2dpService != null) { 1197 return mA2dpService.connectSinkInternal(mDevice); 1198 } 1199 break; 1200 case CONNECT_A2DP_INCOMING: 1201 processIncomingConnectCommand(command); 1202 return true; 1203 case CONNECT_HID_OUTGOING: 1204 return mService.connectInputDeviceInternal(mDevice); 1205 case CONNECT_HID_INCOMING: 1206 processIncomingConnectCommand(command); 1207 return true; 1208 case DISCONNECT_HFP_OUTGOING: 1209 if (mHeadsetService == null) { 1210 deferProfileServiceMessage(command); 1211 } else { 1212 // Disconnect PBAP 1213 // TODO(): Add PBAP to the state machine. 1214 Message m = new Message(); 1215 m.what = DISCONNECT_PBAP_OUTGOING; 1216 deferMessage(m); 1217 if (mHeadsetService.getPriority(mDevice) == 1218 BluetoothHeadset.PRIORITY_AUTO_CONNECT) { 1219 mHeadsetService.setPriority(mDevice, BluetoothHeadset.PRIORITY_ON); 1220 } 1221 return mHeadsetService.disconnectHeadsetInternal(mDevice); 1222 } 1223 break; 1224 case DISCONNECT_HFP_INCOMING: 1225 // ignore 1226 return true; 1227 case DISCONNECT_A2DP_INCOMING: 1228 // ignore 1229 return true; 1230 case DISCONNECT_A2DP_OUTGOING: 1231 if (mA2dpService != null) { 1232 if (mA2dpService.getPriority(mDevice) == 1233 BluetoothA2dp.PRIORITY_AUTO_CONNECT) { 1234 mA2dpService.setPriority(mDevice, BluetoothHeadset.PRIORITY_ON); 1235 } 1236 return mA2dpService.disconnectSinkInternal(mDevice); 1237 } 1238 break; 1239 case DISCONNECT_HID_INCOMING: 1240 // ignore 1241 return true; 1242 case DISCONNECT_HID_OUTGOING: 1243 if (mService.getInputDevicePriority(mDevice) == 1244 BluetoothInputDevice.PRIORITY_AUTO_CONNECT) { 1245 mService.setInputDevicePriority(mDevice, BluetoothInputDevice.PRIORITY_ON); 1246 } 1247 return mService.disconnectInputDeviceInternal(mDevice); 1248 case DISCONNECT_PBAP_OUTGOING: 1249 if (!mPbapServiceConnected) { 1250 deferProfileServiceMessage(command); 1251 } else { 1252 return mPbapService.disconnect(); 1253 } 1254 break; 1255 case UNPAIR: 1256 writeTimerValue(INIT_INCOMING_REJECT_TIMER); 1257 setTrust(CONNECTION_ACCESS_UNDEFINED); 1258 return mService.removeBondInternal(mDevice.getAddress()); 1259 default: 1260 Log.e(TAG, "Error: Unknown Command"); 1261 } 1262 return false; 1263 } 1264 processIncomingConnectCommand(int command)1265 private void processIncomingConnectCommand(int command) { 1266 // Check if device is already trusted 1267 int access = getTrust(); 1268 if (access == BluetoothDevice.CONNECTION_ACCESS_YES) { 1269 handleIncomingConnection(command, true); 1270 } else if (access == BluetoothDevice.CONNECTION_ACCESS_NO && 1271 !readIncomingAllowedValue()) { 1272 handleIncomingConnection(command, false); 1273 } else { 1274 sendConnectionAccessIntent(); 1275 Message msg = obtainMessage(CONNECTION_ACCESS_REQUEST_EXPIRY); 1276 sendMessageDelayed(msg, 1277 CONNECTION_ACCESS_REQUEST_EXPIRY_TIMEOUT); 1278 } 1279 } 1280 handleConnectionOfOtherProfiles(int command)1281 private void handleConnectionOfOtherProfiles(int command) { 1282 // The white paper recommendations mentions that when there is a 1283 // link loss, it is the responsibility of the remote device to connect. 1284 // Many connect only 1 profile - and they connect the second profile on 1285 // some user action (like play being pressed) and so we need this code. 1286 // Auto Connect code only connects to the last connected device - which 1287 // is useful in cases like when the phone reboots. But consider the 1288 // following case: 1289 // User is connected to the car's phone and A2DP profile. 1290 // User comes to the desk and places the phone in the dock 1291 // (or any speaker or music system or even another headset) and thus 1292 // gets connected to the A2DP profile. User goes back to the car. 1293 // Ideally the car's system is supposed to send incoming connections 1294 // from both Handsfree and A2DP profile. But they don't. The Auto 1295 // connect code, will not work here because we only auto connect to the 1296 // last connected device for that profile which in this case is the dock. 1297 // Now suppose a user is using 2 headsets simultaneously, one for the 1298 // phone profile one for the A2DP profile. If this is the use case, we 1299 // expect the user to use the preference to turn off the A2DP profile in 1300 // the Settings screen for the first headset. Else, after link loss, 1301 // there can be an incoming connection from the first headset which 1302 // might result in the connection of the A2DP profile (if the second 1303 // headset is slower) and thus the A2DP profile on the second headset 1304 // will never get connected. 1305 // 1306 // TODO(): Handle other profiles here. 1307 switch (command) { 1308 case CONNECT_HFP_INCOMING: 1309 // Connect A2DP if there is no incoming connection 1310 // If the priority is OFF - don't auto connect. 1311 if (mA2dpService.getPriority(mDevice) == BluetoothProfile.PRIORITY_ON || 1312 mA2dpService.getPriority(mDevice) == 1313 BluetoothProfile.PRIORITY_AUTO_CONNECT) { 1314 Message msg = new Message(); 1315 msg.what = CONNECT_OTHER_PROFILES; 1316 msg.arg1 = CONNECT_A2DP_OUTGOING; 1317 sendMessageDelayed(msg, CONNECT_OTHER_PROFILES_DELAY); 1318 } 1319 break; 1320 case CONNECT_A2DP_INCOMING: 1321 // This is again against spec. HFP incoming connections should be made 1322 // before A2DP, so we should not hit this case. But many devices 1323 // don't follow this. 1324 if (mHeadsetService != null && 1325 (mHeadsetService.getPriority(mDevice) == BluetoothProfile.PRIORITY_ON || 1326 mHeadsetService.getPriority(mDevice) == 1327 BluetoothProfile.PRIORITY_AUTO_CONNECT)) { 1328 Message msg = new Message(); 1329 msg.what = CONNECT_OTHER_PROFILES; 1330 msg.arg1 = CONNECT_HFP_OUTGOING; 1331 sendMessageDelayed(msg, CONNECT_OTHER_PROFILES_DELAY); 1332 } 1333 break; 1334 default: 1335 break; 1336 } 1337 1338 } 1339 getDevice()1340 /*package*/ BluetoothDevice getDevice() { 1341 return mDevice; 1342 } 1343 log(String message)1344 private void log(String message) { 1345 if (DBG) { 1346 Log.i(TAG, "Device:" + mDevice + " Message:" + message); 1347 } 1348 } 1349 } 1350