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