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