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