BluetoothDeviceProfileState.java revision adbda6f094bf957e2943f80ef63d4530f6fcfc5a
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 17package android.bluetooth; 18 19import android.content.BroadcastReceiver; 20import android.content.Context; 21import android.content.Intent; 22import android.content.IntentFilter; 23import android.os.Message; 24import android.server.BluetoothA2dpService; 25import android.server.BluetoothService; 26import android.util.Log; 27 28import com.android.internal.util.HierarchicalState; 29import com.android.internal.util.HierarchicalStateMachine; 30 31/** 32 * This class is the Profile connection state machine associated with a remote 33 * device. When the device bonds an instance of this class is created. 34 * This tracks incoming and outgoing connections of all the profiles. Incoming 35 * connections are preferred over outgoing connections and HFP preferred over 36 * A2DP. When the device is unbonded, the instance is removed. 37 * 38 * States: 39 * {@link BondedDevice}: This state represents a bonded device. When in this 40 * state none of the profiles are in transition states. 41 * 42 * {@link OutgoingHandsfree}: Handsfree profile connection is in a transition 43 * state because of a outgoing Connect or Disconnect. 44 * 45 * {@link IncomingHandsfree}: Handsfree profile connection is in a transition 46 * state because of a incoming Connect or Disconnect. 47 * 48 * {@link IncomingA2dp}: A2dp profile connection is in a transition 49 * state because of a incoming Connect or Disconnect. 50 * 51 * {@link OutgoingA2dp}: A2dp profile connection is in a transition 52 * state because of a outgoing Connect or Disconnect. 53 * 54 * Todo(): Write tests for this class, when the Android Mock support is completed. 55 * @hide 56 */ 57public final class BluetoothDeviceProfileState extends HierarchicalStateMachine { 58 private static final String TAG = "BluetoothDeviceProfileState"; 59 private static final boolean DBG = true; //STOPSHIP - Change to false 60 61 public static final int CONNECT_HFP_OUTGOING = 1; 62 public static final int CONNECT_HFP_INCOMING = 2; 63 public static final int CONNECT_A2DP_OUTGOING = 3; 64 public static final int CONNECT_A2DP_INCOMING = 4; 65 66 public static final int DISCONNECT_HFP_OUTGOING = 5; 67 private static final int DISCONNECT_HFP_INCOMING = 6; 68 public static final int DISCONNECT_A2DP_OUTGOING = 7; 69 public static final int DISCONNECT_A2DP_INCOMING = 8; 70 public static final int DISCONNECT_PBAP_OUTGOING = 9; 71 72 public static final int UNPAIR = 100; 73 public static final int AUTO_CONNECT_PROFILES = 101; 74 public static final int TRANSITION_TO_STABLE = 102; 75 76 private static final int AUTO_CONNECT_DELAY = 6000; // 6 secs 77 78 private BondedDevice mBondedDevice = new BondedDevice(); 79 private OutgoingHandsfree mOutgoingHandsfree = new OutgoingHandsfree(); 80 private IncomingHandsfree mIncomingHandsfree = new IncomingHandsfree(); 81 private IncomingA2dp mIncomingA2dp = new IncomingA2dp(); 82 private OutgoingA2dp mOutgoingA2dp = new OutgoingA2dp(); 83 84 private Context mContext; 85 private BluetoothService mService; 86 private BluetoothA2dpService mA2dpService; 87 private BluetoothHeadset mHeadsetService; 88 private BluetoothPbap mPbapService; 89 private boolean mHeadsetServiceConnected; 90 private boolean mPbapServiceConnected; 91 92 private BluetoothDevice mDevice; 93 private int mHeadsetState; 94 private int mA2dpState; 95 96 private BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() { 97 @Override 98 public void onReceive(Context context, Intent intent) { 99 String action = intent.getAction(); 100 BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE); 101 if (!device.equals(mDevice)) return; 102 103 if (action.equals(BluetoothHeadset.ACTION_STATE_CHANGED)) { 104 int newState = intent.getIntExtra(BluetoothHeadset.EXTRA_STATE, 0); 105 int oldState = intent.getIntExtra(BluetoothHeadset.EXTRA_PREVIOUS_STATE, 0); 106 int initiator = intent.getIntExtra( 107 BluetoothHeadset.EXTRA_DISCONNECT_INITIATOR, 108 BluetoothHeadset.LOCAL_DISCONNECT); 109 mHeadsetState = newState; 110 if (newState == BluetoothHeadset.STATE_DISCONNECTED && 111 initiator == BluetoothHeadset.REMOTE_DISCONNECT) { 112 sendMessage(DISCONNECT_HFP_INCOMING); 113 } 114 if (newState == BluetoothHeadset.STATE_CONNECTED || 115 newState == BluetoothHeadset.STATE_DISCONNECTED) { 116 sendMessage(TRANSITION_TO_STABLE); 117 } 118 } else if (action.equals(BluetoothA2dp.ACTION_SINK_STATE_CHANGED)) { 119 int newState = intent.getIntExtra(BluetoothA2dp.EXTRA_SINK_STATE, 0); 120 int oldState = intent.getIntExtra(BluetoothA2dp.EXTRA_PREVIOUS_SINK_STATE, 0); 121 mA2dpState = newState; 122 if ((oldState == BluetoothA2dp.STATE_CONNECTED || 123 oldState == BluetoothA2dp.STATE_PLAYING) && 124 newState == BluetoothA2dp.STATE_DISCONNECTED) { 125 sendMessage(DISCONNECT_A2DP_INCOMING); 126 } 127 if (newState == BluetoothA2dp.STATE_CONNECTED || 128 newState == BluetoothA2dp.STATE_DISCONNECTED) { 129 sendMessage(TRANSITION_TO_STABLE); 130 } 131 } else if (action.equals(BluetoothDevice.ACTION_ACL_CONNECTED)) { 132 if (!getCurrentState().equals(mBondedDevice)) { 133 Log.e(TAG, "State is: " + getCurrentState()); 134 return; 135 } 136 Message msg = new Message(); 137 msg.what = AUTO_CONNECT_PROFILES; 138 sendMessageDelayed(msg, AUTO_CONNECT_DELAY); 139 } 140 } 141 }; 142 143 private boolean isPhoneDocked(BluetoothDevice autoConnectDevice) { 144 // This works only because these broadcast intents are "sticky" 145 Intent i = mContext.registerReceiver(null, new IntentFilter(Intent.ACTION_DOCK_EVENT)); 146 if (i != null) { 147 int state = i.getIntExtra(Intent.EXTRA_DOCK_STATE, Intent.EXTRA_DOCK_STATE_UNDOCKED); 148 if (state != Intent.EXTRA_DOCK_STATE_UNDOCKED) { 149 BluetoothDevice device = i.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE); 150 if (device != null && autoConnectDevice.equals(device)) { 151 return true; 152 } 153 } 154 } 155 return false; 156 } 157 158 public BluetoothDeviceProfileState(Context context, String address, 159 BluetoothService service, BluetoothA2dpService a2dpService) { 160 super(address); 161 mContext = context; 162 mDevice = new BluetoothDevice(address); 163 mService = service; 164 mA2dpService = a2dpService; 165 166 addState(mBondedDevice); 167 addState(mOutgoingHandsfree); 168 addState(mIncomingHandsfree); 169 addState(mIncomingA2dp); 170 addState(mOutgoingA2dp); 171 setInitialState(mBondedDevice); 172 173 IntentFilter filter = new IntentFilter(); 174 // Fine-grained state broadcasts 175 filter.addAction(BluetoothA2dp.ACTION_SINK_STATE_CHANGED); 176 filter.addAction(BluetoothHeadset.ACTION_STATE_CHANGED); 177 filter.addAction(BluetoothDevice.ACTION_ACL_CONNECTED); 178 179 mContext.registerReceiver(mBroadcastReceiver, filter); 180 181 HeadsetServiceListener l = new HeadsetServiceListener(); 182 PbapServiceListener p = new PbapServiceListener(); 183 } 184 185 private class HeadsetServiceListener implements BluetoothHeadset.ServiceListener { 186 public HeadsetServiceListener() { 187 mHeadsetService = new BluetoothHeadset(mContext, this); 188 } 189 public void onServiceConnected() { 190 synchronized(BluetoothDeviceProfileState.this) { 191 mHeadsetServiceConnected = true; 192 } 193 } 194 public void onServiceDisconnected() { 195 synchronized(BluetoothDeviceProfileState.this) { 196 mHeadsetServiceConnected = false; 197 } 198 } 199 } 200 201 private class PbapServiceListener implements BluetoothPbap.ServiceListener { 202 public PbapServiceListener() { 203 mPbapService = new BluetoothPbap(mContext, this); 204 } 205 public void onServiceConnected() { 206 synchronized(BluetoothDeviceProfileState.this) { 207 mPbapServiceConnected = true; 208 } 209 } 210 public void onServiceDisconnected() { 211 synchronized(BluetoothDeviceProfileState.this) { 212 mPbapServiceConnected = false; 213 } 214 } 215 } 216 217 private class BondedDevice extends HierarchicalState { 218 @Override 219 protected void enter() { 220 log("Entering ACL Connected state with: " + getCurrentMessage().what); 221 Message m = new Message(); 222 m.copyFrom(getCurrentMessage()); 223 sendMessageAtFrontOfQueue(m); 224 } 225 @Override 226 protected boolean processMessage(Message message) { 227 log("ACL Connected State -> Processing Message: " + message.what); 228 switch(message.what) { 229 case CONNECT_HFP_OUTGOING: 230 case DISCONNECT_HFP_OUTGOING: 231 transitionTo(mOutgoingHandsfree); 232 break; 233 case CONNECT_HFP_INCOMING: 234 transitionTo(mIncomingHandsfree); 235 break; 236 case DISCONNECT_HFP_INCOMING: 237 transitionTo(mIncomingHandsfree); 238 break; 239 case CONNECT_A2DP_OUTGOING: 240 case DISCONNECT_A2DP_OUTGOING: 241 transitionTo(mOutgoingA2dp); 242 break; 243 case CONNECT_A2DP_INCOMING: 244 case DISCONNECT_A2DP_INCOMING: 245 transitionTo(mIncomingA2dp); 246 break; 247 case DISCONNECT_PBAP_OUTGOING: 248 processCommand(DISCONNECT_PBAP_OUTGOING); 249 break; 250 case UNPAIR: 251 if (mHeadsetState != BluetoothHeadset.STATE_DISCONNECTED) { 252 sendMessage(DISCONNECT_HFP_OUTGOING); 253 deferMessage(message); 254 break; 255 } else if (mA2dpState != BluetoothA2dp.STATE_DISCONNECTED) { 256 sendMessage(DISCONNECT_A2DP_OUTGOING); 257 deferMessage(message); 258 break; 259 } 260 processCommand(UNPAIR); 261 break; 262 case AUTO_CONNECT_PROFILES: 263 if (isPhoneDocked(mDevice)) { 264 // Don't auto connect to docks. 265 break; 266 } else if (!mHeadsetServiceConnected) { 267 deferMessage(message); 268 } else { 269 if (mHeadsetService.getPriority(mDevice) == 270 BluetoothHeadset.PRIORITY_AUTO_CONNECT && 271 !mHeadsetService.isConnected(mDevice)) { 272 mHeadsetService.connectHeadset(mDevice); 273 } 274 if (mA2dpService != null && 275 mA2dpService.getSinkPriority(mDevice) == 276 BluetoothA2dp.PRIORITY_AUTO_CONNECT && 277 mA2dpService.getConnectedSinks().length == 0) { 278 mA2dpService.connectSink(mDevice); 279 } 280 } 281 break; 282 case TRANSITION_TO_STABLE: 283 // ignore. 284 break; 285 default: 286 return NOT_HANDLED; 287 } 288 return HANDLED; 289 } 290 } 291 292 private class OutgoingHandsfree extends HierarchicalState { 293 private boolean mStatus = false; 294 private int mCommand; 295 296 @Override 297 protected void enter() { 298 log("Entering OutgoingHandsfree state with: " + getCurrentMessage().what); 299 mCommand = getCurrentMessage().what; 300 if (mCommand != CONNECT_HFP_OUTGOING && 301 mCommand != DISCONNECT_HFP_OUTGOING) { 302 Log.e(TAG, "Error: OutgoingHandsfree state with command:" + mCommand); 303 } 304 mStatus = processCommand(mCommand); 305 if (!mStatus) sendMessage(TRANSITION_TO_STABLE); 306 } 307 308 @Override 309 protected boolean processMessage(Message message) { 310 log("OutgoingHandsfree State -> Processing Message: " + message.what); 311 Message deferMsg = new Message(); 312 int command = message.what; 313 switch(command) { 314 case CONNECT_HFP_OUTGOING: 315 if (command != mCommand) { 316 // Disconnect followed by a connect - defer 317 deferMessage(message); 318 } 319 break; 320 case CONNECT_HFP_INCOMING: 321 if (mCommand == CONNECT_HFP_OUTGOING) { 322 // Cancel outgoing connect, accept incoming 323 cancelCommand(CONNECT_HFP_OUTGOING); 324 transitionTo(mIncomingHandsfree); 325 } else { 326 // We have done the disconnect but we are not 327 // sure which state we are in at this point. 328 deferMessage(message); 329 } 330 break; 331 case CONNECT_A2DP_INCOMING: 332 // accept incoming A2DP, retry HFP_OUTGOING 333 transitionTo(mIncomingA2dp); 334 335 if (mStatus) { 336 deferMsg.what = mCommand; 337 deferMessage(deferMsg); 338 } 339 break; 340 case CONNECT_A2DP_OUTGOING: 341 deferMessage(message); 342 break; 343 case DISCONNECT_HFP_OUTGOING: 344 if (mCommand == CONNECT_HFP_OUTGOING) { 345 // Cancel outgoing connect 346 cancelCommand(CONNECT_HFP_OUTGOING); 347 processCommand(DISCONNECT_HFP_OUTGOING); 348 } 349 // else ignore 350 break; 351 case DISCONNECT_HFP_INCOMING: 352 // When this happens the socket would be closed and the headset 353 // state moved to DISCONNECTED, cancel the outgoing thread. 354 // if it still is in CONNECTING state 355 cancelCommand(CONNECT_HFP_OUTGOING); 356 break; 357 case DISCONNECT_A2DP_OUTGOING: 358 deferMessage(message); 359 break; 360 case DISCONNECT_A2DP_INCOMING: 361 // Bluez will handle the disconnect. If because of this the outgoing 362 // handsfree connection has failed, then retry. 363 if (mStatus) { 364 deferMsg.what = mCommand; 365 deferMessage(deferMsg); 366 } 367 break; 368 case DISCONNECT_PBAP_OUTGOING: 369 case UNPAIR: 370 case AUTO_CONNECT_PROFILES: 371 deferMessage(message); 372 break; 373 case TRANSITION_TO_STABLE: 374 transitionTo(mBondedDevice); 375 break; 376 default: 377 return NOT_HANDLED; 378 } 379 return HANDLED; 380 } 381 } 382 383 private class IncomingHandsfree extends HierarchicalState { 384 private boolean mStatus = false; 385 private int mCommand; 386 387 @Override 388 protected void enter() { 389 log("Entering IncomingHandsfree state with: " + getCurrentMessage().what); 390 mCommand = getCurrentMessage().what; 391 if (mCommand != CONNECT_HFP_INCOMING && 392 mCommand != DISCONNECT_HFP_INCOMING) { 393 Log.e(TAG, "Error: IncomingHandsfree state with command:" + mCommand); 394 } 395 mStatus = processCommand(mCommand); 396 if (!mStatus) sendMessage(TRANSITION_TO_STABLE); 397 } 398 399 @Override 400 protected boolean processMessage(Message message) { 401 log("IncomingHandsfree State -> Processing Message: " + message.what); 402 switch(message.what) { 403 case CONNECT_HFP_OUTGOING: 404 deferMessage(message); 405 break; 406 case CONNECT_HFP_INCOMING: 407 // Ignore 408 Log.e(TAG, "Error: Incoming connection with a pending incoming connection"); 409 break; 410 case CONNECT_A2DP_INCOMING: 411 // Serialize the commands. 412 deferMessage(message); 413 break; 414 case CONNECT_A2DP_OUTGOING: 415 deferMessage(message); 416 break; 417 case DISCONNECT_HFP_OUTGOING: 418 // We don't know at what state we are in the incoming HFP connection state. 419 // We can be changing from DISCONNECTED to CONNECTING, or 420 // from CONNECTING to CONNECTED, so serializing this command is 421 // the safest option. 422 deferMessage(message); 423 break; 424 case DISCONNECT_HFP_INCOMING: 425 // Nothing to do here, we will already be DISCONNECTED 426 // by this point. 427 break; 428 case DISCONNECT_A2DP_OUTGOING: 429 deferMessage(message); 430 break; 431 case DISCONNECT_A2DP_INCOMING: 432 // Bluez handles incoming A2DP disconnect. 433 // If this causes incoming HFP to fail, it is more of a headset problem 434 // since both connections are incoming ones. 435 break; 436 case DISCONNECT_PBAP_OUTGOING: 437 case UNPAIR: 438 case AUTO_CONNECT_PROFILES: 439 deferMessage(message); 440 break; 441 case TRANSITION_TO_STABLE: 442 transitionTo(mBondedDevice); 443 break; 444 default: 445 return NOT_HANDLED; 446 } 447 return HANDLED; 448 } 449 } 450 451 private class OutgoingA2dp extends HierarchicalState { 452 private boolean mStatus = false; 453 private int mCommand; 454 455 @Override 456 protected void enter() { 457 log("Entering OutgoingA2dp state with: " + getCurrentMessage().what); 458 mCommand = getCurrentMessage().what; 459 if (mCommand != CONNECT_A2DP_OUTGOING && 460 mCommand != DISCONNECT_A2DP_OUTGOING) { 461 Log.e(TAG, "Error: OutgoingA2DP state with command:" + mCommand); 462 } 463 mStatus = processCommand(mCommand); 464 if (!mStatus) sendMessage(TRANSITION_TO_STABLE); 465 } 466 467 @Override 468 protected boolean processMessage(Message message) { 469 log("OutgoingA2dp State->Processing Message: " + message.what); 470 Message deferMsg = new Message(); 471 switch(message.what) { 472 case CONNECT_HFP_OUTGOING: 473 processCommand(CONNECT_HFP_OUTGOING); 474 475 // Don't cancel A2DP outgoing as there is no guarantee it 476 // will get canceled. 477 // It might already be connected but we might not have got the 478 // A2DP_SINK_STATE_CHANGE. Hence, no point disconnecting here. 479 // The worst case, the connection will fail, retry. 480 // The same applies to Disconnecting an A2DP connection. 481 if (mStatus) { 482 deferMsg.what = mCommand; 483 deferMessage(deferMsg); 484 } 485 break; 486 case CONNECT_HFP_INCOMING: 487 processCommand(CONNECT_HFP_INCOMING); 488 489 // Don't cancel A2DP outgoing as there is no guarantee 490 // it will get canceled. 491 // The worst case, the connection will fail, retry. 492 if (mStatus) { 493 deferMsg.what = mCommand; 494 deferMessage(deferMsg); 495 } 496 break; 497 case CONNECT_A2DP_INCOMING: 498 // Bluez will take care of conflicts between incoming and outgoing 499 // connections. 500 transitionTo(mIncomingA2dp); 501 break; 502 case CONNECT_A2DP_OUTGOING: 503 // Ignore 504 break; 505 case DISCONNECT_HFP_OUTGOING: 506 deferMessage(message); 507 break; 508 case DISCONNECT_HFP_INCOMING: 509 // At this point, we are already disconnected 510 // with HFP. Sometimes A2DP connection can 511 // fail due to the disconnection of HFP. So add a retry 512 // for the A2DP. 513 if (mStatus) { 514 deferMsg.what = mCommand; 515 deferMessage(deferMsg); 516 } 517 break; 518 case DISCONNECT_A2DP_OUTGOING: 519 deferMessage(message); 520 break; 521 case DISCONNECT_A2DP_INCOMING: 522 // Ignore, will be handled by Bluez 523 break; 524 case DISCONNECT_PBAP_OUTGOING: 525 case UNPAIR: 526 case AUTO_CONNECT_PROFILES: 527 deferMessage(message); 528 break; 529 case TRANSITION_TO_STABLE: 530 transitionTo(mBondedDevice); 531 break; 532 default: 533 return NOT_HANDLED; 534 } 535 return HANDLED; 536 } 537 } 538 539 private class IncomingA2dp extends HierarchicalState { 540 private boolean mStatus = false; 541 private int mCommand; 542 543 @Override 544 protected void enter() { 545 log("Entering IncomingA2dp state with: " + getCurrentMessage().what); 546 mCommand = getCurrentMessage().what; 547 if (mCommand != CONNECT_A2DP_INCOMING && 548 mCommand != DISCONNECT_A2DP_INCOMING) { 549 Log.e(TAG, "Error: IncomingA2DP state with command:" + mCommand); 550 } 551 mStatus = processCommand(mCommand); 552 if (!mStatus) sendMessage(TRANSITION_TO_STABLE); 553 } 554 555 @Override 556 protected boolean processMessage(Message message) { 557 log("IncomingA2dp State->Processing Message: " + message.what); 558 Message deferMsg = new Message(); 559 switch(message.what) { 560 case CONNECT_HFP_OUTGOING: 561 deferMessage(message); 562 break; 563 case CONNECT_HFP_INCOMING: 564 // Shouldn't happen, but serialize the commands. 565 deferMessage(message); 566 break; 567 case CONNECT_A2DP_INCOMING: 568 // ignore 569 break; 570 case CONNECT_A2DP_OUTGOING: 571 // Defer message and retry 572 deferMessage(message); 573 break; 574 case DISCONNECT_HFP_OUTGOING: 575 deferMessage(message); 576 break; 577 case DISCONNECT_HFP_INCOMING: 578 // Shouldn't happen but if does, we can handle it. 579 // Depends if the headset can handle it. 580 // Incoming A2DP will be handled by Bluez, Disconnect HFP 581 // the socket would have already been closed. 582 // ignore 583 break; 584 case DISCONNECT_A2DP_OUTGOING: 585 deferMessage(message); 586 break; 587 case DISCONNECT_A2DP_INCOMING: 588 // Ignore, will be handled by Bluez 589 break; 590 case DISCONNECT_PBAP_OUTGOING: 591 case UNPAIR: 592 case AUTO_CONNECT_PROFILES: 593 deferMessage(message); 594 break; 595 case TRANSITION_TO_STABLE: 596 transitionTo(mBondedDevice); 597 break; 598 default: 599 return NOT_HANDLED; 600 } 601 return HANDLED; 602 } 603 } 604 605 606 607 synchronized void cancelCommand(int command) { 608 if (command == CONNECT_HFP_OUTGOING ) { 609 // Cancel the outgoing thread. 610 if (mHeadsetServiceConnected) { 611 mHeadsetService.cancelConnectThread(); 612 } 613 // HeadsetService is down. Phone process most likely crashed. 614 // The thread would have got killed. 615 } 616 } 617 618 synchronized void deferProfileServiceMessage(int command) { 619 Message msg = new Message(); 620 msg.what = command; 621 deferMessage(msg); 622 } 623 624 synchronized boolean processCommand(int command) { 625 log("Processing command:" + command); 626 switch(command) { 627 case CONNECT_HFP_OUTGOING: 628 if (mHeadsetService != null) { 629 return mHeadsetService.connectHeadsetInternal(mDevice); 630 } 631 break; 632 case CONNECT_HFP_INCOMING: 633 if (!mHeadsetServiceConnected) { 634 deferProfileServiceMessage(command); 635 } else if (mHeadsetState == BluetoothHeadset.STATE_CONNECTING) { 636 return mHeadsetService.acceptIncomingConnect(mDevice); 637 } else if (mHeadsetState == BluetoothHeadset.STATE_DISCONNECTED) { 638 return mHeadsetService.createIncomingConnect(mDevice); 639 } 640 break; 641 case CONNECT_A2DP_OUTGOING: 642 if (mA2dpService != null) { 643 return mA2dpService.connectSinkInternal(mDevice); 644 } 645 break; 646 case CONNECT_A2DP_INCOMING: 647 // ignore, Bluez takes care 648 return true; 649 case DISCONNECT_HFP_OUTGOING: 650 if (!mHeadsetServiceConnected) { 651 deferProfileServiceMessage(command); 652 } else { 653 // Disconnect PBAP 654 // TODO(): Add PBAP to the state machine. 655 Message m = new Message(); 656 m.what = DISCONNECT_PBAP_OUTGOING; 657 deferMessage(m); 658 if (mHeadsetService.getPriority(mDevice) == 659 BluetoothHeadset.PRIORITY_AUTO_CONNECT) { 660 mHeadsetService.setPriority(mDevice, BluetoothHeadset.PRIORITY_ON); 661 } 662 return mHeadsetService.disconnectHeadsetInternal(mDevice); 663 } 664 break; 665 case DISCONNECT_HFP_INCOMING: 666 // ignore 667 return true; 668 case DISCONNECT_A2DP_INCOMING: 669 // ignore 670 return true; 671 case DISCONNECT_A2DP_OUTGOING: 672 if (mA2dpService != null) { 673 if (mA2dpService.getSinkPriority(mDevice) == 674 BluetoothA2dp.PRIORITY_AUTO_CONNECT) { 675 mA2dpService.setSinkPriority(mDevice, BluetoothHeadset.PRIORITY_ON); 676 } 677 return mA2dpService.disconnectSinkInternal(mDevice); 678 } 679 break; 680 case DISCONNECT_PBAP_OUTGOING: 681 if (!mPbapServiceConnected) { 682 deferProfileServiceMessage(command); 683 } else { 684 return mPbapService.disconnect(); 685 } 686 break; 687 case UNPAIR: 688 return mService.removeBondInternal(mDevice.getAddress()); 689 default: 690 Log.e(TAG, "Error: Unknown Command"); 691 } 692 return false; 693 } 694 695 /*package*/ BluetoothDevice getDevice() { 696 return mDevice; 697 } 698 699 private void log(String message) { 700 if (DBG) { 701 Log.i(TAG, "Device:" + mDevice + " Message:" + message); 702 } 703 } 704} 705