BluetoothTestUtils.java revision 7af75afb2c8608dbe73509036eefd3281d646a5f
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_UPDATED.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 assertTrue(adapter.cancelDiscovery()); 682 683 while (System.currentTimeMillis() - start < START_STOP_SCAN_TIMEOUT) { 684 if (!adapter.isDiscovering() && ((receiver.getFiredFlags() & mask) == mask)) { 685 writeOutput(String.format("stopScan() completed in %d ms", 686 (receiver.getCompletedTime() - start))); 687 removeReceiver(receiver); 688 return; 689 } 690 sleep(POLL_TIME); 691 } 692 693 int firedFlags = receiver.getFiredFlags(); 694 removeReceiver(receiver); 695 fail(String.format("stopScan() timeout: isDiscovering=%b, flags=0x%x (expected 0x%x)", 696 adapter.isDiscovering(), firedFlags, mask)); 697 698 } 699 700 /** 701 * Enables PAN tethering on the local device and checks to make sure that tethering is enabled. 702 * 703 * @param adapter The BT adapter. 704 */ 705 public void enablePan(BluetoothAdapter adapter) { 706 if (mPan == null) mPan = (BluetoothPan) connectProxy(adapter, BluetoothProfile.PAN); 707 assertNotNull(mPan); 708 709 long start = System.currentTimeMillis(); 710 mPan.setBluetoothTethering(true); 711 long stop = System.currentTimeMillis(); 712 assertTrue(mPan.isTetheringOn()); 713 714 writeOutput(String.format("enablePan() completed in %d ms", (stop - start))); 715 } 716 717 /** 718 * Disables PAN tethering on the local device and checks to make sure that tethering is 719 * disabled. 720 * 721 * @param adapter The BT adapter. 722 */ 723 public void disablePan(BluetoothAdapter adapter) { 724 if (mPan == null) mPan = (BluetoothPan) connectProxy(adapter, BluetoothProfile.PAN); 725 assertNotNull(mPan); 726 727 long start = System.currentTimeMillis(); 728 mPan.setBluetoothTethering(false); 729 long stop = System.currentTimeMillis(); 730 assertFalse(mPan.isTetheringOn()); 731 732 writeOutput(String.format("disablePan() completed in %d ms", (stop - start))); 733 } 734 735 /** 736 * Initiates a pairing with a remote device and checks to make sure that the devices are paired 737 * and that the correct actions were broadcast. 738 * 739 * @param adapter The BT adapter. 740 * @param device The remote device. 741 * @param passkey The pairing passkey if pairing requires a passkey. Any value if not. 742 * @param pin The pairing pin if pairing requires a pin. Any value if not. 743 */ 744 public void pair(BluetoothAdapter adapter, BluetoothDevice device, int passkey, byte[] pin) { 745 pairOrAcceptPair(adapter, device, passkey, pin, true); 746 } 747 748 /** 749 * Accepts a pairing with a remote device and checks to make sure that the devices are paired 750 * and that the correct actions were broadcast. 751 * 752 * @param adapter The BT adapter. 753 * @param device The remote device. 754 * @param passkey The pairing passkey if pairing requires a passkey. Any value if not. 755 * @param pin The pairing pin if pairing requires a pin. Any value if not. 756 */ 757 public void acceptPair(BluetoothAdapter adapter, BluetoothDevice device, int passkey, 758 byte[] pin) { 759 pairOrAcceptPair(adapter, device, passkey, pin, false); 760 } 761 762 /** 763 * Helper method used by {@link #pair(BluetoothAdapter, BluetoothDevice, int, byte[])} and 764 * {@link #acceptPair(BluetoothAdapter, BluetoothDevice, int, byte[])} to either pair or accept 765 * a pairing request. 766 * 767 * @param adapter The BT adapter. 768 * @param device The remote device. 769 * @param passkey The pairing passkey if pairing requires a passkey. Any value if not. 770 * @param pin The pairing pin if pairing requires a pin. Any value if not. 771 * @param shouldPair Whether to pair or accept the pair. 772 */ 773 private void pairOrAcceptPair(BluetoothAdapter adapter, BluetoothDevice device, int passkey, 774 byte[] pin, boolean shouldPair) { 775 int mask = PairReceiver.STATE_BONDING_FLAG | PairReceiver.STATE_BONDED_FLAG; 776 long start = -1; 777 String methodName; 778 if (shouldPair) { 779 methodName = String.format("pair(device=%s)", device); 780 } else { 781 methodName = String.format("acceptPair(device=%s)", device); 782 } 783 784 if (!adapter.isEnabled()) { 785 fail(String.format("%s bluetooth not enabled", methodName)); 786 } 787 788 PairReceiver receiver = getPairReceiver(device, passkey, pin, mask); 789 790 int state = device.getBondState(); 791 switch (state) { 792 case BluetoothDevice.BOND_NONE: 793 assertFalse(adapter.getBondedDevices().contains(device)); 794 start = System.currentTimeMillis(); 795 if (shouldPair) { 796 assertTrue(device.createBond()); 797 } 798 break; 799 case BluetoothDevice.BOND_BONDING: 800 mask = 0; // Don't check for received intents since we might have missed them. 801 break; 802 case BluetoothDevice.BOND_BONDED: 803 assertTrue(adapter.getBondedDevices().contains(device)); 804 return; 805 default: 806 removeReceiver(receiver); 807 fail(String.format("%s invalid state: state=%d", methodName, state)); 808 } 809 810 long s = System.currentTimeMillis(); 811 while (System.currentTimeMillis() - s < PAIR_UNPAIR_TIMEOUT) { 812 state = device.getBondState(); 813 if (state == BluetoothDevice.BOND_BONDED && (receiver.getFiredFlags() & mask) == mask) { 814 assertTrue(adapter.getBondedDevices().contains(device)); 815 long finish = receiver.getCompletedTime(); 816 if (start != -1 && finish != -1) { 817 writeOutput(String.format("%s completed in %d ms", methodName, 818 (finish - start))); 819 } else { 820 writeOutput(String.format("%s completed", methodName)); 821 } 822 removeReceiver(receiver); 823 return; 824 } 825 sleep(POLL_TIME); 826 } 827 828 int firedFlags = receiver.getFiredFlags(); 829 removeReceiver(receiver); 830 fail(String.format("%s timeout: state=%d (expected %d), flags=0x%x (expected 0x%x)", 831 methodName, state, BluetoothDevice.BOND_BONDED, firedFlags, mask)); 832 } 833 834 /** 835 * Deletes a pairing with a remote device and checks to make sure that the devices are unpaired 836 * and that the correct actions were broadcast. 837 * 838 * @param adapter The BT adapter. 839 * @param device The remote device. 840 */ 841 public void unpair(BluetoothAdapter adapter, BluetoothDevice device) { 842 int mask = PairReceiver.STATE_NONE_FLAG; 843 long start = -1; 844 String methodName = String.format("unpair(device=%s)", device); 845 846 if (!adapter.isEnabled()) { 847 fail(String.format("%s bluetooth not enabled", methodName)); 848 } 849 850 PairReceiver receiver = getPairReceiver(device, 0, null, mask); 851 852 int state = device.getBondState(); 853 switch (state) { 854 case BluetoothDevice.BOND_NONE: 855 assertFalse(adapter.getBondedDevices().contains(device)); 856 removeReceiver(receiver); 857 return; 858 case BluetoothDevice.BOND_BONDING: 859 start = System.currentTimeMillis(); 860 assertTrue(device.removeBond()); 861 break; 862 case BluetoothDevice.BOND_BONDED: 863 assertTrue(adapter.getBondedDevices().contains(device)); 864 start = System.currentTimeMillis(); 865 assertTrue(device.removeBond()); 866 break; 867 default: 868 removeReceiver(receiver); 869 fail(String.format("%s invalid state: state=%d", methodName, state)); 870 } 871 872 long s = System.currentTimeMillis(); 873 while (System.currentTimeMillis() - s < PAIR_UNPAIR_TIMEOUT) { 874 if (device.getBondState() == BluetoothDevice.BOND_NONE 875 && (receiver.getFiredFlags() & mask) == mask) { 876 assertFalse(adapter.getBondedDevices().contains(device)); 877 long finish = receiver.getCompletedTime(); 878 if (start != -1 && finish != -1) { 879 writeOutput(String.format("%s completed in %d ms", methodName, 880 (finish - start))); 881 } else { 882 writeOutput(String.format("%s completed", methodName)); 883 } 884 removeReceiver(receiver); 885 return; 886 } 887 } 888 889 int firedFlags = receiver.getFiredFlags(); 890 removeReceiver(receiver); 891 fail(String.format("%s timeout: state=%d (expected %d), flags=0x%x (expected 0x%x)", 892 methodName, state, BluetoothDevice.BOND_BONDED, firedFlags, mask)); 893 } 894 895 /** 896 * Connects a profile from the local device to a remote device and checks to make sure that the 897 * profile is connected and that the correct actions were broadcast. 898 * 899 * @param adapter The BT adapter. 900 * @param device The remote device. 901 * @param profile The profile to connect. One of {@link BluetoothProfile#A2DP}, 902 * {@link BluetoothProfile#HEADSET}, or {@link BluetoothProfile#INPUT_DEVICE}. 903 * @param methodName The method name to printed in the logs. If null, will be 904 * "connectProfile(profile=<profile>, device=<device>)" 905 */ 906 public void connectProfile(BluetoothAdapter adapter, BluetoothDevice device, int profile, 907 String methodName) { 908 if (methodName == null) { 909 methodName = String.format("connectProfile(profile=%d, device=%s)", profile, device); 910 } 911 int mask = (ConnectProfileReceiver.STATE_CONNECTING_FLAG 912 | ConnectProfileReceiver.STATE_CONNECTED_FLAG); 913 long start = -1; 914 915 if (!adapter.isEnabled()) { 916 fail(String.format("%s bluetooth not enabled", methodName)); 917 } 918 919 if (!adapter.getBondedDevices().contains(device)) { 920 fail(String.format("%s device not paired", methodName)); 921 } 922 923 BluetoothProfile proxy = connectProxy(adapter, profile); 924 assertNotNull(proxy); 925 926 ConnectProfileReceiver receiver = getConnectProfileReceiver(device, profile, mask); 927 928 int state = proxy.getConnectionState(device); 929 switch (state) { 930 case BluetoothProfile.STATE_CONNECTED: 931 removeReceiver(receiver); 932 return; 933 case BluetoothProfile.STATE_CONNECTING: 934 mask = 0; // Don't check for received intents since we might have missed them. 935 break; 936 case BluetoothProfile.STATE_DISCONNECTED: 937 case BluetoothProfile.STATE_DISCONNECTING: 938 start = System.currentTimeMillis(); 939 assertTrue(proxy.connect(device)); 940 break; 941 default: 942 removeReceiver(receiver); 943 fail(String.format("%s invalid state: state=%d", methodName, state)); 944 } 945 946 long s = System.currentTimeMillis(); 947 while (System.currentTimeMillis() - s < CONNECT_DISCONNECT_PROFILE_TIMEOUT) { 948 state = proxy.getConnectionState(device); 949 if (state == BluetoothProfile.STATE_CONNECTED 950 && (receiver.getFiredFlags() & mask) == mask) { 951 long finish = receiver.getCompletedTime(); 952 if (start != -1 && finish != -1) { 953 writeOutput(String.format("%s completed in %d ms", methodName, 954 (finish - start))); 955 } else { 956 writeOutput(String.format("%s completed", methodName)); 957 } 958 removeReceiver(receiver); 959 return; 960 } 961 sleep(POLL_TIME); 962 } 963 964 int firedFlags = receiver.getFiredFlags(); 965 removeReceiver(receiver); 966 fail(String.format("%s timeout: state=%d (expected %d), flags=0x%x (expected 0x%x)", 967 methodName, state, BluetoothProfile.STATE_CONNECTED, firedFlags, mask)); 968 } 969 970 /** 971 * Disconnects a profile between the local device and a remote device and checks to make sure 972 * that the profile is disconnected and that the correct actions were broadcast. 973 * 974 * @param adapter The BT adapter. 975 * @param device The remote device. 976 * @param profile The profile to disconnect. One of {@link BluetoothProfile#A2DP}, 977 * {@link BluetoothProfile#HEADSET}, or {@link BluetoothProfile#INPUT_DEVICE}. 978 * @param methodName The method name to printed in the logs. If null, will be 979 * "connectProfile(profile=<profile>, device=<device>)" 980 */ 981 public void disconnectProfile(BluetoothAdapter adapter, BluetoothDevice device, int profile, 982 String methodName) { 983 if (methodName == null) { 984 methodName = String.format("disconnectProfile(profile=%d, device=%s)", profile, device); 985 } 986 int mask = (ConnectProfileReceiver.STATE_DISCONNECTING_FLAG 987 | ConnectProfileReceiver.STATE_DISCONNECTED_FLAG); 988 long start = -1; 989 990 if (!adapter.isEnabled()) { 991 fail(String.format("%s bluetooth not enabled", methodName)); 992 } 993 994 if (!adapter.getBondedDevices().contains(device)) { 995 fail(String.format("%s device not paired", methodName)); 996 } 997 998 BluetoothProfile proxy = connectProxy(adapter, profile); 999 assertNotNull(proxy); 1000 1001 ConnectProfileReceiver receiver = getConnectProfileReceiver(device, profile, mask); 1002 1003 int state = proxy.getConnectionState(device); 1004 switch (state) { 1005 case BluetoothProfile.STATE_CONNECTED: 1006 case BluetoothProfile.STATE_CONNECTING: 1007 start = System.currentTimeMillis(); 1008 assertTrue(proxy.disconnect(device)); 1009 break; 1010 case BluetoothProfile.STATE_DISCONNECTED: 1011 removeReceiver(receiver); 1012 return; 1013 case BluetoothProfile.STATE_DISCONNECTING: 1014 mask = 0; // Don't check for received intents since we might have missed them. 1015 break; 1016 default: 1017 removeReceiver(receiver); 1018 fail(String.format("%s invalid state: state=%d", methodName, state)); 1019 } 1020 1021 long s = System.currentTimeMillis(); 1022 while (System.currentTimeMillis() - s < CONNECT_DISCONNECT_PROFILE_TIMEOUT) { 1023 state = proxy.getConnectionState(device); 1024 if (state == BluetoothProfile.STATE_DISCONNECTED 1025 && (receiver.getFiredFlags() & mask) == mask) { 1026 long finish = receiver.getCompletedTime(); 1027 if (start != -1 && finish != -1) { 1028 writeOutput(String.format("%s completed in %d ms", methodName, 1029 (finish - start))); 1030 } else { 1031 writeOutput(String.format("%s completed", methodName)); 1032 } 1033 removeReceiver(receiver); 1034 return; 1035 } 1036 sleep(POLL_TIME); 1037 } 1038 1039 int firedFlags = receiver.getFiredFlags(); 1040 removeReceiver(receiver); 1041 fail(String.format("%s timeout: state=%d (expected %d), flags=0x%x (expected 0x%x)", 1042 methodName, state, BluetoothProfile.STATE_DISCONNECTED, firedFlags, mask)); 1043 } 1044 1045 /** 1046 * Connects the PANU to a remote NAP and checks to make sure that the PANU is connected and that 1047 * the correct actions were broadcast. 1048 * 1049 * @param adapter The BT adapter. 1050 * @param device The remote device. 1051 */ 1052 public void connectPan(BluetoothAdapter adapter, BluetoothDevice device) { 1053 connectPanOrIncomingPanConnection(adapter, device, true); 1054 } 1055 1056 /** 1057 * Checks that a remote PANU connects to the local NAP correctly and that the correct actions 1058 * were broadcast. 1059 * 1060 * @param adapter The BT adapter. 1061 * @param device The remote device. 1062 */ 1063 public void incomingPanConnection(BluetoothAdapter adapter, BluetoothDevice device) { 1064 connectPanOrIncomingPanConnection(adapter, device, false); 1065 } 1066 1067 /** 1068 * Helper method used by {@link #connectPan(BluetoothAdapter, BluetoothDevice)} and 1069 * {@link #incomingPanConnection(BluetoothAdapter, BluetoothDevice)} to either connect to a 1070 * remote NAP or verify that a remote device connected to the local NAP. 1071 * 1072 * @param adapter The BT adapter. 1073 * @param device The remote device. 1074 * @param connect If the method should initiate the connection (is PANU) 1075 */ 1076 private void connectPanOrIncomingPanConnection(BluetoothAdapter adapter, BluetoothDevice device, 1077 boolean connect) { 1078 long start = -1; 1079 int mask, role; 1080 String methodName; 1081 1082 if (connect) { 1083 methodName = String.format("connectPan(device=%s)", device); 1084 mask = (ConnectProfileReceiver.STATE_CONNECTED_FLAG | 1085 ConnectProfileReceiver.STATE_CONNECTING_FLAG); 1086 role = BluetoothPan.LOCAL_PANU_ROLE; 1087 } else { 1088 methodName = String.format("incomingPanConnection(device=%s)", device); 1089 mask = ConnectProfileReceiver.STATE_CONNECTED_FLAG; 1090 role = BluetoothPan.LOCAL_NAP_ROLE; 1091 } 1092 1093 if (!adapter.isEnabled()) { 1094 fail(String.format("%s bluetooth not enabled", methodName)); 1095 } 1096 1097 if (!adapter.getBondedDevices().contains(device)) { 1098 fail(String.format("%s device not paired", methodName)); 1099 } 1100 1101 mPan = (BluetoothPan) connectProxy(adapter, BluetoothProfile.PAN); 1102 assertNotNull(mPan); 1103 ConnectPanReceiver receiver = getConnectPanReceiver(device, role, mask); 1104 1105 int state = mPan.getConnectionState(device); 1106 switch (state) { 1107 case BluetoothPan.STATE_CONNECTED: 1108 removeReceiver(receiver); 1109 return; 1110 case BluetoothPan.STATE_CONNECTING: 1111 mask = 0; // Don't check for received intents since we might have missed them. 1112 break; 1113 case BluetoothPan.STATE_DISCONNECTED: 1114 case BluetoothPan.STATE_DISCONNECTING: 1115 start = System.currentTimeMillis(); 1116 if (role == BluetoothPan.LOCAL_PANU_ROLE) { 1117 Log.i("BT", "connect to pan"); 1118 assertTrue(mPan.connect(device)); 1119 } 1120 break; 1121 default: 1122 removeReceiver(receiver); 1123 fail(String.format("%s invalid state: state=%d", methodName, state)); 1124 } 1125 1126 long s = System.currentTimeMillis(); 1127 while (System.currentTimeMillis() - s < CONNECT_DISCONNECT_PROFILE_TIMEOUT) { 1128 state = mPan.getConnectionState(device); 1129 if (state == BluetoothPan.STATE_CONNECTED 1130 && (receiver.getFiredFlags() & mask) == mask) { 1131 long finish = receiver.getCompletedTime(); 1132 if (start != -1 && finish != -1) { 1133 writeOutput(String.format("%s completed in %d ms", methodName, 1134 (finish - start))); 1135 } else { 1136 writeOutput(String.format("%s completed", methodName)); 1137 } 1138 removeReceiver(receiver); 1139 return; 1140 } 1141 sleep(POLL_TIME); 1142 } 1143 1144 int firedFlags = receiver.getFiredFlags(); 1145 removeReceiver(receiver); 1146 fail(String.format("%s timeout: state=%d (expected %d), flags=0x%x (expected 0x%s)", 1147 methodName, state, BluetoothPan.STATE_CONNECTED, firedFlags, mask)); 1148 } 1149 1150 /** 1151 * Disconnects the PANU from a remote NAP and checks to make sure that the PANU is disconnected 1152 * and that the correct actions were broadcast. 1153 * 1154 * @param adapter The BT adapter. 1155 * @param device The remote device. 1156 */ 1157 public void disconnectPan(BluetoothAdapter adapter, BluetoothDevice device) { 1158 disconnectFromRemoteOrVerifyConnectNap(adapter, device, true); 1159 } 1160 1161 /** 1162 * Checks that a remote PANU disconnects from the local NAP correctly and that the correct 1163 * actions were broadcast. 1164 * 1165 * @param adapter The BT adapter. 1166 * @param device The remote device. 1167 */ 1168 public void incomingPanDisconnection(BluetoothAdapter adapter, BluetoothDevice device) { 1169 disconnectFromRemoteOrVerifyConnectNap(adapter, device, false); 1170 } 1171 1172 /** 1173 * Helper method used by {@link #disconnectPan(BluetoothAdapter, BluetoothDevice)} and 1174 * {@link #incomingPanDisconnection(BluetoothAdapter, BluetoothDevice)} to either disconnect 1175 * from a remote NAP or verify that a remote device disconnected from the local NAP. 1176 * 1177 * @param adapter The BT adapter. 1178 * @param device The remote device. 1179 * @param disconnect Whether the method should connect or verify. 1180 */ 1181 private void disconnectFromRemoteOrVerifyConnectNap(BluetoothAdapter adapter, 1182 BluetoothDevice device, boolean disconnect) { 1183 long start = -1; 1184 int mask, role; 1185 String methodName; 1186 1187 if (disconnect) { 1188 methodName = String.format("disconnectPan(device=%s)", device); 1189 mask = (ConnectProfileReceiver.STATE_DISCONNECTED_FLAG | 1190 ConnectProfileReceiver.STATE_DISCONNECTING_FLAG); 1191 role = BluetoothPan.LOCAL_PANU_ROLE; 1192 } else { 1193 methodName = String.format("incomingPanDisconnection(device=%s)", device); 1194 mask = ConnectProfileReceiver.STATE_DISCONNECTED_FLAG; 1195 role = BluetoothPan.LOCAL_NAP_ROLE; 1196 } 1197 1198 if (!adapter.isEnabled()) { 1199 fail(String.format("%s bluetooth not enabled", methodName)); 1200 } 1201 1202 if (!adapter.getBondedDevices().contains(device)) { 1203 fail(String.format("%s device not paired", methodName)); 1204 } 1205 1206 mPan = (BluetoothPan) connectProxy(adapter, BluetoothProfile.PAN); 1207 assertNotNull(mPan); 1208 ConnectPanReceiver receiver = getConnectPanReceiver(device, role, mask); 1209 1210 int state = mPan.getConnectionState(device); 1211 switch (state) { 1212 case BluetoothPan.STATE_CONNECTED: 1213 case BluetoothPan.STATE_CONNECTING: 1214 start = System.currentTimeMillis(); 1215 if (role == BluetoothPan.LOCAL_PANU_ROLE) { 1216 assertTrue(mPan.disconnect(device)); 1217 } 1218 break; 1219 case BluetoothPan.STATE_DISCONNECTED: 1220 removeReceiver(receiver); 1221 return; 1222 case BluetoothPan.STATE_DISCONNECTING: 1223 mask = 0; // Don't check for received intents since we might have missed them. 1224 break; 1225 default: 1226 removeReceiver(receiver); 1227 fail(String.format("%s invalid state: state=%d", methodName, state)); 1228 } 1229 1230 long s = System.currentTimeMillis(); 1231 while (System.currentTimeMillis() - s < CONNECT_DISCONNECT_PROFILE_TIMEOUT) { 1232 state = mPan.getConnectionState(device); 1233 if (state == BluetoothInputDevice.STATE_DISCONNECTED 1234 && (receiver.getFiredFlags() & mask) == mask) { 1235 long finish = receiver.getCompletedTime(); 1236 if (start != -1 && finish != -1) { 1237 writeOutput(String.format("%s completed in %d ms", methodName, 1238 (finish - start))); 1239 } else { 1240 writeOutput(String.format("%s completed", methodName)); 1241 } 1242 removeReceiver(receiver); 1243 return; 1244 } 1245 sleep(POLL_TIME); 1246 } 1247 1248 int firedFlags = receiver.getFiredFlags(); 1249 removeReceiver(receiver); 1250 fail(String.format("%s timeout: state=%d (expected %d), flags=0x%x (expected 0x%s)", 1251 methodName, state, BluetoothInputDevice.STATE_DISCONNECTED, firedFlags, mask)); 1252 } 1253 1254 /** 1255 * Opens a SCO channel using {@link android.media.AudioManager#startBluetoothSco()} and checks 1256 * to make sure that the channel is opened and that the correct actions were broadcast. 1257 * 1258 * @param adapter The BT adapter. 1259 * @param device The remote device. 1260 */ 1261 public void startSco(BluetoothAdapter adapter, BluetoothDevice device) { 1262 startStopSco(adapter, device, true); 1263 } 1264 1265 /** 1266 * Closes a SCO channel using {@link android.media.AudioManager#stopBluetoothSco()} and checks 1267 * to make sure that the channel is closed and that the correct actions were broadcast. 1268 * 1269 * @param adapter The BT adapter. 1270 * @param device The remote device. 1271 */ 1272 public void stopSco(BluetoothAdapter adapter, BluetoothDevice device) { 1273 startStopSco(adapter, device, false); 1274 } 1275 /** 1276 * Helper method for {@link #startSco(BluetoothAdapter, BluetoothDevice)} and 1277 * {@link #stopSco(BluetoothAdapter, BluetoothDevice)}. 1278 * 1279 * @param adapter The BT adapter. 1280 * @param device The remote device. 1281 * @param isStart Whether the SCO channel should be opened. 1282 */ 1283 private void startStopSco(BluetoothAdapter adapter, BluetoothDevice device, boolean isStart) { 1284 long start = -1; 1285 int mask; 1286 String methodName; 1287 1288 if (isStart) { 1289 methodName = String.format("startSco(device=%s)", device); 1290 mask = StartStopScoReceiver.STATE_CONNECTED_FLAG; 1291 } else { 1292 methodName = String.format("stopSco(device=%s)", device); 1293 mask = StartStopScoReceiver.STATE_DISCONNECTED_FLAG; 1294 } 1295 1296 if (!adapter.isEnabled()) { 1297 fail(String.format("%s bluetooth not enabled", methodName)); 1298 } 1299 1300 if (!adapter.getBondedDevices().contains(device)) { 1301 fail(String.format("%s device not paired", methodName)); 1302 } 1303 1304 AudioManager manager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE); 1305 assertNotNull(manager); 1306 1307 if (!manager.isBluetoothScoAvailableOffCall()) { 1308 fail(String.format("%s device does not support SCO", methodName)); 1309 } 1310 1311 boolean isScoOn = manager.isBluetoothScoOn(); 1312 if (isStart == isScoOn) { 1313 return; 1314 } 1315 1316 StartStopScoReceiver receiver = getStartStopScoReceiver(mask); 1317 start = System.currentTimeMillis(); 1318 if (isStart) { 1319 manager.startBluetoothSco(); 1320 } else { 1321 manager.stopBluetoothSco(); 1322 } 1323 1324 long s = System.currentTimeMillis(); 1325 while (System.currentTimeMillis() - s < START_STOP_SCO_TIMEOUT) { 1326 isScoOn = manager.isBluetoothScoOn(); 1327 if (isStart == isScoOn && (receiver.getFiredFlags() & mask) == mask) { 1328 long finish = receiver.getCompletedTime(); 1329 if (start != -1 && finish != -1) { 1330 writeOutput(String.format("%s completed in %d ms", methodName, 1331 (finish - start))); 1332 } else { 1333 writeOutput(String.format("%s completed", methodName)); 1334 } 1335 removeReceiver(receiver); 1336 return; 1337 } 1338 sleep(POLL_TIME); 1339 } 1340 1341 int firedFlags = receiver.getFiredFlags(); 1342 removeReceiver(receiver); 1343 fail(String.format("%s timeout: on=%b (expected %b), flags=0x%x (expected 0x%x)", 1344 methodName, isScoOn, isStart, firedFlags, mask)); 1345 } 1346 1347 /** 1348 * Writes a string to the logcat and a file if a file has been specified in the constructor. 1349 * 1350 * @param s The string to be written. 1351 */ 1352 public void writeOutput(String s) { 1353 Log.i(mTag, s); 1354 if (mOutputWriter == null) { 1355 return; 1356 } 1357 try { 1358 mOutputWriter.write(s + "\n"); 1359 mOutputWriter.flush(); 1360 } catch (IOException e) { 1361 Log.w(mTag, "Could not write to output file", e); 1362 } 1363 } 1364 1365 private void addReceiver(BroadcastReceiver receiver, String[] actions) { 1366 IntentFilter filter = new IntentFilter(); 1367 for (String action: actions) { 1368 filter.addAction(action); 1369 } 1370 mContext.registerReceiver(receiver, filter); 1371 mReceivers.add(receiver); 1372 } 1373 1374 private BluetoothReceiver getBluetoothReceiver(int expectedFlags) { 1375 String[] actions = { 1376 BluetoothAdapter.ACTION_DISCOVERY_FINISHED, 1377 BluetoothAdapter.ACTION_DISCOVERY_STARTED, 1378 BluetoothAdapter.ACTION_SCAN_MODE_CHANGED, 1379 BluetoothAdapter.ACTION_STATE_CHANGED}; 1380 BluetoothReceiver receiver = new BluetoothReceiver(expectedFlags); 1381 addReceiver(receiver, actions); 1382 return receiver; 1383 } 1384 1385 private PairReceiver getPairReceiver(BluetoothDevice device, int passkey, byte[] pin, 1386 int expectedFlags) { 1387 String[] actions = { 1388 BluetoothDevice.ACTION_PAIRING_REQUEST, 1389 BluetoothDevice.ACTION_BOND_STATE_CHANGED}; 1390 PairReceiver receiver = new PairReceiver(device, passkey, pin, expectedFlags); 1391 addReceiver(receiver, actions); 1392 return receiver; 1393 } 1394 1395 private ConnectProfileReceiver getConnectProfileReceiver(BluetoothDevice device, int profile, 1396 int expectedFlags) { 1397 String[] actions = { 1398 BluetoothA2dp.ACTION_CONNECTION_STATE_CHANGED, 1399 BluetoothHeadset.ACTION_CONNECTION_STATE_CHANGED, 1400 BluetoothInputDevice.ACTION_CONNECTION_STATE_CHANGED}; 1401 ConnectProfileReceiver receiver = new ConnectProfileReceiver(device, profile, 1402 expectedFlags); 1403 addReceiver(receiver, actions); 1404 return receiver; 1405 } 1406 1407 private ConnectPanReceiver getConnectPanReceiver(BluetoothDevice device, int role, 1408 int expectedFlags) { 1409 String[] actions = {BluetoothPan.ACTION_CONNECTION_STATE_CHANGED}; 1410 ConnectPanReceiver receiver = new ConnectPanReceiver(device, role, expectedFlags); 1411 addReceiver(receiver, actions); 1412 return receiver; 1413 } 1414 1415 private StartStopScoReceiver getStartStopScoReceiver(int expectedFlags) { 1416 String[] actions = {AudioManager.ACTION_SCO_AUDIO_STATE_CHANGED}; 1417 StartStopScoReceiver receiver = new StartStopScoReceiver(expectedFlags); 1418 addReceiver(receiver, actions); 1419 return receiver; 1420 } 1421 1422 private void removeReceiver(BroadcastReceiver receiver) { 1423 mContext.unregisterReceiver(receiver); 1424 mReceivers.remove(receiver); 1425 } 1426 1427 private BluetoothProfile connectProxy(BluetoothAdapter adapter, int profile) { 1428 switch (profile) { 1429 case BluetoothProfile.A2DP: 1430 if (mA2dp != null) { 1431 return mA2dp; 1432 } 1433 break; 1434 case BluetoothProfile.HEADSET: 1435 if (mHeadset != null) { 1436 return mHeadset; 1437 } 1438 break; 1439 case BluetoothProfile.INPUT_DEVICE: 1440 if (mInput != null) { 1441 return mInput; 1442 } 1443 break; 1444 case BluetoothProfile.PAN: 1445 if (mPan != null) { 1446 return mPan; 1447 } 1448 break; 1449 default: 1450 return null; 1451 } 1452 adapter.getProfileProxy(mContext, mServiceListener, profile); 1453 long s = System.currentTimeMillis(); 1454 switch (profile) { 1455 case BluetoothProfile.A2DP: 1456 while (mA2dp == null && System.currentTimeMillis() - s < CONNECT_PROXY_TIMEOUT) { 1457 sleep(POLL_TIME); 1458 } 1459 return mA2dp; 1460 case BluetoothProfile.HEADSET: 1461 while (mHeadset == null && System.currentTimeMillis() - s < CONNECT_PROXY_TIMEOUT) { 1462 sleep(POLL_TIME); 1463 } 1464 return mHeadset; 1465 case BluetoothProfile.INPUT_DEVICE: 1466 while (mInput == null && System.currentTimeMillis() - s < CONNECT_PROXY_TIMEOUT) { 1467 sleep(POLL_TIME); 1468 } 1469 return mInput; 1470 case BluetoothProfile.PAN: 1471 while (mPan == null && System.currentTimeMillis() - s < CONNECT_PROXY_TIMEOUT) { 1472 sleep(POLL_TIME); 1473 } 1474 return mPan; 1475 default: 1476 return null; 1477 } 1478 } 1479 1480 private void sleep(long time) { 1481 try { 1482 Thread.sleep(time); 1483 } catch (InterruptedException e) { 1484 } 1485 } 1486} 1487