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