1 /* 2 * Copyright (C) 2019 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 com.android.tradefed.util; 18 19 import com.android.tradefed.device.DeviceNotAvailableException; 20 import com.android.tradefed.device.ITestDevice; 21 import com.android.tradefed.log.LogUtil.CLog; 22 import com.android.tradefed.util.sl4a.Sl4aClient; 23 import com.android.tradefed.util.sl4a.Sl4aEventDispatcher.EventSl4aObject; 24 25 import com.google.common.annotations.VisibleForTesting; 26 27 import org.json.JSONArray; 28 import org.json.JSONException; 29 import org.json.JSONObject; 30 31 import java.io.File; 32 import java.io.IOException; 33 import java.time.Duration; 34 import java.util.HashMap; 35 import java.util.HashSet; 36 import java.util.Map; 37 import java.util.Set; 38 import java.util.stream.Collectors; 39 import java.util.stream.Stream; 40 41 /** A utility class provides Bluetooth operations on one or two devices using SL4A */ 42 public class Sl4aBluetoothUtil { 43 44 private static final long BT_STATE_CHANGE_TIMEOUT_MS = 10000; 45 private static final long BT_PAIRING_CHECK_INTERVAL_MS = 200; 46 private static final long BT_CHECK_CONNECTION_INTERVAL_MS = 100; 47 48 @VisibleForTesting 49 static final String BT_SNOOP_LOG_CMD_LEGACY = "setprop persist.bluetooth.btsnoopenable %s"; 50 51 @VisibleForTesting 52 static final String BT_SNOOP_LOG_CMD = "setprop persist.bluetooth.btsnooplogmode %s"; 53 54 /** Holding mappings from device serial number to the {@link Sl4aClient} of the device */ 55 private Map<String, Sl4aClient> mSl4aClients = new HashMap<>(); 56 57 /** Holding mappings from {@link ITestDevice} instance to device MAC address */ 58 private Map<ITestDevice, String> mAddresses = new HashMap<>(); 59 60 private Duration mBtPairTimeout = Duration.ofSeconds(25); 61 62 private Duration mBtConnectionTimeout = Duration.ofSeconds(15); 63 64 /** SL4A RPC commands to be used for Bluetooth operations */ 65 @VisibleForTesting 66 static class Commands { 67 static final String BLUETOOTH_CHECK_STATE = "bluetoothCheckState"; 68 static final String BLUETOOTH_TOGGLE_STATE = "bluetoothToggleState"; 69 static final String BLUETOOTH_GET_LOCAL_ADDRESS = "bluetoothGetLocalAddress"; 70 static final String BLUETOOTH_GET_BONDED_DEVICES = "bluetoothGetBondedDevices"; 71 static final String BLUETOOTH_MAKE_DISCOVERABLE = "bluetoothMakeDiscoverable"; 72 static final String BLUETOOTH_GET_SCAN_MODE = "bluetoothGetScanMode"; 73 static final String BLUETOOTH_START_PAIRING_HELPER = "bluetoothStartPairingHelper"; 74 static final String BLUETOOTH_DISCOVER_AND_BOND = "bluetoothDiscoverAndBond"; 75 static final String BLUETOOTH_UNBOND = "bluetoothUnbond"; 76 static final String BLUETOOTH_START_CONNECTION_STATE_CHANGE_MONITOR = 77 "bluetoothStartConnectionStateChangeMonitor"; 78 static final String BLUETOOTH_CONNECT_BONDED = "bluetoothConnectBonded"; 79 static final String BLUETOOTH_DISCONNECT_CONNECTED_PROFILE = 80 "bluetoothDisconnectConnectedProfile"; 81 static final String BLUETOOTH_HFP_CLIENT_GET_CONNECTED_DEVICES = 82 "bluetoothHfpClientGetConnectedDevices"; 83 static final String BLUETOOTH_A2DP_GET_CONNECTED_DEVICES = 84 "bluetoothA2dpGetConnectedDevices"; 85 static final String BLUETOOTH_A2DP_SINK_GET_CONNECTED_DEVICES = 86 "bluetoothA2dpSinkGetConnectedDevices"; 87 static final String BLUETOOTH_PBAP_CLIENT_GET_CONNECTED_DEVICES = 88 "bluetoothPbapClientGetConnectedDevices"; 89 static final String BLUETOOTH_PAN_GET_CONNECTED_DEVICES = "bluetoothPanGetConnectedDevices"; 90 static final String BLUETOOTH_MAP_GET_CONNECTED_DEVICES = "bluetoothMapGetConnectedDevices"; 91 static final String BLUETOOTH_MAP_CLIENT_GET_CONNECTED_DEVICES = 92 "bluetoothMapClientGetConnectedDevices"; 93 static final String BLUETOOTH_CHANGE_PROFILE_ACCESS_PERMISSION = 94 "bluetoothChangeProfileAccessPermission"; 95 static final String BLUETOOTH_A2DP_SINK_SET_PRIORITY = "bluetoothA2dpSinkSetPriority"; 96 static final String BLUETOOTH_HFP_CLIENT_SET_PRIORITY = "bluetoothHfpClientSetPriority"; 97 static final String BLUETOOTH_PBAP_CLIENT_SET_PRIORITY = "bluetoothPbapClientSetPriority"; 98 } 99 100 /** SL4A events to be used for Bluetooth */ 101 @VisibleForTesting 102 static class Events { 103 static final String BLUETOOTH_STATE_CHANGED_ON = "BluetoothStateChangedOn"; 104 static final String BLUETOOTH_STATE_CHANGED_OFF = "BluetoothStateChangedOff"; 105 static final String BLUETOOTH_PROFILE_CONNECTION_STATE_CHANGED = 106 "BluetoothProfileConnectionStateChanged"; 107 } 108 109 /** Enums for Bluetooth profiles which are based on {@code BluetoothProfile.java} */ 110 public enum BluetoothProfile { 111 HEADSET(1), 112 A2DP(2), 113 HID_HOST(4), 114 PAN(5), 115 PBAP(6), 116 GATT(7), 117 GATT_SERVER(8), 118 MAP(9), 119 SAP(10), 120 A2DP_SINK(11), 121 AVRCP_CONTROLLER(12), 122 HEADSET_CLIENT(16), 123 PBAP_CLIENT(17), 124 MAP_CLIENT(18); 125 126 private static final Map<Integer, BluetoothProfile> sProfileToValue = 127 Stream.of(values()) 128 .collect(Collectors.toMap(BluetoothProfile::getProfile, value -> value)); 129 130 private final int mProfile; 131 valueOfProfile(int profile)132 public static BluetoothProfile valueOfProfile(int profile) { 133 return sProfileToValue.get(profile); 134 } 135 BluetoothProfile(int profile)136 BluetoothProfile(int profile) { 137 mProfile = profile; 138 } 139 getProfile()140 public int getProfile() { 141 return mProfile; 142 } 143 } 144 145 /** Enums for Bluetooth connection states which are based on {@code BluetoothProfile.java} */ 146 public enum BluetoothConnectionState { 147 DISCONNECTED(0), 148 CONNECTING(1), 149 CONNECTED(2), 150 DISCONNECTING(3); 151 152 private final int mState; 153 BluetoothConnectionState(int state)154 BluetoothConnectionState(int state) { 155 mState = state; 156 } 157 getState()158 public int getState() { 159 return mState; 160 } 161 } 162 163 /** Enums for Bluetooth device access level which are based on {@code BluetoothDevice.java} */ 164 public enum BluetoothAccessLevel { 165 ACCESS_UNKNOWN(0), 166 ACCESS_ALLOWED(1), 167 ACCESS_REJECTED(2); 168 169 private final int mAccess; 170 BluetoothAccessLevel(int access)171 BluetoothAccessLevel(int access) { 172 mAccess = access; 173 } 174 getAccess()175 public int getAccess() { 176 return mAccess; 177 } 178 } 179 180 /** 181 * Enums for Bluetooth profile priority level which are based on {@code BluetoothProfile.java} 182 */ 183 public enum BluetoothPriorityLevel { 184 PRIORITY_AUTO_CONNECT(1000), 185 PRIORITY_ON(100), 186 PRIORITY_OFF(0), 187 PRIORITY_UNDEFINED(-1); 188 189 private final int mPriority; 190 BluetoothPriorityLevel(int priority)191 BluetoothPriorityLevel(int priority) { 192 mPriority = priority; 193 } 194 getPriority()195 public int getPriority() { 196 return mPriority; 197 } 198 } 199 setBtPairTimeout(Duration timeout)200 public void setBtPairTimeout(Duration timeout) { 201 mBtPairTimeout = timeout; 202 } 203 setBtConnectionTimeout(Duration timeout)204 public void setBtConnectionTimeout(Duration timeout) { 205 mBtConnectionTimeout = timeout; 206 } 207 208 /** 209 * Explicitly start SL4A client with the given device and SL4A apk file. Normally this method is 210 * not required, because SL4A connection will always be established before actual operations. 211 * 212 * @param device the device to be connected using SL4A 213 * @param sl4aApkFile the optional SL4A apk to install and use. 214 * @throws DeviceNotAvailableException 215 */ startSl4a(ITestDevice device, File sl4aApkFile)216 public void startSl4a(ITestDevice device, File sl4aApkFile) throws DeviceNotAvailableException { 217 Sl4aClient sl4aClient = Sl4aClient.startSL4A(device, sl4aApkFile); 218 mSl4aClients.put(device.getSerialNumber(), sl4aClient); 219 } 220 221 /** 222 * Stop SL4A clients that already being opened. It basically provide a way to cleanup clients 223 * immediately after they are no longer used 224 */ stopSl4a()225 public void stopSl4a() { 226 for (Map.Entry<String, Sl4aClient> entry : mSl4aClients.entrySet()) { 227 entry.getValue().close(); 228 } 229 mSl4aClients.clear(); 230 } 231 232 @VisibleForTesting setSl4a(ITestDevice device, Sl4aClient client)233 void setSl4a(ITestDevice device, Sl4aClient client) { 234 mSl4aClients.put(device.getSerialNumber(), client); 235 } 236 237 /** Clean up all SL4A connections */ 238 @Override finalize()239 protected void finalize() { 240 stopSl4a(); 241 } 242 243 /** 244 * Enable Bluetooth on target device 245 * 246 * @param device target device 247 * @return true if Bluetooth successfully enabled 248 * @throws DeviceNotAvailableException 249 */ enable(ITestDevice device)250 public boolean enable(ITestDevice device) throws DeviceNotAvailableException { 251 return toggleState(device, true); 252 } 253 254 /** 255 * Disable Bluetooth on target device 256 * 257 * @param device target device 258 * @return true if Bluetooth successfully disabled 259 * @throws DeviceNotAvailableException 260 */ disable(ITestDevice device)261 public boolean disable(ITestDevice device) throws DeviceNotAvailableException { 262 return toggleState(device, false); 263 } 264 265 /** 266 * Get Bluetooth MAC Address of target device 267 * 268 * @param device target device 269 * @return MAC Address string 270 * @throws DeviceNotAvailableException 271 */ getAddress(ITestDevice device)272 public String getAddress(ITestDevice device) throws DeviceNotAvailableException { 273 if (mAddresses.containsKey(device)) { 274 return mAddresses.get(device); 275 } 276 Sl4aClient client = getSl4aClient(device); 277 String address = null; 278 try { 279 address = (String) client.rpcCall(Commands.BLUETOOTH_GET_LOCAL_ADDRESS); 280 mAddresses.put(device, address); 281 } catch (IOException e) { 282 CLog.e( 283 "Failed to get Bluetooth MAC address on device: %s, %s", 284 device.getSerialNumber(), e); 285 } 286 return address; 287 } 288 289 /** 290 * Get set of Bluetooth MAC addresses of the bonded (paired) devices on the target device 291 * 292 * @param device target device 293 * @return Set of Bluetooth MAC addresses 294 * @throws DeviceNotAvailableException 295 */ getBondedDevices(ITestDevice device)296 public Set<String> getBondedDevices(ITestDevice device) throws DeviceNotAvailableException { 297 Set<String> addresses = new HashSet<>(); 298 Sl4aClient client = getSl4aClient(device); 299 try { 300 Object response = client.rpcCall(Commands.BLUETOOTH_GET_BONDED_DEVICES); 301 if (response != null) { 302 JSONArray bondedDevices = (JSONArray) response; 303 for (int i = 0; i < bondedDevices.length(); i++) { 304 JSONObject bondedDevice = bondedDevices.getJSONObject(i); 305 if (bondedDevice.has("address")) { 306 addresses.add(bondedDevice.getString("address")); 307 } 308 } 309 } 310 } catch (IOException | JSONException e) { 311 CLog.e("Failed to get bonded devices for device: %s, %s", device.getSerialNumber(), e); 312 } 313 return addresses; 314 } 315 316 /** 317 * Pair primary device to secondary device 318 * 319 * @param primary device to pair from 320 * @param secondary device to pair to 321 * @return true if pairing is successful 322 * @throws DeviceNotAvailableException 323 */ pair(ITestDevice primary, ITestDevice secondary)324 public boolean pair(ITestDevice primary, ITestDevice secondary) 325 throws DeviceNotAvailableException { 326 Sl4aClient primaryClient = getSl4aClient(primary); 327 Sl4aClient secondaryClient = getSl4aClient(secondary); 328 try { 329 if (isPaired(primary, secondary)) { 330 CLog.i("The two devices are already paired."); 331 return true; 332 } 333 CLog.d("Make secondary device discoverable"); 334 secondaryClient.rpcCall(Commands.BLUETOOTH_MAKE_DISCOVERABLE); 335 Integer response = (Integer) secondaryClient.rpcCall(Commands.BLUETOOTH_GET_SCAN_MODE); 336 if (response != 3) { 337 CLog.e("Scan mode is not CONNECTABLE_DISCOVERABLE"); 338 return false; 339 } 340 CLog.d("Secondary device is made discoverable"); 341 342 CLog.d("Start pairing helper on both devices"); 343 primaryClient.rpcCall(Commands.BLUETOOTH_START_PAIRING_HELPER); 344 secondaryClient.rpcCall(Commands.BLUETOOTH_START_PAIRING_HELPER); 345 346 // Discover and bond (pair) companion device 347 CLog.d("Start discover and bond to secondary device: %s", secondary.getSerialNumber()); 348 primaryClient.getEventDispatcher().clearAllEvents(); 349 primaryClient.rpcCall(Commands.BLUETOOTH_DISCOVER_AND_BOND, getAddress(secondary)); 350 351 if (!waitUntilPaired(primary, secondary)) { 352 CLog.e("Bluetooth pairing timeout"); 353 return false; 354 } 355 } catch (IOException | InterruptedException e) { 356 CLog.e("Error when pair two devices, %s", e); 357 return false; 358 } 359 CLog.i("Secondary device successfully paired"); 360 return true; 361 } 362 363 /** 364 * Un-pair all paired devices for current device 365 * 366 * @param device Current device to perform the action 367 * @return true if un-pair successfully 368 * @throws DeviceNotAvailableException 369 */ unpairAll(ITestDevice device)370 public boolean unpairAll(ITestDevice device) throws DeviceNotAvailableException { 371 Set<String> bondedDevices = getBondedDevices(device); 372 Sl4aClient client = getSl4aClient(device); 373 for (String address : bondedDevices) { 374 try { 375 Boolean res = (Boolean) client.rpcCall(Commands.BLUETOOTH_UNBOND, address); 376 if (!res) { 377 CLog.w( 378 "Failed to unpair device %s. It may not be an actual failure, instead" 379 + " it may be due to trying to unpair an already unpaired device." 380 + " This usually happens when device was first connected using LE" 381 + " transport where both LE address and classic address are paired" 382 + " and unpaired at the same time.", 383 address); 384 } 385 } catch (IOException e) { 386 CLog.e("Failed to unpair all Bluetooth devices, %s", e); 387 } 388 } 389 return getBondedDevices(device).isEmpty(); 390 } 391 392 /** 393 * Connect primary device to secondary device on given Bluetooth profiles 394 * 395 * @param primary device to connect from 396 * @param secondary device to connect to 397 * @param profiles A set of Bluetooth profiles are required to be connected 398 * @return true if connection are successful 399 * @throws DeviceNotAvailableException 400 */ connect( ITestDevice primary, ITestDevice secondary, Set<BluetoothProfile> profiles)401 public boolean connect( 402 ITestDevice primary, ITestDevice secondary, Set<BluetoothProfile> profiles) 403 throws DeviceNotAvailableException { 404 if (!isPaired(primary, secondary)) { 405 CLog.e("Primary device have not yet paired to secondary device"); 406 return false; 407 } 408 CLog.d("Connecting to profiles: %s", profiles); 409 Sl4aClient primaryClient = getSl4aClient(primary); 410 String address = getAddress(secondary); 411 try { 412 primaryClient.rpcCall( 413 Commands.BLUETOOTH_START_CONNECTION_STATE_CHANGE_MONITOR, address); 414 primaryClient.rpcCall(Commands.BLUETOOTH_CONNECT_BONDED, address); 415 Set<BluetoothProfile> connectedProfiles = 416 waitForConnectedOrDisconnectedProfiles( 417 primary, address, BluetoothConnectionState.CONNECTED, profiles); 418 return waitForRemainingProfilesConnected(primary, address, connectedProfiles, profiles); 419 } catch (IOException | InterruptedException | JSONException e) { 420 CLog.e("Failed to connect to secondary device, %s", e); 421 } 422 return false; 423 } 424 425 /** 426 * Disconnect primary device from secondary device 427 * 428 * @param primary device to perform disconnect operation 429 * @param secondary device to be disconnected 430 * @param profiles Given set of Bluetooth profiles required to be disconnected 431 * @return true if disconnected successfully 432 * @throws DeviceNotAvailableException 433 */ disconnect( ITestDevice primary, ITestDevice secondary, Set<BluetoothProfile> profiles)434 public boolean disconnect( 435 ITestDevice primary, ITestDevice secondary, Set<BluetoothProfile> profiles) 436 throws DeviceNotAvailableException { 437 CLog.d("Disconnecting to profiles: %s", profiles); 438 Sl4aClient primaryClient = getSl4aClient(primary); 439 String address = getAddress(secondary); 440 try { 441 primaryClient.rpcCall( 442 Commands.BLUETOOTH_START_CONNECTION_STATE_CHANGE_MONITOR, address); 443 primaryClient.rpcCall( 444 Commands.BLUETOOTH_DISCONNECT_CONNECTED_PROFILE, 445 address, 446 new JSONArray( 447 profiles.stream() 448 .map(profile -> profile.getProfile()) 449 .collect(Collectors.toList()))); 450 Set<BluetoothProfile> disconnectedProfiles = 451 waitForConnectedOrDisconnectedProfiles( 452 primary, address, BluetoothConnectionState.DISCONNECTED, profiles); 453 return waitForRemainingProfilesDisconnected( 454 primary, address, disconnectedProfiles, profiles); 455 } catch (IOException | JSONException | InterruptedException e) { 456 CLog.e("Failed to disconnect from secondary device, %s", e); 457 } 458 return false; 459 } 460 461 /** 462 * Enable Bluetooth snoop log 463 * 464 * @param device to enable snoop log 465 * @return true if enabled successfully 466 * @throws DeviceNotAvailableException 467 */ enableBluetoothSnoopLog(ITestDevice device)468 public boolean enableBluetoothSnoopLog(ITestDevice device) throws DeviceNotAvailableException { 469 if (isQAndAbove(device)) { 470 device.executeShellCommand(String.format(BT_SNOOP_LOG_CMD, "full")); 471 } else { 472 device.executeShellCommand(String.format(BT_SNOOP_LOG_CMD_LEGACY, "true")); 473 } 474 return disable(device) && enable(device); 475 } 476 477 /** 478 * Disable Bluetooth snoop log 479 * 480 * @param device to disable snoop log 481 * @return true if disabled successfully 482 * @throws DeviceNotAvailableException 483 */ disableBluetoothSnoopLog(ITestDevice device)484 public boolean disableBluetoothSnoopLog(ITestDevice device) throws DeviceNotAvailableException { 485 if (isQAndAbove(device)) { 486 device.executeShellCommand(String.format(BT_SNOOP_LOG_CMD, "disabled")); 487 } else { 488 device.executeShellCommand(String.format(BT_SNOOP_LOG_CMD_LEGACY, "false")); 489 } 490 return disable(device) && enable(device); 491 } 492 493 /** 494 * Change Bluetooth profile access permission of secondary device on primary device in order for 495 * secondary device to access primary device on the given profile 496 * 497 * @param primary device to change permission 498 * @param secondary device that accesses primary device on the given profile 499 * @param profile Bluetooth profile to access 500 * @param access level of access, see {@code BluetoothAccessLevel} 501 * @return true if permission changed successfully 502 * @throws DeviceNotAvailableException 503 */ changeProfileAccessPermission( ITestDevice primary, ITestDevice secondary, BluetoothProfile profile, BluetoothAccessLevel access)504 public boolean changeProfileAccessPermission( 505 ITestDevice primary, 506 ITestDevice secondary, 507 BluetoothProfile profile, 508 BluetoothAccessLevel access) 509 throws DeviceNotAvailableException { 510 Sl4aClient primaryClient = getSl4aClient(primary); 511 String secondaryAddress = getAddress(secondary); 512 try { 513 primaryClient.rpcCall( 514 Commands.BLUETOOTH_CHANGE_PROFILE_ACCESS_PERMISSION, 515 secondaryAddress, 516 profile.mProfile, 517 access.getAccess()); 518 } catch (IOException e) { 519 CLog.e("Failed to set profile access level %s for profile %s, %s", access, profile, e); 520 return false; 521 } 522 return true; 523 } 524 525 /** 526 * Change priority setting of given profiles on primary device towards secondary device 527 * 528 * @param primary device to set priority on 529 * @param secondary device to set priority for 530 * @param profiles Bluetooth profiles to change priority setting 531 * @param priority level of priority 532 * @return true if set priority successfully 533 * @throws DeviceNotAvailableException 534 */ setProfilePriority( ITestDevice primary, ITestDevice secondary, Set<BluetoothProfile> profiles, BluetoothPriorityLevel priority)535 public boolean setProfilePriority( 536 ITestDevice primary, 537 ITestDevice secondary, 538 Set<BluetoothProfile> profiles, 539 BluetoothPriorityLevel priority) 540 throws DeviceNotAvailableException { 541 Sl4aClient primaryClient = getSl4aClient(primary); 542 String secondaryAddress = getAddress(secondary); 543 544 for (BluetoothProfile profile : profiles) { 545 try { 546 switch (profile) { 547 case A2DP_SINK: 548 primaryClient.rpcCall( 549 Commands.BLUETOOTH_A2DP_SINK_SET_PRIORITY, 550 secondaryAddress, 551 priority.getPriority()); 552 break; 553 case HEADSET_CLIENT: 554 primaryClient.rpcCall( 555 Commands.BLUETOOTH_HFP_CLIENT_SET_PRIORITY, 556 secondaryAddress, 557 priority.getPriority()); 558 break; 559 case PBAP_CLIENT: 560 primaryClient.rpcCall( 561 Commands.BLUETOOTH_PBAP_CLIENT_SET_PRIORITY, 562 secondaryAddress, 563 priority.getPriority()); 564 break; 565 default: 566 CLog.e("Profile %s is not yet supported for priority settings", profile); 567 return false; 568 } 569 } catch (IOException e) { 570 CLog.e("Failed to set profile %s with priority %s, %s", profile, priority, e); 571 return false; 572 } 573 } 574 return true; 575 } 576 toggleState(ITestDevice device, boolean targetState)577 private boolean toggleState(ITestDevice device, boolean targetState) 578 throws DeviceNotAvailableException { 579 Sl4aClient client = getSl4aClient(device); 580 try { 581 boolean currentState = (Boolean) client.rpcCall(Commands.BLUETOOTH_CHECK_STATE); 582 if (currentState == targetState) { 583 return true; 584 } 585 client.getEventDispatcher().clearAllEvents(); 586 Boolean result = (Boolean) client.rpcCall(Commands.BLUETOOTH_TOGGLE_STATE, targetState); 587 if (!result) { 588 CLog.e( 589 "Error in sl4a when toggling %s Bluetooth state.", 590 targetState ? "ON" : "OFF"); 591 return false; 592 } 593 String event = 594 targetState 595 ? Events.BLUETOOTH_STATE_CHANGED_ON 596 : Events.BLUETOOTH_STATE_CHANGED_OFF; 597 EventSl4aObject response = 598 client.getEventDispatcher().popEvent(event, BT_STATE_CHANGE_TIMEOUT_MS); 599 if (response == null) { 600 CLog.e( 601 "Get null response after toggling %s Bluetooth state.", 602 targetState ? "ON" : "OFF"); 603 return false; 604 } 605 } catch (IOException e) { 606 CLog.e("Error when toggling %s Bluetooth state, %s", targetState ? "ON" : "OFF", e); 607 return false; 608 } 609 return true; 610 } 611 getSl4aClient(ITestDevice device)612 private Sl4aClient getSl4aClient(ITestDevice device) throws DeviceNotAvailableException { 613 String serial = device.getSerialNumber(); 614 if (!mSl4aClients.containsKey(serial)) { 615 Sl4aClient client = Sl4aClient.startSL4A(device, null); 616 mSl4aClients.put(serial, client); 617 } 618 return mSl4aClients.get(serial); 619 } 620 waitUntilPaired(ITestDevice primary, ITestDevice secondary)621 private boolean waitUntilPaired(ITestDevice primary, ITestDevice secondary) 622 throws InterruptedException, DeviceNotAvailableException { 623 long endTime = System.currentTimeMillis() + mBtPairTimeout.toMillis(); 624 while (System.currentTimeMillis() < endTime) { 625 if (isPaired(primary, secondary)) { 626 return true; 627 } 628 Thread.sleep(BT_PAIRING_CHECK_INTERVAL_MS); 629 } 630 return false; 631 } 632 isPaired(ITestDevice primary, ITestDevice secondary)633 private boolean isPaired(ITestDevice primary, ITestDevice secondary) 634 throws DeviceNotAvailableException { 635 return getBondedDevices(primary).contains(getAddress(secondary)); 636 } 637 waitForConnectedOrDisconnectedProfiles( ITestDevice primary, String address, BluetoothConnectionState targetState, Set<BluetoothProfile> allProfiles)638 private Set<BluetoothProfile> waitForConnectedOrDisconnectedProfiles( 639 ITestDevice primary, 640 String address, 641 BluetoothConnectionState targetState, 642 Set<BluetoothProfile> allProfiles) 643 throws DeviceNotAvailableException, JSONException { 644 645 Set<BluetoothProfile> targetProfiles = new HashSet<>(); 646 Sl4aClient primaryClient = getSl4aClient(primary); 647 // Check connection broadcast receiver 648 while (!targetProfiles.containsAll(allProfiles)) { 649 EventSl4aObject event = 650 primaryClient 651 .getEventDispatcher() 652 .popEvent( 653 Events.BLUETOOTH_PROFILE_CONNECTION_STATE_CHANGED, 654 mBtConnectionTimeout.toMillis()); 655 if (event == null) { 656 CLog.w("Timeout while waiting for connection state changes for all profiles"); 657 return targetProfiles; 658 } 659 JSONObject profileData = new JSONObject(event.getData()); 660 int profile = profileData.getInt("profile"); 661 int state = profileData.getInt("state"); 662 String actualAddress = profileData.getString("addr"); 663 if (state == targetState.getState() && address.equals(actualAddress)) { 664 targetProfiles.add(BluetoothProfile.valueOfProfile(profile)); 665 } 666 } 667 return targetProfiles; 668 } 669 waitForRemainingProfilesConnected( ITestDevice primary, String address, Set<BluetoothProfile> connectedProfiles, Set<BluetoothProfile> allProfiles)670 private boolean waitForRemainingProfilesConnected( 671 ITestDevice primary, 672 String address, 673 Set<BluetoothProfile> connectedProfiles, 674 Set<BluetoothProfile> allProfiles) 675 throws InterruptedException, DeviceNotAvailableException, JSONException, IOException { 676 long endTime = System.currentTimeMillis() + mBtConnectionTimeout.toMillis(); 677 while (System.currentTimeMillis() < endTime 678 && !connectedProfiles.containsAll(allProfiles)) { 679 for (BluetoothProfile profile : allProfiles) { 680 if (connectedProfiles.contains(profile)) { 681 continue; 682 } 683 if (isProfileConnected(primary, address, profile)) { 684 connectedProfiles.add(profile); 685 } 686 CLog.d("Connected profiles for now: %s", connectedProfiles); 687 } 688 Thread.sleep(BT_CHECK_CONNECTION_INTERVAL_MS); 689 } 690 return connectedProfiles.containsAll(allProfiles); 691 } 692 waitForRemainingProfilesDisconnected( ITestDevice primary, String address, Set<BluetoothProfile> disConnectedProfiles, Set<BluetoothProfile> allProfiles)693 private boolean waitForRemainingProfilesDisconnected( 694 ITestDevice primary, 695 String address, 696 Set<BluetoothProfile> disConnectedProfiles, 697 Set<BluetoothProfile> allProfiles) 698 throws InterruptedException, DeviceNotAvailableException, JSONException, IOException { 699 long endTime = System.currentTimeMillis() + mBtConnectionTimeout.toMillis(); 700 while (System.currentTimeMillis() < endTime 701 && !disConnectedProfiles.containsAll(allProfiles)) { 702 for (BluetoothProfile profile : allProfiles) { 703 if (disConnectedProfiles.contains(profile)) { 704 continue; 705 } 706 if (!isProfileConnected(primary, address, profile)) { 707 disConnectedProfiles.add(profile); 708 } 709 CLog.d("Disconnected profiles for now: %s", disConnectedProfiles); 710 } 711 Thread.sleep(BT_CHECK_CONNECTION_INTERVAL_MS); 712 } 713 return disConnectedProfiles.containsAll(allProfiles); 714 } 715 isProfileConnected( ITestDevice primary, String address, BluetoothProfile profile)716 private boolean isProfileConnected( 717 ITestDevice primary, String address, BluetoothProfile profile) 718 throws DeviceNotAvailableException, IOException, JSONException { 719 switch (profile) { 720 case HEADSET_CLIENT: 721 return checkConnectedDevice( 722 primary, address, Commands.BLUETOOTH_HFP_CLIENT_GET_CONNECTED_DEVICES); 723 case A2DP: 724 return checkConnectedDevice( 725 primary, address, Commands.BLUETOOTH_A2DP_GET_CONNECTED_DEVICES); 726 case A2DP_SINK: 727 return checkConnectedDevice( 728 primary, address, Commands.BLUETOOTH_A2DP_SINK_GET_CONNECTED_DEVICES); 729 case PAN: 730 return checkConnectedDevice( 731 primary, address, Commands.BLUETOOTH_PAN_GET_CONNECTED_DEVICES); 732 case PBAP_CLIENT: 733 return checkConnectedDevice( 734 primary, address, Commands.BLUETOOTH_PBAP_CLIENT_GET_CONNECTED_DEVICES); 735 case MAP: 736 return checkConnectedDevice( 737 primary, address, Commands.BLUETOOTH_MAP_GET_CONNECTED_DEVICES); 738 case MAP_CLIENT: 739 return checkConnectedDevice( 740 primary, address, Commands.BLUETOOTH_MAP_CLIENT_GET_CONNECTED_DEVICES); 741 default: 742 CLog.e("Unsupported profile %s to check connection state", profile); 743 } 744 return false; 745 } 746 checkConnectedDevice(ITestDevice primary, String address, String sl4aCommand)747 private boolean checkConnectedDevice(ITestDevice primary, String address, String sl4aCommand) 748 throws DeviceNotAvailableException, IOException, JSONException { 749 Sl4aClient primaryClient = getSl4aClient(primary); 750 JSONArray devices = (JSONArray) primaryClient.rpcCall(sl4aCommand); 751 if (devices == null) { 752 CLog.e("Empty response"); 753 return false; 754 } 755 for (int i = 0; i < devices.length(); i++) { 756 JSONObject device = devices.getJSONObject(i); 757 if (device.has("address") && device.getString("address").equals(address)) { 758 return true; 759 } 760 } 761 return false; 762 } 763 isQAndAbove(ITestDevice device)764 private boolean isQAndAbove(ITestDevice device) throws DeviceNotAvailableException { 765 return device.getApiLevel() > 28; 766 } 767 } 768