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