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