1 /* 2 * Copyright (C) 2012 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.nfc.handover; 18 19 import android.bluetooth.BluetoothA2dp; 20 import android.bluetooth.BluetoothAdapter; 21 import android.bluetooth.BluetoothClass; 22 import android.bluetooth.BluetoothDevice; 23 import android.bluetooth.BluetoothHeadset; 24 import android.bluetooth.BluetoothHidHost; 25 import android.bluetooth.BluetoothProfile; 26 import android.bluetooth.BluetoothUuid; 27 import android.bluetooth.OobData; 28 import android.content.BroadcastReceiver; 29 import android.content.ContentResolver; 30 import android.content.Context; 31 import android.content.Intent; 32 import android.content.IntentFilter; 33 import android.media.session.MediaSessionLegacyHelper; 34 import android.os.Handler; 35 import android.os.Looper; 36 import android.os.Message; 37 import android.os.ParcelUuid; 38 import android.provider.Settings; 39 import android.util.Log; 40 import android.view.KeyEvent; 41 import android.widget.Toast; 42 43 import com.android.nfc.R; 44 45 /** 46 * Connects / Disconnects from a Bluetooth headset (or any device that 47 * might implement BT HSP, HFP, A2DP, or HOGP sink) when touched with NFC. 48 * 49 * This object is created on an NFC interaction, and determines what 50 * sequence of Bluetooth actions to take, and executes them. It is not 51 * designed to be re-used after the sequence has completed or timed out. 52 * Subsequent NFC interactions should use new objects. 53 * 54 */ 55 public class BluetoothPeripheralHandover implements BluetoothProfile.ServiceListener { 56 static final String TAG = "BluetoothPeripheralHandover"; 57 static final boolean DBG = false; 58 59 static final String ACTION_ALLOW_CONNECT = "com.android.nfc.handover.action.ALLOW_CONNECT"; 60 static final String ACTION_DENY_CONNECT = "com.android.nfc.handover.action.DENY_CONNECT"; 61 static final String ACTION_TIMEOUT_CONNECT = "com.android.nfc.handover.action.TIMEOUT_CONNECT"; 62 63 static final int TIMEOUT_MS = 20000; 64 static final int RETRY_PAIRING_WAIT_TIME_MS = 2000; 65 static final int RETRY_CONNECT_WAIT_TIME_MS = 5000; 66 67 static final int STATE_INIT = 0; 68 static final int STATE_WAITING_FOR_PROXIES = 1; 69 static final int STATE_INIT_COMPLETE = 2; 70 static final int STATE_WAITING_FOR_BOND_CONFIRMATION = 3; 71 static final int STATE_BONDING = 4; 72 static final int STATE_CONNECTING = 5; 73 static final int STATE_DISCONNECTING = 6; 74 static final int STATE_COMPLETE = 7; 75 76 static final int RESULT_PENDING = 0; 77 static final int RESULT_CONNECTED = 1; 78 static final int RESULT_DISCONNECTED = 2; 79 80 static final int ACTION_INIT = 0; 81 static final int ACTION_DISCONNECT = 1; 82 static final int ACTION_CONNECT = 2; 83 84 static final int MSG_TIMEOUT = 1; 85 static final int MSG_NEXT_STEP = 2; 86 static final int MSG_RETRY = 3; 87 88 static final int MAX_RETRY_COUNT = 3; 89 90 final Context mContext; 91 final BluetoothDevice mDevice; 92 final String mName; 93 final Callback mCallback; 94 final BluetoothAdapter mBluetoothAdapter; 95 final int mTransport; 96 final boolean mProvisioning; 97 98 final Object mLock = new Object(); 99 100 // only used on main thread 101 int mAction; 102 int mState; 103 int mHfpResult; // used only in STATE_CONNECTING and STATE_DISCONNETING 104 int mA2dpResult; // used only in STATE_CONNECTING and STATE_DISCONNETING 105 int mHidResult; 106 int mRetryCount; 107 OobData mOobData; 108 boolean mIsHeadsetAvailable; 109 boolean mIsA2dpAvailable; 110 111 // protected by mLock 112 BluetoothA2dp mA2dp; 113 BluetoothHeadset mHeadset; 114 BluetoothHidHost mInput; 115 116 public interface Callback { 117 public void onBluetoothPeripheralHandoverComplete(boolean connected); 118 } 119 120 public BluetoothPeripheralHandover(Context context, BluetoothDevice device, String name, 121 int transport, OobData oobData, ParcelUuid[] uuids, BluetoothClass btClass, 122 Callback callback) { 123 checkMainThread(); // mHandler must get get constructed on Main Thread for toasts to work 124 mContext = context; 125 mDevice = device; 126 mName = name; 127 mTransport = transport; 128 mOobData = oobData; 129 mCallback = callback; 130 mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter(); 131 132 ContentResolver contentResolver = mContext.getContentResolver(); 133 mProvisioning = Settings.Secure.getInt(contentResolver, 134 Settings.Global.DEVICE_PROVISIONED, 0) == 0; 135 136 mIsHeadsetAvailable = hasHeadsetCapability(uuids, btClass); 137 mIsA2dpAvailable = hasA2dpCapability(uuids, btClass); 138 139 // Capability information is from NDEF optional field, then it might be empty. 140 // If all capabilities indicate false, try to connect Headset and A2dp just in case. 141 if (!mIsHeadsetAvailable && !mIsA2dpAvailable) { 142 mIsHeadsetAvailable = true; 143 mIsA2dpAvailable = true; 144 } 145 146 mState = STATE_INIT; 147 } 148 149 public boolean hasStarted() { 150 return mState != STATE_INIT; 151 } 152 153 /** 154 * Main entry point. This method is usually called after construction, 155 * to begin the BT sequence. Must be called on Main thread. 156 */ 157 public boolean start() { 158 checkMainThread(); 159 if (mState != STATE_INIT || mBluetoothAdapter == null 160 || (mProvisioning && mTransport != BluetoothDevice.TRANSPORT_LE)) { 161 return false; 162 } 163 164 165 IntentFilter filter = new IntentFilter(); 166 filter.addAction(BluetoothAdapter.ACTION_STATE_CHANGED); 167 filter.addAction(BluetoothDevice.ACTION_BOND_STATE_CHANGED); 168 filter.addAction(BluetoothA2dp.ACTION_CONNECTION_STATE_CHANGED); 169 filter.addAction(BluetoothHeadset.ACTION_CONNECTION_STATE_CHANGED); 170 filter.addAction(BluetoothHidHost.ACTION_CONNECTION_STATE_CHANGED); 171 filter.addAction(ACTION_ALLOW_CONNECT); 172 filter.addAction(ACTION_DENY_CONNECT); 173 174 mContext.registerReceiver(mReceiver, filter); 175 176 mHandler.sendMessageDelayed(mHandler.obtainMessage(MSG_TIMEOUT), TIMEOUT_MS); 177 178 mAction = ACTION_INIT; 179 mRetryCount = 0; 180 181 nextStep(); 182 183 return true; 184 } 185 186 /** 187 * Called to execute next step in state machine 188 */ 189 void nextStep() { 190 if (mAction == ACTION_INIT) { 191 nextStepInit(); 192 } else if (mAction == ACTION_CONNECT) { 193 nextStepConnect(); 194 } else { 195 nextStepDisconnect(); 196 } 197 } 198 199 /* 200 * Enables bluetooth and gets the profile proxies 201 */ 202 void nextStepInit() { 203 switch (mState) { 204 case STATE_INIT: 205 if (mA2dp == null || mHeadset == null || mInput == null) { 206 mState = STATE_WAITING_FOR_PROXIES; 207 if (!getProfileProxys()) { 208 complete(false); 209 } 210 break; 211 } 212 // fall-through 213 case STATE_WAITING_FOR_PROXIES: 214 mState = STATE_INIT_COMPLETE; 215 // Check connected devices and see if we need to disconnect 216 synchronized(mLock) { 217 if (mTransport == BluetoothDevice.TRANSPORT_LE) { 218 if (mInput.getConnectedDevices().contains(mDevice)) { 219 Log.i(TAG, "ACTION_DISCONNECT addr=" + mDevice + " name=" + mName); 220 mAction = ACTION_DISCONNECT; 221 } else { 222 Log.i(TAG, "ACTION_CONNECT addr=" + mDevice + " name=" + mName); 223 mAction = ACTION_CONNECT; 224 } 225 } else { 226 if (mA2dp.getConnectedDevices().contains(mDevice) || 227 mHeadset.getConnectedDevices().contains(mDevice)) { 228 Log.i(TAG, "ACTION_DISCONNECT addr=" + mDevice + " name=" + mName); 229 mAction = ACTION_DISCONNECT; 230 } else { 231 // Check if each profile of the device is disabled or not 232 if (mHeadset.getPriority(mDevice) == BluetoothProfile.PRIORITY_OFF) { 233 mIsHeadsetAvailable = false; 234 } 235 if (mA2dp.getPriority(mDevice) == BluetoothProfile.PRIORITY_OFF) { 236 mIsA2dpAvailable = false; 237 } 238 if (!mIsHeadsetAvailable && !mIsA2dpAvailable) { 239 Log.i(TAG, "Both Headset and A2DP profiles are unavailable"); 240 complete(false); 241 break; 242 } 243 Log.i(TAG, "ACTION_CONNECT addr=" + mDevice + " name=" + mName); 244 mAction = ACTION_CONNECT; 245 } 246 } 247 } 248 nextStep(); 249 } 250 251 } 252 253 void nextStepDisconnect() { 254 switch (mState) { 255 case STATE_INIT_COMPLETE: 256 mState = STATE_DISCONNECTING; 257 synchronized (mLock) { 258 if (mTransport == BluetoothDevice.TRANSPORT_LE) { 259 if (mInput.getConnectionState(mDevice) 260 != BluetoothProfile.STATE_DISCONNECTED) { 261 mHidResult = RESULT_PENDING; 262 mInput.disconnect(mDevice); 263 toast(getToastString(R.string.disconnecting_peripheral)); 264 break; 265 } else { 266 mHidResult = RESULT_DISCONNECTED; 267 } 268 } else { 269 if (mHeadset.getConnectionState(mDevice) 270 != BluetoothProfile.STATE_DISCONNECTED) { 271 mHfpResult = RESULT_PENDING; 272 mHeadset.disconnect(mDevice); 273 } else { 274 mHfpResult = RESULT_DISCONNECTED; 275 } 276 if (mA2dp.getConnectionState(mDevice) 277 != BluetoothProfile.STATE_DISCONNECTED) { 278 mA2dpResult = RESULT_PENDING; 279 mA2dp.disconnect(mDevice); 280 } else { 281 mA2dpResult = RESULT_DISCONNECTED; 282 } 283 if (mA2dpResult == RESULT_PENDING || mHfpResult == RESULT_PENDING) { 284 toast(getToastString(R.string.disconnecting_peripheral)); 285 break; 286 } 287 } 288 } 289 // fall-through 290 case STATE_DISCONNECTING: 291 if (mTransport == BluetoothDevice.TRANSPORT_LE) { 292 if (mHidResult == RESULT_DISCONNECTED) { 293 toast(getToastString(R.string.disconnected_peripheral)); 294 complete(false); 295 } 296 297 break; 298 } else { 299 if (mA2dpResult == RESULT_PENDING || mHfpResult == RESULT_PENDING) { 300 // still disconnecting 301 break; 302 } 303 if (mA2dpResult == RESULT_DISCONNECTED && mHfpResult == RESULT_DISCONNECTED) { 304 toast(getToastString(R.string.disconnected_peripheral)); 305 } 306 complete(false); 307 break; 308 } 309 310 } 311 312 } 313 314 private String getToastString(int resid) { 315 return mContext.getString(resid, mName != null ? mName : R.string.device); 316 } 317 318 boolean getProfileProxys() { 319 320 if (mTransport == BluetoothDevice.TRANSPORT_LE) { 321 if (!mBluetoothAdapter.getProfileProxy(mContext, this, BluetoothProfile.HID_HOST)) 322 return false; 323 } else { 324 if(!mBluetoothAdapter.getProfileProxy(mContext, this, BluetoothProfile.HEADSET)) 325 return false; 326 327 if(!mBluetoothAdapter.getProfileProxy(mContext, this, BluetoothProfile.A2DP)) 328 return false; 329 } 330 331 return true; 332 } 333 334 void nextStepConnect() { 335 switch (mState) { 336 case STATE_INIT_COMPLETE: 337 338 if (mDevice.getBondState() != BluetoothDevice.BOND_BONDED) { 339 requestPairConfirmation(); 340 mState = STATE_WAITING_FOR_BOND_CONFIRMATION; 341 break; 342 } 343 344 if (mTransport == BluetoothDevice.TRANSPORT_LE) { 345 if (mDevice.getBondState() != BluetoothDevice.BOND_NONE) { 346 mDevice.removeBond(); 347 requestPairConfirmation(); 348 mState = STATE_WAITING_FOR_BOND_CONFIRMATION; 349 break; 350 } 351 } 352 // fall-through 353 case STATE_WAITING_FOR_BOND_CONFIRMATION: 354 if (mDevice.getBondState() != BluetoothDevice.BOND_BONDED) { 355 startBonding(); 356 break; 357 } 358 // fall-through 359 case STATE_BONDING: 360 // Bluetooth Profile service will correctly serialize 361 // HFP then A2DP connect 362 mState = STATE_CONNECTING; 363 synchronized (mLock) { 364 if (mTransport == BluetoothDevice.TRANSPORT_LE) { 365 if (mInput.getConnectionState(mDevice) 366 != BluetoothProfile.STATE_CONNECTED) { 367 mHidResult = RESULT_PENDING; 368 toast(getToastString(R.string.connecting_peripheral)); 369 break; 370 } else { 371 mHidResult = RESULT_CONNECTED; 372 } 373 } else { 374 if (mHeadset.getConnectionState(mDevice) != 375 BluetoothProfile.STATE_CONNECTED) { 376 if (mIsHeadsetAvailable) { 377 mHfpResult = RESULT_PENDING; 378 mHeadset.connect(mDevice); 379 } else { 380 mHfpResult = RESULT_DISCONNECTED; 381 } 382 } else { 383 mHfpResult = RESULT_CONNECTED; 384 } 385 if (mA2dp.getConnectionState(mDevice) != BluetoothProfile.STATE_CONNECTED) { 386 if (mIsA2dpAvailable) { 387 mA2dpResult = RESULT_PENDING; 388 mA2dp.connect(mDevice); 389 } else { 390 mA2dpResult = RESULT_DISCONNECTED; 391 } 392 } else { 393 mA2dpResult = RESULT_CONNECTED; 394 } 395 if (mA2dpResult == RESULT_PENDING || mHfpResult == RESULT_PENDING) { 396 if (mRetryCount == 0) { 397 toast(getToastString(R.string.connecting_peripheral)); 398 } 399 if (mRetryCount < MAX_RETRY_COUNT) { 400 sendRetryMessage(RETRY_CONNECT_WAIT_TIME_MS); 401 break; 402 } 403 } 404 } 405 } 406 // fall-through 407 case STATE_CONNECTING: 408 if (mTransport == BluetoothDevice.TRANSPORT_LE) { 409 if (mHidResult == RESULT_PENDING) { 410 break; 411 } else if (mHidResult == RESULT_CONNECTED) { 412 toast(getToastString(R.string.connected_peripheral)); 413 mDevice.setAlias(mName); 414 complete(true); 415 } else { 416 toast (getToastString(R.string.connect_peripheral_failed)); 417 complete(false); 418 } 419 } else { 420 if (mA2dpResult == RESULT_PENDING || mHfpResult == RESULT_PENDING) { 421 // another connection type still pending 422 break; 423 } 424 if (mA2dpResult == RESULT_CONNECTED || mHfpResult == RESULT_CONNECTED) { 425 // we'll take either as success 426 toast(getToastString(R.string.connected_peripheral)); 427 if (mA2dpResult == RESULT_CONNECTED) startTheMusic(); 428 mDevice.setAlias(mName); 429 complete(true); 430 } else { 431 toast (getToastString(R.string.connect_peripheral_failed)); 432 complete(false); 433 } 434 } 435 break; 436 } 437 } 438 439 void startBonding() { 440 mState = STATE_BONDING; 441 if (mRetryCount == 0) { 442 toast(getToastString(R.string.pairing_peripheral)); 443 } 444 if (mOobData != null) { 445 if (!mDevice.createBondOutOfBand(mTransport, mOobData)) { 446 toast(getToastString(R.string.pairing_peripheral_failed)); 447 complete(false); 448 } 449 } else if (!mDevice.createBond(mTransport)) { 450 toast(getToastString(R.string.pairing_peripheral_failed)); 451 complete(false); 452 } 453 } 454 455 void handleIntent(Intent intent) { 456 String action = intent.getAction(); 457 // Everything requires the device to match... 458 BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE); 459 if (!mDevice.equals(device)) return; 460 461 if (ACTION_ALLOW_CONNECT.equals(action)) { 462 mHandler.removeMessages(MSG_TIMEOUT); 463 mHandler.sendMessageDelayed(mHandler.obtainMessage(MSG_TIMEOUT), TIMEOUT_MS); 464 nextStepConnect(); 465 } else if (ACTION_DENY_CONNECT.equals(action)) { 466 complete(false); 467 } else if (BluetoothDevice.ACTION_BOND_STATE_CHANGED.equals(action) 468 && mState == STATE_BONDING) { 469 int bond = intent.getIntExtra(BluetoothDevice.EXTRA_BOND_STATE, 470 BluetoothAdapter.ERROR); 471 if (bond == BluetoothDevice.BOND_BONDED) { 472 mRetryCount = 0; 473 nextStepConnect(); 474 } else if (bond == BluetoothDevice.BOND_NONE) { 475 if (mRetryCount < MAX_RETRY_COUNT) { 476 sendRetryMessage(RETRY_PAIRING_WAIT_TIME_MS); 477 } else { 478 toast(getToastString(R.string.pairing_peripheral_failed)); 479 complete(false); 480 } 481 } 482 } else if (BluetoothHeadset.ACTION_CONNECTION_STATE_CHANGED.equals(action) && 483 (mState == STATE_CONNECTING || mState == STATE_DISCONNECTING)) { 484 int state = intent.getIntExtra(BluetoothProfile.EXTRA_STATE, BluetoothAdapter.ERROR); 485 if (state == BluetoothProfile.STATE_CONNECTED) { 486 mHfpResult = RESULT_CONNECTED; 487 nextStep(); 488 } else if (state == BluetoothProfile.STATE_DISCONNECTED) { 489 if (mAction == ACTION_CONNECT && mRetryCount < MAX_RETRY_COUNT) { 490 sendRetryMessage(RETRY_CONNECT_WAIT_TIME_MS); 491 } else { 492 mHfpResult = RESULT_DISCONNECTED; 493 nextStep(); 494 } 495 } 496 } else if (BluetoothA2dp.ACTION_CONNECTION_STATE_CHANGED.equals(action) && 497 (mState == STATE_CONNECTING || mState == STATE_DISCONNECTING)) { 498 int state = intent.getIntExtra(BluetoothProfile.EXTRA_STATE, BluetoothAdapter.ERROR); 499 if (state == BluetoothProfile.STATE_CONNECTED) { 500 mA2dpResult = RESULT_CONNECTED; 501 nextStep(); 502 } else if (state == BluetoothProfile.STATE_DISCONNECTED) { 503 if (mAction == ACTION_CONNECT && mRetryCount < MAX_RETRY_COUNT) { 504 sendRetryMessage(RETRY_CONNECT_WAIT_TIME_MS); 505 } else { 506 mA2dpResult = RESULT_DISCONNECTED; 507 nextStep(); 508 } 509 } 510 } else if (BluetoothHidHost.ACTION_CONNECTION_STATE_CHANGED.equals(action) && 511 (mState == STATE_CONNECTING || mState == STATE_DISCONNECTING)) { 512 int state = intent.getIntExtra(BluetoothProfile.EXTRA_STATE, BluetoothAdapter.ERROR); 513 if (state == BluetoothProfile.STATE_CONNECTED) { 514 mHidResult = RESULT_CONNECTED; 515 nextStep(); 516 } else if (state == BluetoothProfile.STATE_DISCONNECTED) { 517 mHidResult = RESULT_DISCONNECTED; 518 nextStep(); 519 } 520 } 521 } 522 523 void complete(boolean connected) { 524 if (DBG) Log.d(TAG, "complete()"); 525 mState = STATE_COMPLETE; 526 mContext.unregisterReceiver(mReceiver); 527 mHandler.removeMessages(MSG_TIMEOUT); 528 mHandler.removeMessages(MSG_RETRY); 529 synchronized (mLock) { 530 if (mA2dp != null) { 531 mBluetoothAdapter.closeProfileProxy(BluetoothProfile.A2DP, mA2dp); 532 } 533 if (mHeadset != null) { 534 mBluetoothAdapter.closeProfileProxy(BluetoothProfile.HEADSET, mHeadset); 535 } 536 537 if (mInput != null) { 538 mBluetoothAdapter.closeProfileProxy(BluetoothProfile.HID_HOST, mInput); 539 } 540 541 mA2dp = null; 542 mHeadset = null; 543 mInput = null; 544 } 545 mCallback.onBluetoothPeripheralHandoverComplete(connected); 546 } 547 548 void toast(CharSequence text) { 549 Toast.makeText(mContext, text, Toast.LENGTH_SHORT).show(); 550 } 551 552 void startTheMusic() { 553 MediaSessionLegacyHelper helper = MediaSessionLegacyHelper.getHelper(mContext); 554 if (helper != null) { 555 KeyEvent keyEvent = new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_MEDIA_PLAY); 556 helper.sendMediaButtonEvent(keyEvent, false); 557 keyEvent = new KeyEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_MEDIA_PLAY); 558 helper.sendMediaButtonEvent(keyEvent, false); 559 } else { 560 Log.w(TAG, "Unable to send media key event"); 561 } 562 } 563 564 void requestPairConfirmation() { 565 Intent dialogIntent = new Intent(mContext, ConfirmConnectActivity.class); 566 dialogIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK); 567 dialogIntent.putExtra(BluetoothDevice.EXTRA_DEVICE, mDevice); 568 dialogIntent.putExtra(BluetoothDevice.EXTRA_NAME, mName); 569 570 mContext.startActivity(dialogIntent); 571 } 572 573 boolean hasA2dpCapability(ParcelUuid[] uuids, BluetoothClass btClass) { 574 if (uuids != null) { 575 for (ParcelUuid uuid : uuids) { 576 if (BluetoothUuid.isAudioSink(uuid) || BluetoothUuid.isAdvAudioDist(uuid)) { 577 return true; 578 } 579 } 580 } 581 if (btClass != null && btClass.doesClassMatch(BluetoothClass.PROFILE_A2DP)) { 582 return true; 583 } 584 return false; 585 } 586 587 boolean hasHeadsetCapability(ParcelUuid[] uuids, BluetoothClass btClass) { 588 if (uuids != null) { 589 for (ParcelUuid uuid : uuids) { 590 if (BluetoothUuid.isHandsfree(uuid) || BluetoothUuid.isHeadset(uuid)) { 591 return true; 592 } 593 } 594 } 595 if (btClass != null && btClass.doesClassMatch(BluetoothClass.PROFILE_HEADSET)) { 596 return true; 597 } 598 return false; 599 } 600 601 final Handler mHandler = new Handler() { 602 @Override 603 public void handleMessage(Message msg) { 604 switch (msg.what) { 605 case MSG_TIMEOUT: 606 if (mState == STATE_COMPLETE) return; 607 Log.i(TAG, "Timeout completing BT handover"); 608 if (mState == STATE_WAITING_FOR_BOND_CONFIRMATION) { 609 mContext.sendBroadcast(new Intent(ACTION_TIMEOUT_CONNECT)); 610 } else if (mState == STATE_BONDING) { 611 toast(getToastString(R.string.pairing_peripheral_failed)); 612 } else if (mState == STATE_CONNECTING) { 613 if (mHidResult == RESULT_PENDING) { 614 mHidResult = RESULT_DISCONNECTED; 615 } 616 if (mA2dpResult == RESULT_PENDING) { 617 mA2dpResult = RESULT_DISCONNECTED; 618 } 619 if (mHfpResult == RESULT_PENDING) { 620 mHfpResult = RESULT_DISCONNECTED; 621 } 622 // Check if any one profile is connected, then it takes as success 623 nextStepConnect(); 624 break; 625 } 626 complete(false); 627 break; 628 case MSG_NEXT_STEP: 629 nextStep(); 630 break; 631 case MSG_RETRY: 632 mHandler.removeMessages(MSG_RETRY); 633 if (mState == STATE_BONDING) { 634 mState = STATE_WAITING_FOR_BOND_CONFIRMATION; 635 } else if (mState == STATE_CONNECTING) { 636 mState = STATE_BONDING; 637 } 638 mRetryCount++; 639 nextStepConnect(); 640 break; 641 } 642 } 643 }; 644 645 final BroadcastReceiver mReceiver = new BroadcastReceiver() { 646 @Override 647 public void onReceive(Context context, Intent intent) { 648 handleIntent(intent); 649 } 650 }; 651 652 static void checkMainThread() { 653 if (Looper.myLooper() != Looper.getMainLooper()) { 654 throw new IllegalThreadStateException("must be called on main thread"); 655 } 656 } 657 658 @Override 659 public void onServiceConnected(int profile, BluetoothProfile proxy) { 660 synchronized (mLock) { 661 switch (profile) { 662 case BluetoothProfile.HEADSET: 663 mHeadset = (BluetoothHeadset) proxy; 664 if (mA2dp != null) { 665 mHandler.sendEmptyMessage(MSG_NEXT_STEP); 666 } 667 break; 668 case BluetoothProfile.A2DP: 669 mA2dp = (BluetoothA2dp) proxy; 670 if (mHeadset != null) { 671 mHandler.sendEmptyMessage(MSG_NEXT_STEP); 672 } 673 break; 674 case BluetoothProfile.HID_HOST: 675 mInput = (BluetoothHidHost) proxy; 676 if (mInput != null) { 677 mHandler.sendEmptyMessage(MSG_NEXT_STEP); 678 } 679 break; 680 } 681 } 682 } 683 684 @Override 685 public void onServiceDisconnected(int profile) { 686 // We can ignore these 687 } 688 689 void sendRetryMessage(int waitTime) { 690 if (!mHandler.hasMessages(MSG_RETRY)) { 691 mHandler.sendMessageDelayed(mHandler.obtainMessage(MSG_RETRY), waitTime); 692 } 693 } 694 } 695