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.BluetoothInputDevice; 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 BluetoothInputDevice mInput; 115 116 public interface Callback { onBluetoothPeripheralHandoverComplete(boolean connected)117 public void onBluetoothPeripheralHandoverComplete(boolean connected); 118 } 119 BluetoothPeripheralHandover(Context context, BluetoothDevice device, String name, int transport, OobData oobData, ParcelUuid[] uuids, BluetoothClass btClass, Callback callback)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 hasStarted()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 */ start()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(BluetoothInputDevice.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 */ nextStep()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 */ nextStepInit()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 nextStepDisconnect()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 getToastString(int resid)314 private String getToastString(int resid) { 315 return mContext.getString(resid, mName != null ? mName : R.string.device); 316 } 317 getProfileProxys()318 boolean getProfileProxys() { 319 320 if (mTransport == BluetoothDevice.TRANSPORT_LE) { 321 if (!mBluetoothAdapter.getProfileProxy(mContext, this, BluetoothProfile.INPUT_DEVICE)) 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 nextStepConnect()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 (mHeadset.getConnectionState(mDevice) != 366 BluetoothProfile.STATE_CONNECTED) { 367 if (mIsHeadsetAvailable) { 368 mHfpResult = RESULT_PENDING; 369 mHeadset.connect(mDevice); 370 } else { 371 mHfpResult = RESULT_DISCONNECTED; 372 } 373 } else { 374 mHfpResult = RESULT_CONNECTED; 375 } 376 if (mA2dp.getConnectionState(mDevice) != BluetoothProfile.STATE_CONNECTED) { 377 if (mIsA2dpAvailable) { 378 mA2dpResult = RESULT_PENDING; 379 mA2dp.connect(mDevice); 380 } else { 381 mA2dpResult = RESULT_DISCONNECTED; 382 } 383 } else { 384 mA2dpResult = RESULT_CONNECTED; 385 } 386 if (mA2dpResult == RESULT_PENDING || mHfpResult == RESULT_PENDING) { 387 if (mRetryCount == 0) { 388 toast(getToastString(R.string.connecting_peripheral)); 389 } 390 if (mRetryCount < MAX_RETRY_COUNT) { 391 sendRetryMessage(RETRY_CONNECT_WAIT_TIME_MS); 392 break; 393 } 394 } 395 } 396 } 397 // fall-through 398 case STATE_CONNECTING: 399 if (mTransport != BluetoothDevice.TRANSPORT_LE) { 400 if (mA2dpResult == RESULT_PENDING || mHfpResult == RESULT_PENDING) { 401 // another connection type still pending 402 break; 403 } 404 if (mA2dpResult == RESULT_CONNECTED || mHfpResult == RESULT_CONNECTED) { 405 // we'll take either as success 406 toast(getToastString(R.string.connected_peripheral)); 407 if (mA2dpResult == RESULT_CONNECTED) startTheMusic(); 408 mDevice.setAlias(mName); 409 complete(true); 410 } else { 411 toast (getToastString(R.string.connect_peripheral_failed)); 412 complete(false); 413 } 414 } 415 break; 416 } 417 } 418 startBonding()419 void startBonding() { 420 mState = STATE_BONDING; 421 if (mRetryCount == 0) { 422 toast(getToastString(R.string.pairing_peripheral)); 423 } 424 if (mOobData != null) { 425 if (!mDevice.createBondOutOfBand(mTransport, mOobData)) { 426 toast(getToastString(R.string.pairing_peripheral_failed)); 427 complete(false); 428 } 429 } else if (!mDevice.createBond(mTransport)) { 430 toast(getToastString(R.string.pairing_peripheral_failed)); 431 complete(false); 432 } 433 } 434 handleIntent(Intent intent)435 void handleIntent(Intent intent) { 436 String action = intent.getAction(); 437 // Everything requires the device to match... 438 BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE); 439 if (!mDevice.equals(device)) return; 440 441 if (ACTION_ALLOW_CONNECT.equals(action)) { 442 mHandler.removeMessages(MSG_TIMEOUT); 443 mHandler.sendMessageDelayed(mHandler.obtainMessage(MSG_TIMEOUT), TIMEOUT_MS); 444 nextStepConnect(); 445 } else if (ACTION_DENY_CONNECT.equals(action)) { 446 complete(false); 447 } else if (BluetoothDevice.ACTION_BOND_STATE_CHANGED.equals(action) 448 && mState == STATE_BONDING) { 449 int bond = intent.getIntExtra(BluetoothDevice.EXTRA_BOND_STATE, 450 BluetoothAdapter.ERROR); 451 if (bond == BluetoothDevice.BOND_BONDED) { 452 mRetryCount = 0; 453 nextStepConnect(); 454 } else if (bond == BluetoothDevice.BOND_NONE) { 455 if (mRetryCount < MAX_RETRY_COUNT) { 456 sendRetryMessage(RETRY_PAIRING_WAIT_TIME_MS); 457 } else { 458 toast(getToastString(R.string.pairing_peripheral_failed)); 459 complete(false); 460 } 461 } 462 } else if (BluetoothHeadset.ACTION_CONNECTION_STATE_CHANGED.equals(action) && 463 (mState == STATE_CONNECTING || mState == STATE_DISCONNECTING)) { 464 int state = intent.getIntExtra(BluetoothProfile.EXTRA_STATE, BluetoothAdapter.ERROR); 465 if (state == BluetoothProfile.STATE_CONNECTED) { 466 mHfpResult = RESULT_CONNECTED; 467 nextStep(); 468 } else if (state == BluetoothProfile.STATE_DISCONNECTED) { 469 if (mAction == ACTION_CONNECT && mRetryCount < MAX_RETRY_COUNT) { 470 sendRetryMessage(RETRY_CONNECT_WAIT_TIME_MS); 471 } else { 472 mHfpResult = RESULT_DISCONNECTED; 473 nextStep(); 474 } 475 } 476 } else if (BluetoothA2dp.ACTION_CONNECTION_STATE_CHANGED.equals(action) && 477 (mState == STATE_CONNECTING || mState == STATE_DISCONNECTING)) { 478 int state = intent.getIntExtra(BluetoothProfile.EXTRA_STATE, BluetoothAdapter.ERROR); 479 if (state == BluetoothProfile.STATE_CONNECTED) { 480 mA2dpResult = RESULT_CONNECTED; 481 nextStep(); 482 } else if (state == BluetoothProfile.STATE_DISCONNECTED) { 483 if (mAction == ACTION_CONNECT && mRetryCount < MAX_RETRY_COUNT) { 484 sendRetryMessage(RETRY_CONNECT_WAIT_TIME_MS); 485 } else { 486 mA2dpResult = RESULT_DISCONNECTED; 487 nextStep(); 488 } 489 } 490 } else if (BluetoothInputDevice.ACTION_CONNECTION_STATE_CHANGED.equals(action) && 491 (mState == STATE_CONNECTING || mState == STATE_DISCONNECTING)) { 492 int state = intent.getIntExtra(BluetoothProfile.EXTRA_STATE, BluetoothAdapter.ERROR); 493 if (state == BluetoothProfile.STATE_CONNECTED) { 494 mHidResult = RESULT_CONNECTED; 495 nextStep(); 496 } else if (state == BluetoothProfile.STATE_DISCONNECTED) { 497 mHidResult = RESULT_DISCONNECTED; 498 nextStep(); 499 } 500 } 501 } 502 complete(boolean connected)503 void complete(boolean connected) { 504 if (DBG) Log.d(TAG, "complete()"); 505 mState = STATE_COMPLETE; 506 mContext.unregisterReceiver(mReceiver); 507 mHandler.removeMessages(MSG_TIMEOUT); 508 mHandler.removeMessages(MSG_RETRY); 509 synchronized (mLock) { 510 if (mA2dp != null) { 511 mBluetoothAdapter.closeProfileProxy(BluetoothProfile.A2DP, mA2dp); 512 } 513 if (mHeadset != null) { 514 mBluetoothAdapter.closeProfileProxy(BluetoothProfile.HEADSET, mHeadset); 515 } 516 517 if (mInput != null) { 518 mBluetoothAdapter.closeProfileProxy(BluetoothProfile.INPUT_DEVICE, mInput); 519 } 520 521 mA2dp = null; 522 mHeadset = null; 523 mInput = null; 524 } 525 mCallback.onBluetoothPeripheralHandoverComplete(connected); 526 } 527 toast(CharSequence text)528 void toast(CharSequence text) { 529 Toast.makeText(mContext, text, Toast.LENGTH_SHORT).show(); 530 } 531 startTheMusic()532 void startTheMusic() { 533 MediaSessionLegacyHelper helper = MediaSessionLegacyHelper.getHelper(mContext); 534 if (helper != null) { 535 KeyEvent keyEvent = new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_MEDIA_PLAY); 536 helper.sendMediaButtonEvent(keyEvent, false); 537 keyEvent = new KeyEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_MEDIA_PLAY); 538 helper.sendMediaButtonEvent(keyEvent, false); 539 } else { 540 Log.w(TAG, "Unable to send media key event"); 541 } 542 } 543 requestPairConfirmation()544 void requestPairConfirmation() { 545 Intent dialogIntent = new Intent(mContext, ConfirmConnectActivity.class); 546 dialogIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK); 547 dialogIntent.putExtra(BluetoothDevice.EXTRA_DEVICE, mDevice); 548 dialogIntent.putExtra(BluetoothDevice.EXTRA_NAME, mName); 549 550 mContext.startActivity(dialogIntent); 551 } 552 hasA2dpCapability(ParcelUuid[] uuids, BluetoothClass btClass)553 boolean hasA2dpCapability(ParcelUuid[] uuids, BluetoothClass btClass) { 554 if (uuids != null) { 555 for (ParcelUuid uuid : uuids) { 556 if (BluetoothUuid.isAudioSink(uuid) || BluetoothUuid.isAdvAudioDist(uuid)) { 557 return true; 558 } 559 } 560 } 561 if (btClass != null && btClass.doesClassMatch(BluetoothClass.PROFILE_A2DP)) { 562 return true; 563 } 564 return false; 565 } 566 hasHeadsetCapability(ParcelUuid[] uuids, BluetoothClass btClass)567 boolean hasHeadsetCapability(ParcelUuid[] uuids, BluetoothClass btClass) { 568 if (uuids != null) { 569 for (ParcelUuid uuid : uuids) { 570 if (BluetoothUuid.isHandsfree(uuid) || BluetoothUuid.isHeadset(uuid)) { 571 return true; 572 } 573 } 574 } 575 if (btClass != null && btClass.doesClassMatch(BluetoothClass.PROFILE_HEADSET)) { 576 return true; 577 } 578 return false; 579 } 580 581 final Handler mHandler = new Handler() { 582 @Override 583 public void handleMessage(Message msg) { 584 switch (msg.what) { 585 case MSG_TIMEOUT: 586 if (mState == STATE_COMPLETE) return; 587 Log.i(TAG, "Timeout completing BT handover"); 588 mContext.sendBroadcast(new Intent(ACTION_TIMEOUT_CONNECT)); 589 complete(false); 590 break; 591 case MSG_NEXT_STEP: 592 nextStep(); 593 break; 594 case MSG_RETRY: 595 mHandler.removeMessages(MSG_RETRY); 596 if (mState == STATE_BONDING) { 597 mState = STATE_WAITING_FOR_BOND_CONFIRMATION; 598 } else if (mState == STATE_CONNECTING) { 599 mState = STATE_BONDING; 600 } 601 mRetryCount++; 602 nextStepConnect(); 603 break; 604 } 605 } 606 }; 607 608 final BroadcastReceiver mReceiver = new BroadcastReceiver() { 609 @Override 610 public void onReceive(Context context, Intent intent) { 611 handleIntent(intent); 612 } 613 }; 614 checkMainThread()615 static void checkMainThread() { 616 if (Looper.myLooper() != Looper.getMainLooper()) { 617 throw new IllegalThreadStateException("must be called on main thread"); 618 } 619 } 620 621 @Override onServiceConnected(int profile, BluetoothProfile proxy)622 public void onServiceConnected(int profile, BluetoothProfile proxy) { 623 synchronized (mLock) { 624 switch (profile) { 625 case BluetoothProfile.HEADSET: 626 mHeadset = (BluetoothHeadset) proxy; 627 if (mA2dp != null) { 628 mHandler.sendEmptyMessage(MSG_NEXT_STEP); 629 } 630 break; 631 case BluetoothProfile.A2DP: 632 mA2dp = (BluetoothA2dp) proxy; 633 if (mHeadset != null) { 634 mHandler.sendEmptyMessage(MSG_NEXT_STEP); 635 } 636 break; 637 case BluetoothProfile.INPUT_DEVICE: 638 mInput = (BluetoothInputDevice) proxy; 639 if (mInput != null) { 640 mHandler.sendEmptyMessage(MSG_NEXT_STEP); 641 } 642 break; 643 } 644 } 645 } 646 647 @Override onServiceDisconnected(int profile)648 public void onServiceDisconnected(int profile) { 649 // We can ignore these 650 } 651 sendRetryMessage(int waitTime)652 void sendRetryMessage(int waitTime) { 653 if (!mHandler.hasMessages(MSG_RETRY)) { 654 mHandler.sendMessageDelayed(mHandler.obtainMessage(MSG_RETRY), waitTime); 655 } 656 } 657 } 658