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