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