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, mOobData)) { 457 toast(getToastString(R.string.pairing_peripheral_failed)); 458 complete(false); 459 } 460 } else if (!mDevice.createBond(mTransport)) { 461 toast(getToastString(R.string.pairing_peripheral_failed)); 462 complete(false); 463 } 464 } 465 handleIntent(Intent intent)466 void handleIntent(Intent intent) { 467 String action = intent.getAction(); 468 // Everything requires the device to match... 469 BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE); 470 if (!mDevice.equals(device)) return; 471 472 if (ACTION_ALLOW_CONNECT.equals(action)) { 473 mHandler.removeMessages(MSG_TIMEOUT); 474 mHandler.sendMessageDelayed(mHandler.obtainMessage(MSG_TIMEOUT), TIMEOUT_MS); 475 nextStepConnect(); 476 } else if (ACTION_DENY_CONNECT.equals(action)) { 477 complete(false); 478 } else if (BluetoothDevice.ACTION_BOND_STATE_CHANGED.equals(action) 479 && mState == STATE_BONDING) { 480 int bond = intent.getIntExtra(BluetoothDevice.EXTRA_BOND_STATE, 481 BluetoothAdapter.ERROR); 482 if (bond == BluetoothDevice.BOND_BONDED) { 483 mRetryCount = 0; 484 nextStepConnect(); 485 } else if (bond == BluetoothDevice.BOND_NONE) { 486 if (mRetryCount < MAX_RETRY_COUNT) { 487 sendRetryMessage(RETRY_PAIRING_WAIT_TIME_MS); 488 } else { 489 toast(getToastString(R.string.pairing_peripheral_failed)); 490 complete(false); 491 } 492 } 493 } else if (BluetoothHeadset.ACTION_CONNECTION_STATE_CHANGED.equals(action) && 494 (mState == STATE_CONNECTING || mState == STATE_DISCONNECTING)) { 495 int state = intent.getIntExtra(BluetoothProfile.EXTRA_STATE, BluetoothAdapter.ERROR); 496 if (state == BluetoothProfile.STATE_CONNECTED) { 497 mHfpResult = RESULT_CONNECTED; 498 nextStep(); 499 } else if (state == BluetoothProfile.STATE_DISCONNECTED) { 500 if (mAction == ACTION_CONNECT && mRetryCount < MAX_RETRY_COUNT) { 501 sendRetryMessage(RETRY_CONNECT_WAIT_TIME_MS); 502 } else { 503 mHfpResult = RESULT_DISCONNECTED; 504 nextStep(); 505 } 506 } 507 } else if (BluetoothA2dp.ACTION_CONNECTION_STATE_CHANGED.equals(action) && 508 (mState == STATE_CONNECTING || mState == STATE_DISCONNECTING)) { 509 int state = intent.getIntExtra(BluetoothProfile.EXTRA_STATE, BluetoothAdapter.ERROR); 510 if (state == BluetoothProfile.STATE_CONNECTED) { 511 mA2dpResult = RESULT_CONNECTED; 512 nextStep(); 513 } else if (state == BluetoothProfile.STATE_DISCONNECTED) { 514 if (mAction == ACTION_CONNECT && mRetryCount < MAX_RETRY_COUNT) { 515 sendRetryMessage(RETRY_CONNECT_WAIT_TIME_MS); 516 } else { 517 mA2dpResult = RESULT_DISCONNECTED; 518 nextStep(); 519 } 520 } 521 } else if (BluetoothHidHost.ACTION_CONNECTION_STATE_CHANGED.equals(action) && 522 (mState == STATE_CONNECTING || mState == STATE_DISCONNECTING)) { 523 int state = intent.getIntExtra(BluetoothProfile.EXTRA_STATE, BluetoothAdapter.ERROR); 524 if (state == BluetoothProfile.STATE_CONNECTED) { 525 mHidResult = RESULT_CONNECTED; 526 nextStep(); 527 } else if (state == BluetoothProfile.STATE_DISCONNECTED) { 528 mHidResult = RESULT_DISCONNECTED; 529 nextStep(); 530 } 531 } 532 } 533 complete(boolean connected)534 void complete(boolean connected) { 535 if (DBG) Log.d(TAG, "complete()"); 536 mState = STATE_COMPLETE; 537 mContext.unregisterReceiver(mReceiver); 538 mHandler.removeMessages(MSG_TIMEOUT); 539 mHandler.removeMessages(MSG_RETRY); 540 synchronized (mLock) { 541 if (mA2dp != null) { 542 mBluetoothAdapter.closeProfileProxy(BluetoothProfile.A2DP, mA2dp); 543 } 544 if (mHeadset != null) { 545 mBluetoothAdapter.closeProfileProxy(BluetoothProfile.HEADSET, mHeadset); 546 } 547 548 if (mInput != null) { 549 mBluetoothAdapter.closeProfileProxy(BluetoothProfile.HID_HOST, mInput); 550 } 551 552 mA2dp = null; 553 mHeadset = null; 554 mInput = null; 555 } 556 mCallback.onBluetoothPeripheralHandoverComplete(connected); 557 } 558 toast(CharSequence text)559 void toast(CharSequence text) { 560 Toast.makeText(mContext, text, Toast.LENGTH_SHORT).show(); 561 } 562 startTheMusic()563 void startTheMusic() { 564 if (!mContext.getResources().getBoolean(R.bool.enable_auto_play) && !mIsMusicActive) { 565 return; 566 } 567 568 MediaSessionLegacyHelper helper = MediaSessionLegacyHelper.getHelper(mContext); 569 if (helper != null) { 570 KeyEvent keyEvent = new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_MEDIA_PLAY); 571 helper.sendMediaButtonEvent(keyEvent, false); 572 keyEvent = new KeyEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_MEDIA_PLAY); 573 helper.sendMediaButtonEvent(keyEvent, false); 574 } else { 575 Log.w(TAG, "Unable to send media key event"); 576 } 577 } 578 requestPairConfirmation()579 void requestPairConfirmation() { 580 Intent dialogIntent = new Intent(mContext, ConfirmConnectActivity.class); 581 dialogIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK); 582 dialogIntent.putExtra(BluetoothDevice.EXTRA_DEVICE, mDevice); 583 dialogIntent.putExtra(BluetoothDevice.EXTRA_NAME, mName); 584 585 mContext.startActivity(dialogIntent); 586 } 587 hasA2dpCapability(ParcelUuid[] uuids, BluetoothClass btClass)588 boolean hasA2dpCapability(ParcelUuid[] uuids, BluetoothClass btClass) { 589 if (uuids != null) { 590 for (ParcelUuid uuid : uuids) { 591 if (uuid.equals(BluetoothUuid.A2DP_SINK) || uuid.equals(BluetoothUuid.ADV_AUDIO_DIST)) { 592 return true; 593 } 594 } 595 } 596 if (btClass != null && btClass.doesClassMatch(BluetoothClass.PROFILE_A2DP)) { 597 return true; 598 } 599 return false; 600 } 601 hasHeadsetCapability(ParcelUuid[] uuids, BluetoothClass btClass)602 boolean hasHeadsetCapability(ParcelUuid[] uuids, BluetoothClass btClass) { 603 if (uuids != null) { 604 for (ParcelUuid uuid : uuids) { 605 if (uuid.equals(BluetoothUuid.HFP) || uuid.equals(BluetoothUuid.HSP)) { 606 return true; 607 } 608 } 609 } 610 if (btClass != null && btClass.doesClassMatch(BluetoothClass.PROFILE_HEADSET)) { 611 return true; 612 } 613 return false; 614 } 615 616 final Handler mHandler = new Handler() { 617 @Override 618 public void handleMessage(Message msg) { 619 switch (msg.what) { 620 case MSG_TIMEOUT: 621 if (mState == STATE_COMPLETE) return; 622 Log.i(TAG, "Timeout completing BT handover"); 623 if (mState == STATE_WAITING_FOR_BOND_CONFIRMATION) { 624 mContext.sendBroadcast(new Intent(ACTION_TIMEOUT_CONNECT)); 625 } else if (mState == STATE_BONDING) { 626 toast(getToastString(R.string.pairing_peripheral_failed)); 627 } else if (mState == STATE_CONNECTING) { 628 if (mHidResult == RESULT_PENDING) { 629 mHidResult = RESULT_DISCONNECTED; 630 } 631 if (mA2dpResult == RESULT_PENDING) { 632 mA2dpResult = RESULT_DISCONNECTED; 633 } 634 if (mHfpResult == RESULT_PENDING) { 635 mHfpResult = RESULT_DISCONNECTED; 636 } 637 // Check if any one profile is connected, then it takes as success 638 nextStepConnect(); 639 break; 640 } 641 complete(false); 642 break; 643 case MSG_NEXT_STEP: 644 nextStep(); 645 break; 646 case MSG_RETRY: 647 mHandler.removeMessages(MSG_RETRY); 648 if (mState == STATE_BONDING) { 649 mState = STATE_WAITING_FOR_BOND_CONFIRMATION; 650 } else if (mState == STATE_CONNECTING) { 651 mState = STATE_BONDING; 652 } 653 mRetryCount++; 654 nextStepConnect(); 655 break; 656 } 657 } 658 }; 659 660 final BroadcastReceiver mReceiver = new BroadcastReceiver() { 661 @Override 662 public void onReceive(Context context, Intent intent) { 663 handleIntent(intent); 664 } 665 }; 666 checkMainThread()667 static void checkMainThread() { 668 if (Looper.myLooper() != Looper.getMainLooper()) { 669 throw new IllegalThreadStateException("must be called on main thread"); 670 } 671 } 672 673 @Override onServiceConnected(int profile, BluetoothProfile proxy)674 public void onServiceConnected(int profile, BluetoothProfile proxy) { 675 synchronized (mLock) { 676 switch (profile) { 677 case BluetoothProfile.HEADSET: 678 mHeadset = (BluetoothHeadset) proxy; 679 if (mA2dp != null) { 680 mHandler.sendEmptyMessage(MSG_NEXT_STEP); 681 } 682 break; 683 case BluetoothProfile.A2DP: 684 mA2dp = (BluetoothA2dp) proxy; 685 if (mHeadset != null) { 686 mHandler.sendEmptyMessage(MSG_NEXT_STEP); 687 } 688 break; 689 case BluetoothProfile.HID_HOST: 690 mInput = (BluetoothHidHost) proxy; 691 if (mInput != null) { 692 mHandler.sendEmptyMessage(MSG_NEXT_STEP); 693 } 694 break; 695 } 696 } 697 } 698 699 @Override onServiceDisconnected(int profile)700 public void onServiceDisconnected(int profile) { 701 // We can ignore these 702 } 703 sendRetryMessage(int waitTime)704 void sendRetryMessage(int waitTime) { 705 if (!mHandler.hasMessages(MSG_RETRY)) { 706 mHandler.sendMessageDelayed(mHandler.obtainMessage(MSG_RETRY), waitTime); 707 } 708 } 709 } 710