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