BluetoothTestUtils.java revision b525f089cf0f580ccb26e80bbb0db1376ba554cc
1/* 2 * Copyright (C) 2010 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 17package android.bluetooth; 18 19import android.bluetooth.BluetoothPan; 20import android.bluetooth.BluetoothProfile; 21import android.content.BroadcastReceiver; 22import android.content.Context; 23import android.content.Intent; 24import android.content.IntentFilter; 25import android.media.AudioManager; 26import android.os.Environment; 27import android.util.Log; 28 29import junit.framework.Assert; 30 31import java.io.BufferedWriter; 32import java.io.File; 33import java.io.FileWriter; 34import java.io.IOException; 35import java.util.ArrayList; 36import java.util.List; 37 38public class BluetoothTestUtils extends Assert { 39 40 /** Timeout for enable/disable in ms. */ 41 private static final int ENABLE_DISABLE_TIMEOUT = 20000; 42 /** Timeout for discoverable/undiscoverable in ms. */ 43 private static final int DISCOVERABLE_UNDISCOVERABLE_TIMEOUT = 5000; 44 /** Timeout for starting/stopping a scan in ms. */ 45 private static final int START_STOP_SCAN_TIMEOUT = 5000; 46 /** Timeout for pair/unpair in ms. */ 47 private static final int PAIR_UNPAIR_TIMEOUT = 20000; 48 /** Timeout for connecting/disconnecting a profile in ms. */ 49 private static final int CONNECT_DISCONNECT_PROFILE_TIMEOUT = 20000; 50 /** Timeout to start or stop a SCO channel in ms. */ 51 private static final int START_STOP_SCO_TIMEOUT = 10000; 52 /** Timeout to connect a profile proxy in ms. */ 53 private static final int CONNECT_PROXY_TIMEOUT = 5000; 54 /** Time between polls in ms. */ 55 private static final int POLL_TIME = 100; 56 57 private abstract class FlagReceiver extends BroadcastReceiver { 58 private int mExpectedFlags = 0; 59 private int mFiredFlags = 0; 60 private long mCompletedTime = -1; 61 62 public FlagReceiver(int expectedFlags) { 63 mExpectedFlags = expectedFlags; 64 } 65 66 public int getFiredFlags() { 67 synchronized (this) { 68 return mFiredFlags; 69 } 70 } 71 72 public long getCompletedTime() { 73 synchronized (this) { 74 return mCompletedTime; 75 } 76 } 77 78 protected void setFiredFlag(int flag) { 79 synchronized (this) { 80 mFiredFlags |= flag; 81 if ((mFiredFlags & mExpectedFlags) == mExpectedFlags) { 82 mCompletedTime = System.currentTimeMillis(); 83 } 84 } 85 } 86 } 87 88 private class BluetoothReceiver extends FlagReceiver { 89 private static final int DISCOVERY_STARTED_FLAG = 1; 90 private static final int DISCOVERY_FINISHED_FLAG = 1 << 1; 91 private static final int SCAN_MODE_NONE_FLAG = 1 << 2; 92 private static final int SCAN_MODE_CONNECTABLE_FLAG = 1 << 3; 93 private static final int SCAN_MODE_CONNECTABLE_DISCOVERABLE_FLAG = 1 << 4; 94 private static final int STATE_OFF_FLAG = 1 << 5; 95 private static final int STATE_TURNING_ON_FLAG = 1 << 6; 96 private static final int STATE_ON_FLAG = 1 << 7; 97 private static final int STATE_TURNING_OFF_FLAG = 1 << 8; 98 99 public BluetoothReceiver(int expectedFlags) { 100 super(expectedFlags); 101 } 102 103 @Override 104 public void onReceive(Context context, Intent intent) { 105 if (BluetoothAdapter.ACTION_DISCOVERY_STARTED.equals(intent.getAction())) { 106 setFiredFlag(DISCOVERY_STARTED_FLAG); 107 } else if (BluetoothAdapter.ACTION_DISCOVERY_FINISHED.equals(intent.getAction())) { 108 setFiredFlag(DISCOVERY_FINISHED_FLAG); 109 } else if (BluetoothAdapter.ACTION_SCAN_MODE_CHANGED.equals(intent.getAction())) { 110 int mode = intent.getIntExtra(BluetoothAdapter.EXTRA_SCAN_MODE, -1); 111 assertNotSame(-1, mode); 112 switch (mode) { 113 case BluetoothAdapter.SCAN_MODE_NONE: 114 setFiredFlag(SCAN_MODE_NONE_FLAG); 115 break; 116 case BluetoothAdapter.SCAN_MODE_CONNECTABLE: 117 setFiredFlag(SCAN_MODE_CONNECTABLE_FLAG); 118 break; 119 case BluetoothAdapter.SCAN_MODE_CONNECTABLE_DISCOVERABLE: 120 setFiredFlag(SCAN_MODE_CONNECTABLE_DISCOVERABLE_FLAG); 121 break; 122 } 123 } else if (BluetoothAdapter.ACTION_STATE_CHANGED.equals(intent.getAction())) { 124 int state = intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, -1); 125 assertNotSame(-1, state); 126 switch (state) { 127 case BluetoothAdapter.STATE_OFF: 128 setFiredFlag(STATE_OFF_FLAG); 129 break; 130 case BluetoothAdapter.STATE_TURNING_ON: 131 setFiredFlag(STATE_TURNING_ON_FLAG); 132 break; 133 case BluetoothAdapter.STATE_ON: 134 setFiredFlag(STATE_ON_FLAG); 135 break; 136 case BluetoothAdapter.STATE_TURNING_OFF: 137 setFiredFlag(STATE_TURNING_OFF_FLAG); 138 break; 139 } 140 } 141 } 142 } 143 144 private class PairReceiver extends FlagReceiver { 145 private static final int STATE_BONDED_FLAG = 1; 146 private static final int STATE_BONDING_FLAG = 1 << 1; 147 private static final int STATE_NONE_FLAG = 1 << 2; 148 149 private BluetoothDevice mDevice; 150 private int mPasskey; 151 private byte[] mPin; 152 153 public PairReceiver(BluetoothDevice device, int passkey, byte[] pin, int expectedFlags) { 154 super(expectedFlags); 155 156 mDevice = device; 157 mPasskey = passkey; 158 mPin = pin; 159 } 160 161 @Override 162 public void onReceive(Context context, Intent intent) { 163 if (!mDevice.equals(intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE))) { 164 return; 165 } 166 167 if (BluetoothDevice.ACTION_PAIRING_REQUEST.equals(intent.getAction())) { 168 int varient = intent.getIntExtra(BluetoothDevice.EXTRA_PAIRING_VARIANT, -1); 169 assertNotSame(-1, varient); 170 switch (varient) { 171 case BluetoothDevice.PAIRING_VARIANT_PIN: 172 mDevice.setPin(mPin); 173 break; 174 case BluetoothDevice.PAIRING_VARIANT_PASSKEY: 175 mDevice.setPasskey(mPasskey); 176 break; 177 case BluetoothDevice.PAIRING_VARIANT_PASSKEY_CONFIRMATION: 178 case BluetoothDevice.PAIRING_VARIANT_CONSENT: 179 mDevice.setPairingConfirmation(true); 180 break; 181 case BluetoothDevice.PAIRING_VARIANT_OOB_CONSENT: 182 mDevice.setRemoteOutOfBandData(); 183 break; 184 } 185 } else if (BluetoothDevice.ACTION_BOND_STATE_CHANGED.equals(intent.getAction())) { 186 int state = intent.getIntExtra(BluetoothDevice.EXTRA_BOND_STATE, -1); 187 assertNotSame(-1, state); 188 switch (state) { 189 case BluetoothDevice.BOND_NONE: 190 setFiredFlag(STATE_NONE_FLAG); 191 break; 192 case BluetoothDevice.BOND_BONDING: 193 setFiredFlag(STATE_BONDING_FLAG); 194 break; 195 case BluetoothDevice.BOND_BONDED: 196 setFiredFlag(STATE_BONDED_FLAG); 197 break; 198 } 199 } 200 } 201 } 202 203 private class ConnectProfileReceiver extends FlagReceiver { 204 private static final int STATE_DISCONNECTED_FLAG = 1; 205 private static final int STATE_CONNECTING_FLAG = 1 << 1; 206 private static final int STATE_CONNECTED_FLAG = 1 << 2; 207 private static final int STATE_DISCONNECTING_FLAG = 1 << 3; 208 209 private BluetoothDevice mDevice; 210 private int mProfile; 211 private String mConnectionAction; 212 213 public ConnectProfileReceiver(BluetoothDevice device, int profile, int expectedFlags) { 214 super(expectedFlags); 215 216 mDevice = device; 217 mProfile = profile; 218 219 switch (mProfile) { 220 case BluetoothProfile.A2DP: 221 mConnectionAction = BluetoothA2dp.ACTION_CONNECTION_STATE_CHANGED; 222 break; 223 case BluetoothProfile.HEADSET: 224 mConnectionAction = BluetoothHeadset.ACTION_CONNECTION_STATE_CHANGED; 225 break; 226 case BluetoothProfile.INPUT_DEVICE: 227 mConnectionAction = BluetoothInputDevice.ACTION_CONNECTION_STATE_CHANGED; 228 break; 229 case BluetoothProfile.PAN: 230 mConnectionAction = BluetoothPan.ACTION_CONNECTION_STATE_CHANGED; 231 break; 232 default: 233 mConnectionAction = null; 234 } 235 } 236 237 @Override 238 public void onReceive(Context context, Intent intent) { 239 if (mConnectionAction != null && mConnectionAction.equals(intent.getAction())) { 240 if (!mDevice.equals(intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE))) { 241 return; 242 } 243 244 int state = intent.getIntExtra(BluetoothProfile.EXTRA_STATE, -1); 245 assertNotSame(-1, state); 246 switch (state) { 247 case BluetoothProfile.STATE_DISCONNECTED: 248 setFiredFlag(STATE_DISCONNECTED_FLAG); 249 break; 250 case BluetoothProfile.STATE_CONNECTING: 251 setFiredFlag(STATE_CONNECTING_FLAG); 252 break; 253 case BluetoothProfile.STATE_CONNECTED: 254 setFiredFlag(STATE_CONNECTED_FLAG); 255 break; 256 case BluetoothProfile.STATE_DISCONNECTING: 257 setFiredFlag(STATE_DISCONNECTING_FLAG); 258 break; 259 } 260 } 261 } 262 } 263 264 private class ConnectPanReceiver extends ConnectProfileReceiver { 265 private int mRole; 266 267 public ConnectPanReceiver(BluetoothDevice device, int role, int expectedFlags) { 268 super(device, BluetoothProfile.PAN, expectedFlags); 269 270 mRole = role; 271 } 272 273 @Override 274 public void onReceive(Context context, Intent intent) { 275 if (mRole != intent.getIntExtra(BluetoothPan.EXTRA_LOCAL_ROLE, -1)) { 276 return; 277 } 278 279 super.onReceive(context, intent); 280 } 281 } 282 283 private class StartStopScoReceiver extends FlagReceiver { 284 private static final int STATE_CONNECTED_FLAG = 1; 285 private static final int STATE_DISCONNECTED_FLAG = 1 << 1; 286 287 public StartStopScoReceiver(int expectedFlags) { 288 super(expectedFlags); 289 } 290 291 @Override 292 public void onReceive(Context context, Intent intent) { 293 if (AudioManager.ACTION_SCO_AUDIO_STATE_CHANGED.equals(intent.getAction())) { 294 int state = intent.getIntExtra(AudioManager.EXTRA_SCO_AUDIO_STATE, 295 AudioManager.SCO_AUDIO_STATE_ERROR); 296 assertNotSame(AudioManager.SCO_AUDIO_STATE_ERROR, state); 297 switch(state) { 298 case AudioManager.SCO_AUDIO_STATE_CONNECTED: 299 setFiredFlag(STATE_CONNECTED_FLAG); 300 break; 301 case AudioManager.SCO_AUDIO_STATE_DISCONNECTED: 302 setFiredFlag(STATE_DISCONNECTED_FLAG); 303 break; 304 } 305 } 306 } 307 } 308 309 private BluetoothProfile.ServiceListener mServiceListener = 310 new BluetoothProfile.ServiceListener() { 311 @Override 312 public void onServiceConnected(int profile, BluetoothProfile proxy) { 313 synchronized (this) { 314 switch (profile) { 315 case BluetoothProfile.A2DP: 316 mA2dp = (BluetoothA2dp) proxy; 317 break; 318 case BluetoothProfile.HEADSET: 319 mHeadset = (BluetoothHeadset) proxy; 320 break; 321 case BluetoothProfile.INPUT_DEVICE: 322 mInput = (BluetoothInputDevice) proxy; 323 break; 324 case BluetoothProfile.PAN: 325 mPan = (BluetoothPan) proxy; 326 break; 327 } 328 } 329 } 330 331 @Override 332 public void onServiceDisconnected(int profile) { 333 synchronized (this) { 334 switch (profile) { 335 case BluetoothProfile.A2DP: 336 mA2dp = null; 337 break; 338 case BluetoothProfile.HEADSET: 339 mHeadset = null; 340 break; 341 case BluetoothProfile.INPUT_DEVICE: 342 mInput = null; 343 break; 344 case BluetoothProfile.PAN: 345 mPan = null; 346 break; 347 } 348 } 349 } 350 }; 351 352 private List<BroadcastReceiver> mReceivers = new ArrayList<BroadcastReceiver>(); 353 354 private BufferedWriter mOutputWriter; 355 private String mTag; 356 private String mOutputFile; 357 358 private Context mContext; 359 private BluetoothA2dp mA2dp = null; 360 private BluetoothHeadset mHeadset = null; 361 private BluetoothInputDevice mInput = null; 362 private BluetoothPan mPan = null; 363 364 /** 365 * Creates a utility instance for testing Bluetooth. 366 * 367 * @param context The context of the application using the utility. 368 * @param tag The log tag of the application using the utility. 369 */ 370 public BluetoothTestUtils(Context context, String tag) { 371 this(context, tag, null); 372 } 373 374 /** 375 * Creates a utility instance for testing Bluetooth. 376 * 377 * @param context The context of the application using the utility. 378 * @param tag The log tag of the application using the utility. 379 * @param outputFile The path to an output file if the utility is to write results to a 380 * separate file. 381 */ 382 public BluetoothTestUtils(Context context, String tag, String outputFile) { 383 mContext = context; 384 mTag = tag; 385 mOutputFile = outputFile; 386 387 if (mOutputFile == null) { 388 mOutputWriter = null; 389 } else { 390 try { 391 mOutputWriter = new BufferedWriter(new FileWriter(new File( 392 Environment.getExternalStorageDirectory(), mOutputFile), true)); 393 } catch (IOException e) { 394 Log.w(mTag, "Test output file could not be opened", e); 395 mOutputWriter = null; 396 } 397 } 398 } 399 400 /** 401 * Closes the utility instance and unregisters any BroadcastReceivers. 402 */ 403 public void close() { 404 while (!mReceivers.isEmpty()) { 405 mContext.unregisterReceiver(mReceivers.remove(0)); 406 } 407 408 if (mOutputWriter != null) { 409 try { 410 mOutputWriter.close(); 411 } catch (IOException e) { 412 Log.w(mTag, "Test output file could not be closed", e); 413 } 414 } 415 } 416 417 /** 418 * Enables Bluetooth and checks to make sure that Bluetooth was turned on and that the correct 419 * actions were broadcast. 420 * 421 * @param adapter The BT adapter. 422 */ 423 public void enable(BluetoothAdapter adapter) { 424 int mask = (BluetoothReceiver.STATE_TURNING_ON_FLAG | BluetoothReceiver.STATE_ON_FLAG 425 | BluetoothReceiver.SCAN_MODE_CONNECTABLE_FLAG); 426 long start = -1; 427 BluetoothReceiver receiver = getBluetoothReceiver(mask); 428 429 int state = adapter.getState(); 430 switch (state) { 431 case BluetoothAdapter.STATE_ON: 432 assertTrue(adapter.isEnabled()); 433 removeReceiver(receiver); 434 return; 435 case BluetoothAdapter.STATE_TURNING_ON: 436 assertFalse(adapter.isEnabled()); 437 mask = 0; // Don't check for received intents since we might have missed them. 438 break; 439 case BluetoothAdapter.STATE_OFF: 440 assertFalse(adapter.isEnabled()); 441 start = System.currentTimeMillis(); 442 assertTrue(adapter.enable()); 443 break; 444 case BluetoothAdapter.STATE_TURNING_OFF: 445 start = System.currentTimeMillis(); 446 assertTrue(adapter.enable()); 447 break; 448 default: 449 removeReceiver(receiver); 450 fail(String.format("enable() invalid state: state=%d", state)); 451 } 452 453 long s = System.currentTimeMillis(); 454 while (System.currentTimeMillis() - s < ENABLE_DISABLE_TIMEOUT) { 455 state = adapter.getState(); 456 if (state == BluetoothAdapter.STATE_ON 457 && (receiver.getFiredFlags() & mask) == mask) { 458 assertTrue(adapter.isEnabled()); 459 long finish = receiver.getCompletedTime(); 460 if (start != -1 && finish != -1) { 461 writeOutput(String.format("enable() completed in %d ms", (finish - start))); 462 } else { 463 writeOutput("enable() completed"); 464 } 465 removeReceiver(receiver); 466 return; 467 } 468 sleep(POLL_TIME); 469 } 470 471 int firedFlags = receiver.getFiredFlags(); 472 removeReceiver(receiver); 473 fail(String.format("enable() timeout: state=%d (expected %d), flags=0x%x (expected 0x%x)", 474 state, BluetoothAdapter.STATE_ON, firedFlags, mask)); 475 } 476 477 /** 478 * Disables Bluetooth and checks to make sure that Bluetooth was turned off and that the correct 479 * actions were broadcast. 480 * 481 * @param adapter The BT adapter. 482 */ 483 public void disable(BluetoothAdapter adapter) { 484 int mask = (BluetoothReceiver.STATE_TURNING_OFF_FLAG | BluetoothReceiver.STATE_OFF_FLAG 485 | BluetoothReceiver.SCAN_MODE_NONE_FLAG); 486 long start = -1; 487 BluetoothReceiver receiver = getBluetoothReceiver(mask); 488 489 int state = adapter.getState(); 490 switch (state) { 491 case BluetoothAdapter.STATE_OFF: 492 assertFalse(adapter.isEnabled()); 493 removeReceiver(receiver); 494 return; 495 case BluetoothAdapter.STATE_TURNING_ON: 496 assertFalse(adapter.isEnabled()); 497 start = System.currentTimeMillis(); 498 break; 499 case BluetoothAdapter.STATE_ON: 500 assertTrue(adapter.isEnabled()); 501 start = System.currentTimeMillis(); 502 assertTrue(adapter.disable()); 503 break; 504 case BluetoothAdapter.STATE_TURNING_OFF: 505 assertFalse(adapter.isEnabled()); 506 mask = 0; // Don't check for received intents since we might have missed them. 507 break; 508 default: 509 removeReceiver(receiver); 510 fail(String.format("disable() invalid state: state=%d", state)); 511 } 512 513 long s = System.currentTimeMillis(); 514 while (System.currentTimeMillis() - s < ENABLE_DISABLE_TIMEOUT) { 515 state = adapter.getState(); 516 if (state == BluetoothAdapter.STATE_OFF 517 && (receiver.getFiredFlags() & mask) == mask) { 518 assertFalse(adapter.isEnabled()); 519 long finish = receiver.getCompletedTime(); 520 if (start != -1 && finish != -1) { 521 writeOutput(String.format("disable() completed in %d ms", (finish - start))); 522 } else { 523 writeOutput("disable() completed"); 524 } 525 removeReceiver(receiver); 526 return; 527 } 528 sleep(POLL_TIME); 529 } 530 531 int firedFlags = receiver.getFiredFlags(); 532 removeReceiver(receiver); 533 fail(String.format("disable() timeout: state=%d (expected %d), flags=0x%x (expected 0x%x)", 534 state, BluetoothAdapter.STATE_OFF, firedFlags, mask)); 535 } 536 537 /** 538 * Puts the local device into discoverable mode and checks to make sure that the local device 539 * is in discoverable mode and that the correct actions were broadcast. 540 * 541 * @param adapter The BT adapter. 542 */ 543 public void discoverable(BluetoothAdapter adapter) { 544 int mask = BluetoothReceiver.SCAN_MODE_CONNECTABLE_DISCOVERABLE_FLAG; 545 546 if (!adapter.isEnabled()) { 547 fail("discoverable() bluetooth not enabled"); 548 } 549 550 int scanMode = adapter.getScanMode(); 551 if (scanMode == BluetoothAdapter.SCAN_MODE_CONNECTABLE_DISCOVERABLE) { 552 return; 553 } 554 555 BluetoothReceiver receiver = getBluetoothReceiver(mask); 556 557 assertEquals(BluetoothAdapter.SCAN_MODE_CONNECTABLE, scanMode); 558 long start = System.currentTimeMillis(); 559 assertTrue(adapter.setScanMode(BluetoothAdapter.SCAN_MODE_CONNECTABLE_DISCOVERABLE)); 560 561 while (System.currentTimeMillis() - start < DISCOVERABLE_UNDISCOVERABLE_TIMEOUT) { 562 scanMode = adapter.getScanMode(); 563 if (scanMode == BluetoothAdapter.SCAN_MODE_CONNECTABLE_DISCOVERABLE 564 && (receiver.getFiredFlags() & mask) == mask) { 565 writeOutput(String.format("discoverable() completed in %d ms", 566 (receiver.getCompletedTime() - start))); 567 removeReceiver(receiver); 568 return; 569 } 570 sleep(POLL_TIME); 571 } 572 573 int firedFlags = receiver.getFiredFlags(); 574 removeReceiver(receiver); 575 fail(String.format("discoverable() timeout: scanMode=%d (expected %d), flags=0x%x " 576 + "(expected 0x%x)", scanMode, BluetoothAdapter.SCAN_MODE_CONNECTABLE_DISCOVERABLE, 577 firedFlags, mask)); 578 } 579 580 /** 581 * Puts the local device into connectable only mode and checks to make sure that the local 582 * device is in in connectable mode and that the correct actions were broadcast. 583 * 584 * @param adapter The BT adapter. 585 */ 586 public void undiscoverable(BluetoothAdapter adapter) { 587 int mask = BluetoothReceiver.SCAN_MODE_CONNECTABLE_FLAG; 588 589 if (!adapter.isEnabled()) { 590 fail("undiscoverable() bluetooth not enabled"); 591 } 592 593 int scanMode = adapter.getScanMode(); 594 if (scanMode == BluetoothAdapter.SCAN_MODE_CONNECTABLE) { 595 return; 596 } 597 598 BluetoothReceiver receiver = getBluetoothReceiver(mask); 599 600 assertEquals(BluetoothAdapter.SCAN_MODE_CONNECTABLE_DISCOVERABLE, scanMode); 601 long start = System.currentTimeMillis(); 602 assertTrue(adapter.setScanMode(BluetoothAdapter.SCAN_MODE_CONNECTABLE)); 603 604 while (System.currentTimeMillis() - start < DISCOVERABLE_UNDISCOVERABLE_TIMEOUT) { 605 scanMode = adapter.getScanMode(); 606 if (scanMode == BluetoothAdapter.SCAN_MODE_CONNECTABLE 607 && (receiver.getFiredFlags() & mask) == mask) { 608 writeOutput(String.format("undiscoverable() completed in %d ms", 609 (receiver.getCompletedTime() - start))); 610 removeReceiver(receiver); 611 return; 612 } 613 sleep(POLL_TIME); 614 } 615 616 int firedFlags = receiver.getFiredFlags(); 617 removeReceiver(receiver); 618 fail(String.format("undiscoverable() timeout: scanMode=%d (expected %d), flags=0x%x " 619 + "(expected 0x%x)", scanMode, BluetoothAdapter.SCAN_MODE_CONNECTABLE, firedFlags, 620 mask)); 621 } 622 623 /** 624 * Starts a scan for remote devices and checks to make sure that the local device is scanning 625 * and that the correct actions were broadcast. 626 * 627 * @param adapter The BT adapter. 628 */ 629 public void startScan(BluetoothAdapter adapter) { 630 int mask = BluetoothReceiver.DISCOVERY_STARTED_FLAG; 631 632 if (!adapter.isEnabled()) { 633 fail("startScan() bluetooth not enabled"); 634 } 635 636 if (adapter.isDiscovering()) { 637 return; 638 } 639 640 BluetoothReceiver receiver = getBluetoothReceiver(mask); 641 642 long start = System.currentTimeMillis(); 643 assertTrue(adapter.startDiscovery()); 644 645 while (System.currentTimeMillis() - start < START_STOP_SCAN_TIMEOUT) { 646 if (adapter.isDiscovering() && ((receiver.getFiredFlags() & mask) == mask)) { 647 writeOutput(String.format("startScan() completed in %d ms", 648 (receiver.getCompletedTime() - start))); 649 removeReceiver(receiver); 650 return; 651 } 652 sleep(POLL_TIME); 653 } 654 655 int firedFlags = receiver.getFiredFlags(); 656 removeReceiver(receiver); 657 fail(String.format("startScan() timeout: isDiscovering=%b, flags=0x%x (expected 0x%x)", 658 adapter.isDiscovering(), firedFlags, mask)); 659 } 660 661 /** 662 * Stops a scan for remote devices and checks to make sure that the local device is not scanning 663 * and that the correct actions were broadcast. 664 * 665 * @param adapter The BT adapter. 666 */ 667 public void stopScan(BluetoothAdapter adapter) { 668 int mask = BluetoothReceiver.DISCOVERY_FINISHED_FLAG; 669 670 if (!adapter.isEnabled()) { 671 fail("stopScan() bluetooth not enabled"); 672 } 673 674 if (!adapter.isDiscovering()) { 675 return; 676 } 677 678 BluetoothReceiver receiver = getBluetoothReceiver(mask); 679 680 long start = System.currentTimeMillis(); 681 // TODO: put assertTrue() around cancelDiscovery() once it starts returning true. 682 adapter.cancelDiscovery(); 683 684 while (System.currentTimeMillis() - start < START_STOP_SCAN_TIMEOUT) { 685 if (!adapter.isDiscovering() && ((receiver.getFiredFlags() & mask) == mask)) { 686 writeOutput(String.format("stopScan() completed in %d ms", 687 (receiver.getCompletedTime() - start))); 688 removeReceiver(receiver); 689 return; 690 } 691 sleep(POLL_TIME); 692 } 693 694 int firedFlags = receiver.getFiredFlags(); 695 removeReceiver(receiver); 696 fail(String.format("stopScan() timeout: isDiscovering=%b, flags=0x%x (expected 0x%x)", 697 adapter.isDiscovering(), firedFlags, mask)); 698 699 } 700 701 /** 702 * Enables PAN tethering on the local device and checks to make sure that tethering is enabled. 703 * 704 * @param adapter The BT adapter. 705 */ 706 public void enablePan(BluetoothAdapter adapter) { 707 if (mPan == null) mPan = (BluetoothPan) connectProxy(adapter, BluetoothProfile.PAN); 708 assertNotNull(mPan); 709 710 long start = System.currentTimeMillis(); 711 mPan.setBluetoothTethering(true); 712 long stop = System.currentTimeMillis(); 713 assertTrue(mPan.isTetheringOn()); 714 715 writeOutput(String.format("enablePan() completed in %d ms", (stop - start))); 716 } 717 718 /** 719 * Disables PAN tethering on the local device and checks to make sure that tethering is 720 * disabled. 721 * 722 * @param adapter The BT adapter. 723 */ 724 public void disablePan(BluetoothAdapter adapter) { 725 if (mPan == null) mPan = (BluetoothPan) connectProxy(adapter, BluetoothProfile.PAN); 726 assertNotNull(mPan); 727 728 long start = System.currentTimeMillis(); 729 mPan.setBluetoothTethering(false); 730 long stop = System.currentTimeMillis(); 731 assertFalse(mPan.isTetheringOn()); 732 733 writeOutput(String.format("disablePan() completed in %d ms", (stop - start))); 734 } 735 736 /** 737 * Initiates a pairing with a remote device and checks to make sure that the devices are paired 738 * and that the correct actions were broadcast. 739 * 740 * @param adapter The BT adapter. 741 * @param device The remote device. 742 * @param passkey The pairing passkey if pairing requires a passkey. Any value if not. 743 * @param pin The pairing pin if pairing requires a pin. Any value if not. 744 */ 745 public void pair(BluetoothAdapter adapter, BluetoothDevice device, int passkey, byte[] pin) { 746 pairOrAcceptPair(adapter, device, passkey, pin, true); 747 } 748 749 /** 750 * Accepts a pairing with a remote device and checks to make sure that the devices are paired 751 * and that the correct actions were broadcast. 752 * 753 * @param adapter The BT adapter. 754 * @param device The remote device. 755 * @param passkey The pairing passkey if pairing requires a passkey. Any value if not. 756 * @param pin The pairing pin if pairing requires a pin. Any value if not. 757 */ 758 public void acceptPair(BluetoothAdapter adapter, BluetoothDevice device, int passkey, 759 byte[] pin) { 760 pairOrAcceptPair(adapter, device, passkey, pin, false); 761 } 762 763 /** 764 * Helper method used by {@link #pair(BluetoothAdapter, BluetoothDevice, int, byte[])} and 765 * {@link #acceptPair(BluetoothAdapter, BluetoothDevice, int, byte[])} to either pair or accept 766 * a pairing request. 767 * 768 * @param adapter The BT adapter. 769 * @param device The remote device. 770 * @param passkey The pairing passkey if pairing requires a passkey. Any value if not. 771 * @param pin The pairing pin if pairing requires a pin. Any value if not. 772 * @param shouldPair Whether to pair or accept the pair. 773 */ 774 private void pairOrAcceptPair(BluetoothAdapter adapter, BluetoothDevice device, int passkey, 775 byte[] pin, boolean shouldPair) { 776 int mask = PairReceiver.STATE_BONDING_FLAG | PairReceiver.STATE_BONDED_FLAG; 777 long start = -1; 778 String methodName; 779 if (shouldPair) { 780 methodName = String.format("pair(device=%s)", device); 781 } else { 782 methodName = String.format("acceptPair(device=%s)", device); 783 } 784 785 if (!adapter.isEnabled()) { 786 fail(String.format("%s bluetooth not enabled", methodName)); 787 } 788 789 PairReceiver receiver = getPairReceiver(device, passkey, pin, mask); 790 791 int state = device.getBondState(); 792 switch (state) { 793 case BluetoothDevice.BOND_NONE: 794 assertFalse(adapter.getBondedDevices().contains(device)); 795 start = System.currentTimeMillis(); 796 if (shouldPair) { 797 assertTrue(device.createBond()); 798 } 799 break; 800 case BluetoothDevice.BOND_BONDING: 801 mask = 0; // Don't check for received intents since we might have missed them. 802 break; 803 case BluetoothDevice.BOND_BONDED: 804 assertTrue(adapter.getBondedDevices().contains(device)); 805 return; 806 default: 807 removeReceiver(receiver); 808 fail(String.format("%s invalid state: state=%d", methodName, state)); 809 } 810 811 long s = System.currentTimeMillis(); 812 while (System.currentTimeMillis() - s < PAIR_UNPAIR_TIMEOUT) { 813 state = device.getBondState(); 814 if (state == BluetoothDevice.BOND_BONDED && (receiver.getFiredFlags() & mask) == mask) { 815 assertTrue(adapter.getBondedDevices().contains(device)); 816 long finish = receiver.getCompletedTime(); 817 if (start != -1 && finish != -1) { 818 writeOutput(String.format("%s completed in %d ms", methodName, 819 (finish - start))); 820 } else { 821 writeOutput(String.format("%s completed", methodName)); 822 } 823 removeReceiver(receiver); 824 return; 825 } 826 sleep(POLL_TIME); 827 } 828 829 int firedFlags = receiver.getFiredFlags(); 830 removeReceiver(receiver); 831 fail(String.format("%s timeout: state=%d (expected %d), flags=0x%x (expected 0x%x)", 832 methodName, state, BluetoothDevice.BOND_BONDED, firedFlags, mask)); 833 } 834 835 /** 836 * Deletes a pairing with a remote device and checks to make sure that the devices are unpaired 837 * and that the correct actions were broadcast. 838 * 839 * @param adapter The BT adapter. 840 * @param device The remote device. 841 */ 842 public void unpair(BluetoothAdapter adapter, BluetoothDevice device) { 843 int mask = PairReceiver.STATE_NONE_FLAG; 844 long start = -1; 845 String methodName = String.format("unpair(device=%s)", device); 846 847 if (!adapter.isEnabled()) { 848 fail(String.format("%s bluetooth not enabled", methodName)); 849 } 850 851 PairReceiver receiver = getPairReceiver(device, 0, null, mask); 852 853 int state = device.getBondState(); 854 switch (state) { 855 case BluetoothDevice.BOND_NONE: 856 assertFalse(adapter.getBondedDevices().contains(device)); 857 removeReceiver(receiver); 858 return; 859 case BluetoothDevice.BOND_BONDING: 860 start = System.currentTimeMillis(); 861 assertTrue(device.removeBond()); 862 break; 863 case BluetoothDevice.BOND_BONDED: 864 assertTrue(adapter.getBondedDevices().contains(device)); 865 start = System.currentTimeMillis(); 866 assertTrue(device.removeBond()); 867 break; 868 default: 869 removeReceiver(receiver); 870 fail(String.format("%s invalid state: state=%d", methodName, state)); 871 } 872 873 long s = System.currentTimeMillis(); 874 while (System.currentTimeMillis() - s < PAIR_UNPAIR_TIMEOUT) { 875 if (device.getBondState() == BluetoothDevice.BOND_NONE 876 && (receiver.getFiredFlags() & mask) == mask) { 877 assertFalse(adapter.getBondedDevices().contains(device)); 878 long finish = receiver.getCompletedTime(); 879 if (start != -1 && finish != -1) { 880 writeOutput(String.format("%s completed in %d ms", methodName, 881 (finish - start))); 882 } else { 883 writeOutput(String.format("%s completed", methodName)); 884 } 885 removeReceiver(receiver); 886 return; 887 } 888 } 889 890 int firedFlags = receiver.getFiredFlags(); 891 removeReceiver(receiver); 892 fail(String.format("%s timeout: state=%d (expected %d), flags=0x%x (expected 0x%x)", 893 methodName, state, BluetoothDevice.BOND_BONDED, firedFlags, mask)); 894 } 895 896 /** 897 * Connects a profile from the local device to a remote device and checks to make sure that the 898 * profile is connected and that the correct actions were broadcast. 899 * 900 * @param adapter The BT adapter. 901 * @param device The remote device. 902 * @param profile The profile to connect. One of {@link BluetoothProfile#A2DP}, 903 * {@link BluetoothProfile#HEADSET}, or {@link BluetoothProfile#INPUT_DEVICE}. 904 * @param methodName The method name to printed in the logs. If null, will be 905 * "connectProfile(profile=<profile>, device=<device>)" 906 */ 907 public void connectProfile(BluetoothAdapter adapter, BluetoothDevice device, int profile, 908 String methodName) { 909 if (methodName == null) { 910 methodName = String.format("connectProfile(profile=%d, device=%s)", profile, device); 911 } 912 int mask = (ConnectProfileReceiver.STATE_CONNECTING_FLAG 913 | ConnectProfileReceiver.STATE_CONNECTED_FLAG); 914 long start = -1; 915 916 if (!adapter.isEnabled()) { 917 fail(String.format("%s bluetooth not enabled", methodName)); 918 } 919 920 if (!adapter.getBondedDevices().contains(device)) { 921 fail(String.format("%s device not paired", methodName)); 922 } 923 924 BluetoothProfile proxy = connectProxy(adapter, profile); 925 assertNotNull(proxy); 926 927 ConnectProfileReceiver receiver = getConnectProfileReceiver(device, profile, mask); 928 929 int state = proxy.getConnectionState(device); 930 switch (state) { 931 case BluetoothProfile.STATE_CONNECTED: 932 removeReceiver(receiver); 933 return; 934 case BluetoothProfile.STATE_CONNECTING: 935 mask = 0; // Don't check for received intents since we might have missed them. 936 break; 937 case BluetoothProfile.STATE_DISCONNECTED: 938 case BluetoothProfile.STATE_DISCONNECTING: 939 start = System.currentTimeMillis(); 940 assertTrue(proxy.connect(device)); 941 break; 942 default: 943 removeReceiver(receiver); 944 fail(String.format("%s invalid state: state=%d", methodName, state)); 945 } 946 947 long s = System.currentTimeMillis(); 948 while (System.currentTimeMillis() - s < CONNECT_DISCONNECT_PROFILE_TIMEOUT) { 949 state = proxy.getConnectionState(device); 950 if (state == BluetoothProfile.STATE_CONNECTED 951 && (receiver.getFiredFlags() & mask) == mask) { 952 long finish = receiver.getCompletedTime(); 953 if (start != -1 && finish != -1) { 954 writeOutput(String.format("%s completed in %d ms", methodName, 955 (finish - start))); 956 } else { 957 writeOutput(String.format("%s completed", methodName)); 958 } 959 removeReceiver(receiver); 960 return; 961 } 962 sleep(POLL_TIME); 963 } 964 965 int firedFlags = receiver.getFiredFlags(); 966 removeReceiver(receiver); 967 fail(String.format("%s timeout: state=%d (expected %d), flags=0x%x (expected 0x%x)", 968 methodName, state, BluetoothProfile.STATE_CONNECTED, firedFlags, mask)); 969 } 970 971 /** 972 * Disconnects a profile between the local device and a remote device and checks to make sure 973 * that the profile is disconnected and that the correct actions were broadcast. 974 * 975 * @param adapter The BT adapter. 976 * @param device The remote device. 977 * @param profile The profile to disconnect. One of {@link BluetoothProfile#A2DP}, 978 * {@link BluetoothProfile#HEADSET}, or {@link BluetoothProfile#INPUT_DEVICE}. 979 * @param methodName The method name to printed in the logs. If null, will be 980 * "connectProfile(profile=<profile>, device=<device>)" 981 */ 982 public void disconnectProfile(BluetoothAdapter adapter, BluetoothDevice device, int profile, 983 String methodName) { 984 if (methodName == null) { 985 methodName = String.format("disconnectProfile(profile=%d, device=%s)", profile, device); 986 } 987 int mask = (ConnectProfileReceiver.STATE_DISCONNECTING_FLAG 988 | ConnectProfileReceiver.STATE_DISCONNECTED_FLAG); 989 long start = -1; 990 991 if (!adapter.isEnabled()) { 992 fail(String.format("%s bluetooth not enabled", methodName)); 993 } 994 995 if (!adapter.getBondedDevices().contains(device)) { 996 fail(String.format("%s device not paired", methodName)); 997 } 998 999 BluetoothProfile proxy = connectProxy(adapter, profile); 1000 assertNotNull(proxy); 1001 1002 ConnectProfileReceiver receiver = getConnectProfileReceiver(device, profile, mask); 1003 1004 int state = proxy.getConnectionState(device); 1005 switch (state) { 1006 case BluetoothProfile.STATE_CONNECTED: 1007 case BluetoothProfile.STATE_CONNECTING: 1008 start = System.currentTimeMillis(); 1009 assertTrue(proxy.disconnect(device)); 1010 break; 1011 case BluetoothProfile.STATE_DISCONNECTED: 1012 removeReceiver(receiver); 1013 return; 1014 case BluetoothProfile.STATE_DISCONNECTING: 1015 mask = 0; // Don't check for received intents since we might have missed them. 1016 break; 1017 default: 1018 removeReceiver(receiver); 1019 fail(String.format("%s invalid state: state=%d", methodName, state)); 1020 } 1021 1022 long s = System.currentTimeMillis(); 1023 while (System.currentTimeMillis() - s < CONNECT_DISCONNECT_PROFILE_TIMEOUT) { 1024 state = proxy.getConnectionState(device); 1025 if (state == BluetoothProfile.STATE_DISCONNECTED 1026 && (receiver.getFiredFlags() & mask) == mask) { 1027 long finish = receiver.getCompletedTime(); 1028 if (start != -1 && finish != -1) { 1029 writeOutput(String.format("%s completed in %d ms", methodName, 1030 (finish - start))); 1031 } else { 1032 writeOutput(String.format("%s completed", methodName)); 1033 } 1034 removeReceiver(receiver); 1035 return; 1036 } 1037 sleep(POLL_TIME); 1038 } 1039 1040 int firedFlags = receiver.getFiredFlags(); 1041 removeReceiver(receiver); 1042 fail(String.format("%s timeout: state=%d (expected %d), flags=0x%x (expected 0x%x)", 1043 methodName, state, BluetoothProfile.STATE_DISCONNECTED, firedFlags, mask)); 1044 } 1045 1046 /** 1047 * Connects the PANU to a remote NAP and checks to make sure that the PANU is connected and that 1048 * the correct actions were broadcast. 1049 * 1050 * @param adapter The BT adapter. 1051 * @param device The remote device. 1052 */ 1053 public void connectPan(BluetoothAdapter adapter, BluetoothDevice device) { 1054 connectPanOrIncomingPanConnection(adapter, device, true); 1055 } 1056 1057 /** 1058 * Checks that a remote PANU connects to the local NAP correctly and that the correct actions 1059 * were broadcast. 1060 * 1061 * @param adapter The BT adapter. 1062 * @param device The remote device. 1063 */ 1064 public void incomingPanConnection(BluetoothAdapter adapter, BluetoothDevice device) { 1065 connectPanOrIncomingPanConnection(adapter, device, false); 1066 } 1067 1068 /** 1069 * Helper method used by {@link #connectPan(BluetoothAdapter, BluetoothDevice)} and 1070 * {@link #incomingPanConnection(BluetoothAdapter, BluetoothDevice)} to either connect to a 1071 * remote NAP or verify that a remote device connected to the local NAP. 1072 * 1073 * @param adapter The BT adapter. 1074 * @param device The remote device. 1075 * @param connect If the method should initiate the connection (is PANU) 1076 */ 1077 private void connectPanOrIncomingPanConnection(BluetoothAdapter adapter, BluetoothDevice device, 1078 boolean connect) { 1079 long start = -1; 1080 int mask, role; 1081 String methodName; 1082 1083 if (connect) { 1084 methodName = String.format("connectPan(device=%s)", device); 1085 mask = (ConnectProfileReceiver.STATE_CONNECTED_FLAG | 1086 ConnectProfileReceiver.STATE_CONNECTING_FLAG); 1087 role = BluetoothPan.LOCAL_PANU_ROLE; 1088 } else { 1089 methodName = String.format("incomingPanConnection(device=%s)", device); 1090 mask = ConnectProfileReceiver.STATE_CONNECTED_FLAG; 1091 role = BluetoothPan.LOCAL_NAP_ROLE; 1092 } 1093 1094 if (!adapter.isEnabled()) { 1095 fail(String.format("%s bluetooth not enabled", methodName)); 1096 } 1097 1098 if (!adapter.getBondedDevices().contains(device)) { 1099 fail(String.format("%s device not paired", methodName)); 1100 } 1101 1102 mPan = (BluetoothPan) connectProxy(adapter, BluetoothProfile.PAN); 1103 assertNotNull(mPan); 1104 ConnectPanReceiver receiver = getConnectPanReceiver(device, role, mask); 1105 1106 int state = mPan.getConnectionState(device); 1107 switch (state) { 1108 case BluetoothPan.STATE_CONNECTED: 1109 removeReceiver(receiver); 1110 return; 1111 case BluetoothPan.STATE_CONNECTING: 1112 mask = 0; // Don't check for received intents since we might have missed them. 1113 break; 1114 case BluetoothPan.STATE_DISCONNECTED: 1115 case BluetoothPan.STATE_DISCONNECTING: 1116 start = System.currentTimeMillis(); 1117 if (role == BluetoothPan.LOCAL_PANU_ROLE) { 1118 Log.i("BT", "connect to pan"); 1119 assertTrue(mPan.connect(device)); 1120 } 1121 break; 1122 default: 1123 removeReceiver(receiver); 1124 fail(String.format("%s invalid state: state=%d", methodName, state)); 1125 } 1126 1127 long s = System.currentTimeMillis(); 1128 while (System.currentTimeMillis() - s < CONNECT_DISCONNECT_PROFILE_TIMEOUT) { 1129 state = mPan.getConnectionState(device); 1130 if (state == BluetoothPan.STATE_CONNECTED 1131 && (receiver.getFiredFlags() & mask) == mask) { 1132 long finish = receiver.getCompletedTime(); 1133 if (start != -1 && finish != -1) { 1134 writeOutput(String.format("%s completed in %d ms", methodName, 1135 (finish - start))); 1136 } else { 1137 writeOutput(String.format("%s completed", methodName)); 1138 } 1139 removeReceiver(receiver); 1140 return; 1141 } 1142 sleep(POLL_TIME); 1143 } 1144 1145 int firedFlags = receiver.getFiredFlags(); 1146 removeReceiver(receiver); 1147 fail(String.format("%s timeout: state=%d (expected %d), flags=0x%x (expected 0x%s)", 1148 methodName, state, BluetoothPan.STATE_CONNECTED, firedFlags, mask)); 1149 } 1150 1151 /** 1152 * Disconnects the PANU from a remote NAP and checks to make sure that the PANU is disconnected 1153 * and that the correct actions were broadcast. 1154 * 1155 * @param adapter The BT adapter. 1156 * @param device The remote device. 1157 */ 1158 public void disconnectPan(BluetoothAdapter adapter, BluetoothDevice device) { 1159 disconnectFromRemoteOrVerifyConnectNap(adapter, device, true); 1160 } 1161 1162 /** 1163 * Checks that a remote PANU disconnects from the local NAP correctly and that the correct 1164 * actions were broadcast. 1165 * 1166 * @param adapter The BT adapter. 1167 * @param device The remote device. 1168 */ 1169 public void incomingPanDisconnection(BluetoothAdapter adapter, BluetoothDevice device) { 1170 disconnectFromRemoteOrVerifyConnectNap(adapter, device, false); 1171 } 1172 1173 /** 1174 * Helper method used by {@link #disconnectPan(BluetoothAdapter, BluetoothDevice)} and 1175 * {@link #incomingPanDisconnection(BluetoothAdapter, BluetoothDevice)} to either disconnect 1176 * from a remote NAP or verify that a remote device disconnected from the local NAP. 1177 * 1178 * @param adapter The BT adapter. 1179 * @param device The remote device. 1180 * @param disconnect Whether the method should connect or verify. 1181 */ 1182 private void disconnectFromRemoteOrVerifyConnectNap(BluetoothAdapter adapter, 1183 BluetoothDevice device, boolean disconnect) { 1184 long start = -1; 1185 int mask, role; 1186 String methodName; 1187 1188 if (disconnect) { 1189 methodName = String.format("disconnectPan(device=%s)", device); 1190 mask = (ConnectProfileReceiver.STATE_DISCONNECTED_FLAG | 1191 ConnectProfileReceiver.STATE_DISCONNECTING_FLAG); 1192 role = BluetoothPan.LOCAL_PANU_ROLE; 1193 } else { 1194 methodName = String.format("incomingPanDisconnection(device=%s)", device); 1195 mask = ConnectProfileReceiver.STATE_DISCONNECTED_FLAG; 1196 role = BluetoothPan.LOCAL_NAP_ROLE; 1197 } 1198 1199 if (!adapter.isEnabled()) { 1200 fail(String.format("%s bluetooth not enabled", methodName)); 1201 } 1202 1203 if (!adapter.getBondedDevices().contains(device)) { 1204 fail(String.format("%s device not paired", methodName)); 1205 } 1206 1207 mPan = (BluetoothPan) connectProxy(adapter, BluetoothProfile.PAN); 1208 assertNotNull(mPan); 1209 ConnectPanReceiver receiver = getConnectPanReceiver(device, role, mask); 1210 1211 int state = mPan.getConnectionState(device); 1212 switch (state) { 1213 case BluetoothPan.STATE_CONNECTED: 1214 case BluetoothPan.STATE_CONNECTING: 1215 start = System.currentTimeMillis(); 1216 if (role == BluetoothPan.LOCAL_PANU_ROLE) { 1217 assertTrue(mPan.disconnect(device)); 1218 } 1219 break; 1220 case BluetoothPan.STATE_DISCONNECTED: 1221 removeReceiver(receiver); 1222 return; 1223 case BluetoothPan.STATE_DISCONNECTING: 1224 mask = 0; // Don't check for received intents since we might have missed them. 1225 break; 1226 default: 1227 removeReceiver(receiver); 1228 fail(String.format("%s invalid state: state=%d", methodName, state)); 1229 } 1230 1231 long s = System.currentTimeMillis(); 1232 while (System.currentTimeMillis() - s < CONNECT_DISCONNECT_PROFILE_TIMEOUT) { 1233 state = mPan.getConnectionState(device); 1234 if (state == BluetoothInputDevice.STATE_DISCONNECTED 1235 && (receiver.getFiredFlags() & mask) == mask) { 1236 long finish = receiver.getCompletedTime(); 1237 if (start != -1 && finish != -1) { 1238 writeOutput(String.format("%s completed in %d ms", methodName, 1239 (finish - start))); 1240 } else { 1241 writeOutput(String.format("%s completed", methodName)); 1242 } 1243 removeReceiver(receiver); 1244 return; 1245 } 1246 sleep(POLL_TIME); 1247 } 1248 1249 int firedFlags = receiver.getFiredFlags(); 1250 removeReceiver(receiver); 1251 fail(String.format("%s timeout: state=%d (expected %d), flags=0x%x (expected 0x%s)", 1252 methodName, state, BluetoothInputDevice.STATE_DISCONNECTED, firedFlags, mask)); 1253 } 1254 1255 /** 1256 * Opens a SCO channel using {@link android.media.AudioManager#startBluetoothSco()} and checks 1257 * to make sure that the channel is opened and that the correct actions were broadcast. 1258 * 1259 * @param adapter The BT adapter. 1260 * @param device The remote device. 1261 */ 1262 public void startSco(BluetoothAdapter adapter, BluetoothDevice device) { 1263 startStopSco(adapter, device, true); 1264 } 1265 1266 /** 1267 * Closes a SCO channel using {@link android.media.AudioManager#stopBluetoothSco()} and checks 1268 * to make sure that the channel is closed and that the correct actions were broadcast. 1269 * 1270 * @param adapter The BT adapter. 1271 * @param device The remote device. 1272 */ 1273 public void stopSco(BluetoothAdapter adapter, BluetoothDevice device) { 1274 startStopSco(adapter, device, false); 1275 } 1276 /** 1277 * Helper method for {@link #startSco(BluetoothAdapter, BluetoothDevice)} and 1278 * {@link #stopSco(BluetoothAdapter, BluetoothDevice)}. 1279 * 1280 * @param adapter The BT adapter. 1281 * @param device The remote device. 1282 * @param isStart Whether the SCO channel should be opened. 1283 */ 1284 private void startStopSco(BluetoothAdapter adapter, BluetoothDevice device, boolean isStart) { 1285 long start = -1; 1286 int mask; 1287 String methodName; 1288 1289 if (isStart) { 1290 methodName = String.format("startSco(device=%s)", device); 1291 mask = StartStopScoReceiver.STATE_CONNECTED_FLAG; 1292 } else { 1293 methodName = String.format("stopSco(device=%s)", device); 1294 mask = StartStopScoReceiver.STATE_DISCONNECTED_FLAG; 1295 } 1296 1297 if (!adapter.isEnabled()) { 1298 fail(String.format("%s bluetooth not enabled", methodName)); 1299 } 1300 1301 if (!adapter.getBondedDevices().contains(device)) { 1302 fail(String.format("%s device not paired", methodName)); 1303 } 1304 1305 AudioManager manager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE); 1306 assertNotNull(manager); 1307 1308 if (!manager.isBluetoothScoAvailableOffCall()) { 1309 fail(String.format("%s device does not support SCO", methodName)); 1310 } 1311 1312 boolean isScoOn = manager.isBluetoothScoOn(); 1313 if (isStart == isScoOn) { 1314 return; 1315 } 1316 1317 StartStopScoReceiver receiver = getStartStopScoReceiver(mask); 1318 start = System.currentTimeMillis(); 1319 if (isStart) { 1320 manager.startBluetoothSco(); 1321 } else { 1322 manager.stopBluetoothSco(); 1323 } 1324 1325 long s = System.currentTimeMillis(); 1326 while (System.currentTimeMillis() - s < START_STOP_SCO_TIMEOUT) { 1327 isScoOn = manager.isBluetoothScoOn(); 1328 if (isStart == isScoOn && (receiver.getFiredFlags() & mask) == mask) { 1329 long finish = receiver.getCompletedTime(); 1330 if (start != -1 && finish != -1) { 1331 writeOutput(String.format("%s completed in %d ms", methodName, 1332 (finish - start))); 1333 } else { 1334 writeOutput(String.format("%s completed", methodName)); 1335 } 1336 removeReceiver(receiver); 1337 return; 1338 } 1339 sleep(POLL_TIME); 1340 } 1341 1342 int firedFlags = receiver.getFiredFlags(); 1343 removeReceiver(receiver); 1344 fail(String.format("%s timeout: on=%b (expected %b), flags=0x%x (expected 0x%x)", 1345 methodName, isScoOn, isStart, firedFlags, mask)); 1346 } 1347 1348 /** 1349 * Writes a string to the logcat and a file if a file has been specified in the constructor. 1350 * 1351 * @param s The string to be written. 1352 */ 1353 public void writeOutput(String s) { 1354 Log.i(mTag, s); 1355 if (mOutputWriter == null) { 1356 return; 1357 } 1358 try { 1359 mOutputWriter.write(s + "\n"); 1360 mOutputWriter.flush(); 1361 } catch (IOException e) { 1362 Log.w(mTag, "Could not write to output file", e); 1363 } 1364 } 1365 1366 private void addReceiver(BroadcastReceiver receiver, String[] actions) { 1367 IntentFilter filter = new IntentFilter(); 1368 for (String action: actions) { 1369 filter.addAction(action); 1370 } 1371 mContext.registerReceiver(receiver, filter); 1372 mReceivers.add(receiver); 1373 } 1374 1375 private BluetoothReceiver getBluetoothReceiver(int expectedFlags) { 1376 String[] actions = { 1377 BluetoothAdapter.ACTION_DISCOVERY_FINISHED, 1378 BluetoothAdapter.ACTION_DISCOVERY_STARTED, 1379 BluetoothAdapter.ACTION_SCAN_MODE_CHANGED, 1380 BluetoothAdapter.ACTION_STATE_CHANGED}; 1381 BluetoothReceiver receiver = new BluetoothReceiver(expectedFlags); 1382 addReceiver(receiver, actions); 1383 return receiver; 1384 } 1385 1386 private PairReceiver getPairReceiver(BluetoothDevice device, int passkey, byte[] pin, 1387 int expectedFlags) { 1388 String[] actions = { 1389 BluetoothDevice.ACTION_PAIRING_REQUEST, 1390 BluetoothDevice.ACTION_BOND_STATE_CHANGED}; 1391 PairReceiver receiver = new PairReceiver(device, passkey, pin, expectedFlags); 1392 addReceiver(receiver, actions); 1393 return receiver; 1394 } 1395 1396 private ConnectProfileReceiver getConnectProfileReceiver(BluetoothDevice device, int profile, 1397 int expectedFlags) { 1398 String[] actions = { 1399 BluetoothA2dp.ACTION_CONNECTION_STATE_CHANGED, 1400 BluetoothHeadset.ACTION_CONNECTION_STATE_CHANGED, 1401 BluetoothInputDevice.ACTION_CONNECTION_STATE_CHANGED}; 1402 ConnectProfileReceiver receiver = new ConnectProfileReceiver(device, profile, 1403 expectedFlags); 1404 addReceiver(receiver, actions); 1405 return receiver; 1406 } 1407 1408 private ConnectPanReceiver getConnectPanReceiver(BluetoothDevice device, int role, 1409 int expectedFlags) { 1410 String[] actions = {BluetoothPan.ACTION_CONNECTION_STATE_CHANGED}; 1411 ConnectPanReceiver receiver = new ConnectPanReceiver(device, role, expectedFlags); 1412 addReceiver(receiver, actions); 1413 return receiver; 1414 } 1415 1416 private StartStopScoReceiver getStartStopScoReceiver(int expectedFlags) { 1417 String[] actions = {AudioManager.ACTION_SCO_AUDIO_STATE_CHANGED}; 1418 StartStopScoReceiver receiver = new StartStopScoReceiver(expectedFlags); 1419 addReceiver(receiver, actions); 1420 return receiver; 1421 } 1422 1423 private void removeReceiver(BroadcastReceiver receiver) { 1424 mContext.unregisterReceiver(receiver); 1425 mReceivers.remove(receiver); 1426 } 1427 1428 private BluetoothProfile connectProxy(BluetoothAdapter adapter, int profile) { 1429 switch (profile) { 1430 case BluetoothProfile.A2DP: 1431 if (mA2dp != null) { 1432 return mA2dp; 1433 } 1434 break; 1435 case BluetoothProfile.HEADSET: 1436 if (mHeadset != null) { 1437 return mHeadset; 1438 } 1439 break; 1440 case BluetoothProfile.INPUT_DEVICE: 1441 if (mInput != null) { 1442 return mInput; 1443 } 1444 break; 1445 case BluetoothProfile.PAN: 1446 if (mPan != null) { 1447 return mPan; 1448 } 1449 break; 1450 default: 1451 return null; 1452 } 1453 adapter.getProfileProxy(mContext, mServiceListener, profile); 1454 long s = System.currentTimeMillis(); 1455 switch (profile) { 1456 case BluetoothProfile.A2DP: 1457 while (mA2dp == null && System.currentTimeMillis() - s < CONNECT_PROXY_TIMEOUT) { 1458 sleep(POLL_TIME); 1459 } 1460 return mA2dp; 1461 case BluetoothProfile.HEADSET: 1462 while (mHeadset == null && System.currentTimeMillis() - s < CONNECT_PROXY_TIMEOUT) { 1463 sleep(POLL_TIME); 1464 } 1465 return mHeadset; 1466 case BluetoothProfile.INPUT_DEVICE: 1467 while (mInput == null && System.currentTimeMillis() - s < CONNECT_PROXY_TIMEOUT) { 1468 sleep(POLL_TIME); 1469 } 1470 return mInput; 1471 case BluetoothProfile.PAN: 1472 while (mPan == null && System.currentTimeMillis() - s < CONNECT_PROXY_TIMEOUT) { 1473 sleep(POLL_TIME); 1474 } 1475 return mPan; 1476 default: 1477 return null; 1478 } 1479 } 1480 1481 private void sleep(long time) { 1482 try { 1483 Thread.sleep(time); 1484 } catch (InterruptedException e) { 1485 } 1486 } 1487} 1488