blob: ee0893644dc8b2af6b66183702a1b9ab88e04e04 [file] [log] [blame]
/*
* Copyright (C) 2019 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License
*/
package com.android.tradefed.util;
import com.android.tradefed.device.DeviceNotAvailableException;
import com.android.tradefed.device.ITestDevice;
import com.android.tradefed.log.LogUtil.CLog;
import com.android.tradefed.util.sl4a.Sl4aClient;
import com.android.tradefed.util.sl4a.Sl4aEventDispatcher.EventSl4aObject;
import com.google.common.annotations.VisibleForTesting;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import java.io.File;
import java.io.IOException;
import java.time.Duration;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.Stream;
/** A utility class provides Bluetooth operations on one or two devices using SL4A */
public class Sl4aBluetoothUtil {
private static final long BT_STATE_CHANGE_TIMEOUT_MS = 10000;
private static final long BT_PAIRING_CHECK_INTERVAL_MS = 200;
private static final long BT_CHECK_CONNECTION_INTERVAL_MS = 100;
@VisibleForTesting
static final String BT_SNOOP_LOG_CMD_LEGACY = "setprop persist.bluetooth.btsnoopenable %s";
@VisibleForTesting
static final String BT_SNOOP_LOG_CMD = "setprop persist.bluetooth.btsnooplogmode %s";
/** Holding mappings from device serial number to the {@link Sl4aClient} of the device */
private Map<String, Sl4aClient> mSl4aClients = new HashMap<>();
/** Holding mappings from {@link ITestDevice} instance to device MAC address */
private Map<ITestDevice, String> mAddresses = new HashMap<>();
private Duration mBtPairTimeout = Duration.ofSeconds(25);
private Duration mBtConnectionTimeout = Duration.ofSeconds(15);
/** SL4A RPC commands to be used for Bluetooth operations */
@VisibleForTesting
static class Commands {
static final String BLUETOOTH_CHECK_STATE = "bluetoothCheckState";
static final String BLUETOOTH_TOGGLE_STATE = "bluetoothToggleState";
static final String BLUETOOTH_GET_LOCAL_ADDRESS = "bluetoothGetLocalAddress";
static final String BLUETOOTH_GET_BONDED_DEVICES = "bluetoothGetBondedDevices";
static final String BLUETOOTH_MAKE_DISCOVERABLE = "bluetoothMakeDiscoverable";
static final String BLUETOOTH_GET_SCAN_MODE = "bluetoothGetScanMode";
static final String BLUETOOTH_START_PAIRING_HELPER = "bluetoothStartPairingHelper";
static final String BLUETOOTH_DISCOVER_AND_BOND = "bluetoothDiscoverAndBond";
static final String BLUETOOTH_UNBOND = "bluetoothUnbond";
static final String BLUETOOTH_START_CONNECTION_STATE_CHANGE_MONITOR =
"bluetoothStartConnectionStateChangeMonitor";
static final String BLUETOOTH_CONNECT_BONDED = "bluetoothConnectBonded";
static final String BLUETOOTH_DISCONNECT_CONNECTED_PROFILE =
"bluetoothDisconnectConnectedProfile";
static final String BLUETOOTH_HFP_CLIENT_GET_CONNECTED_DEVICES =
"bluetoothHfpClientGetConnectedDevices";
static final String BLUETOOTH_A2DP_GET_CONNECTED_DEVICES =
"bluetoothA2dpGetConnectedDevices";
static final String BLUETOOTH_A2DP_SINK_GET_CONNECTED_DEVICES =
"bluetoothA2dpSinkGetConnectedDevices";
static final String BLUETOOTH_PBAP_CLIENT_GET_CONNECTED_DEVICES =
"bluetoothPbapClientGetConnectedDevices";
static final String BLUETOOTH_PAN_GET_CONNECTED_DEVICES = "bluetoothPanGetConnectedDevices";
static final String BLUETOOTH_MAP_GET_CONNECTED_DEVICES = "bluetoothMapGetConnectedDevices";
static final String BLUETOOTH_MAP_CLIENT_GET_CONNECTED_DEVICES =
"bluetoothMapClientGetConnectedDevices";
static final String BLUETOOTH_CHANGE_PROFILE_ACCESS_PERMISSION =
"bluetoothChangeProfileAccessPermission";
static final String BLUETOOTH_A2DP_SINK_SET_PRIORITY = "bluetoothA2dpSinkSetPriority";
static final String BLUETOOTH_HFP_CLIENT_SET_PRIORITY = "bluetoothHfpClientSetPriority";
static final String BLUETOOTH_PBAP_CLIENT_SET_PRIORITY = "bluetoothPbapClientSetPriority";
}
/** SL4A events to be used for Bluetooth */
@VisibleForTesting
static class Events {
static final String BLUETOOTH_STATE_CHANGED_ON = "BluetoothStateChangedOn";
static final String BLUETOOTH_STATE_CHANGED_OFF = "BluetoothStateChangedOff";
static final String BLUETOOTH_PROFILE_CONNECTION_STATE_CHANGED =
"BluetoothProfileConnectionStateChanged";
}
/** Enums for Bluetooth profiles which are based on {@code BluetoothProfile.java} */
public enum BluetoothProfile {
HEADSET(1),
A2DP(2),
HID_HOST(4),
PAN(5),
PBAP(6),
GATT(7),
GATT_SERVER(8),
MAP(9),
SAP(10),
A2DP_SINK(11),
AVRCP_CONTROLLER(12),
HEADSET_CLIENT(16),
PBAP_CLIENT(17),
MAP_CLIENT(18);
private static final Map<Integer, BluetoothProfile> sProfileToValue =
Stream.of(values())
.collect(Collectors.toMap(BluetoothProfile::getProfile, value -> value));
private final int mProfile;
public static BluetoothProfile valueOfProfile(int profile) {
return sProfileToValue.get(profile);
}
BluetoothProfile(int profile) {
mProfile = profile;
}
public int getProfile() {
return mProfile;
}
}
/** Enums for Bluetooth connection states which are based on {@code BluetoothProfile.java} */
public enum BluetoothConnectionState {
DISCONNECTED(0),
CONNECTING(1),
CONNECTED(2),
DISCONNECTING(3);
private final int mState;
BluetoothConnectionState(int state) {
mState = state;
}
public int getState() {
return mState;
}
}
/** Enums for Bluetooth device access level which are based on {@code BluetoothDevice.java} */
public enum BluetoothAccessLevel {
ACCESS_UNKNOWN(0),
ACCESS_ALLOWED(1),
ACCESS_REJECTED(2);
private final int mAccess;
BluetoothAccessLevel(int access) {
mAccess = access;
}
public int getAccess() {
return mAccess;
}
}
/**
* Enums for Bluetooth profile priority level which are based on {@code BluetoothProfile.java}
*/
public enum BluetoothPriorityLevel {
PRIORITY_AUTO_CONNECT(1000),
PRIORITY_ON(100),
PRIORITY_OFF(0),
PRIORITY_UNDEFINED(-1);
private final int mPriority;
BluetoothPriorityLevel(int priority) {
mPriority = priority;
}
public int getPriority() {
return mPriority;
}
}
public void setBtPairTimeout(Duration timeout) {
mBtPairTimeout = timeout;
}
public void setBtConnectionTimeout(Duration timeout) {
mBtConnectionTimeout = timeout;
}
/**
* Explicitly start SL4A client with the given device and SL4A apk file. Normally this method is
* not required, because SL4A connection will always be established before actual operations.
*
* @param device the device to be connected using SL4A
* @param sl4aApkFile the optional SL4A apk to install and use.
* @throws DeviceNotAvailableException
*/
public void startSl4a(ITestDevice device, File sl4aApkFile) throws DeviceNotAvailableException {
Sl4aClient sl4aClient = Sl4aClient.startSL4A(device, sl4aApkFile);
mSl4aClients.put(device.getSerialNumber(), sl4aClient);
}
/**
* Stop SL4A clients that already being opened. It basically provide a way to cleanup clients
* immediately after they are no longer used
*/
public void stopSl4a() {
for (Map.Entry<String, Sl4aClient> entry : mSl4aClients.entrySet()) {
entry.getValue().close();
}
mSl4aClients.clear();
}
@VisibleForTesting
void setSl4a(ITestDevice device, Sl4aClient client) {
mSl4aClients.put(device.getSerialNumber(), client);
}
/** Clean up all SL4A connections */
@Override
protected void finalize() {
stopSl4a();
}
/**
* Enable Bluetooth on target device
*
* @param device target device
* @return true if Bluetooth successfully enabled
* @throws DeviceNotAvailableException
*/
public boolean enable(ITestDevice device) throws DeviceNotAvailableException {
return toggleState(device, true);
}
/**
* Disable Bluetooth on target device
*
* @param device target device
* @return true if Bluetooth successfully disabled
* @throws DeviceNotAvailableException
*/
public boolean disable(ITestDevice device) throws DeviceNotAvailableException {
return toggleState(device, false);
}
/**
* Get Bluetooth MAC Address of target device
*
* @param device target device
* @return MAC Address string
* @throws DeviceNotAvailableException
*/
public String getAddress(ITestDevice device) throws DeviceNotAvailableException {
if (mAddresses.containsKey(device)) {
return mAddresses.get(device);
}
Sl4aClient client = getSl4aClient(device);
String address = null;
try {
address = (String) client.rpcCall(Commands.BLUETOOTH_GET_LOCAL_ADDRESS);
mAddresses.put(device, address);
} catch (IOException e) {
CLog.e(
"Failed to get Bluetooth MAC address on device: %s, %s",
device.getSerialNumber(), e);
}
return address;
}
/**
* Get set of Bluetooth MAC addresses of the bonded (paired) devices on the target device
*
* @param device target device
* @return Set of Bluetooth MAC addresses
* @throws DeviceNotAvailableException
*/
public Set<String> getBondedDevices(ITestDevice device) throws DeviceNotAvailableException {
Set<String> addresses = new HashSet<>();
Sl4aClient client = getSl4aClient(device);
try {
Object response = client.rpcCall(Commands.BLUETOOTH_GET_BONDED_DEVICES);
if (response != null) {
JSONArray bondedDevices = (JSONArray) response;
for (int i = 0; i < bondedDevices.length(); i++) {
JSONObject bondedDevice = bondedDevices.getJSONObject(i);
if (bondedDevice.has("address")) {
addresses.add(bondedDevice.getString("address"));
}
}
}
} catch (IOException | JSONException e) {
CLog.e("Failed to get bonded devices for device: %s, %s", device.getSerialNumber(), e);
}
return addresses;
}
/**
* Pair primary device to secondary device
*
* @param primary device to pair from
* @param secondary device to pair to
* @return true if pairing is successful
* @throws DeviceNotAvailableException
*/
public boolean pair(ITestDevice primary, ITestDevice secondary)
throws DeviceNotAvailableException {
Sl4aClient primaryClient = getSl4aClient(primary);
Sl4aClient secondaryClient = getSl4aClient(secondary);
try {
if (isPaired(primary, secondary)) {
CLog.i("The two devices are already paired.");
return true;
}
CLog.d("Make secondary device discoverable");
secondaryClient.rpcCall(Commands.BLUETOOTH_MAKE_DISCOVERABLE);
Integer response = (Integer) secondaryClient.rpcCall(Commands.BLUETOOTH_GET_SCAN_MODE);
if (response != 3) {
CLog.e("Scan mode is not CONNECTABLE_DISCOVERABLE");
return false;
}
CLog.d("Secondary device is made discoverable");
CLog.d("Start pairing helper on both devices");
primaryClient.rpcCall(Commands.BLUETOOTH_START_PAIRING_HELPER);
secondaryClient.rpcCall(Commands.BLUETOOTH_START_PAIRING_HELPER);
// Discover and bond (pair) companion device
CLog.d("Start discover and bond to secondary device: %s", secondary.getSerialNumber());
primaryClient.getEventDispatcher().clearAllEvents();
primaryClient.rpcCall(Commands.BLUETOOTH_DISCOVER_AND_BOND, getAddress(secondary));
if (!waitUntilPaired(primary, secondary)) {
CLog.e("Bluetooth pairing timeout");
return false;
}
} catch (IOException | InterruptedException e) {
CLog.e("Error when pair two devices, %s", e);
return false;
}
CLog.i("Secondary device successfully paired");
return true;
}
/**
* Un-pair all paired devices for current device
*
* @param device Current device to perform the action
* @return true if un-pair successfully
* @throws DeviceNotAvailableException
*/
public boolean unpairAll(ITestDevice device) throws DeviceNotAvailableException {
Set<String> bondedDevices = getBondedDevices(device);
Sl4aClient client = getSl4aClient(device);
for (String address : bondedDevices) {
try {
Boolean res = (Boolean) client.rpcCall(Commands.BLUETOOTH_UNBOND, address);
if (!res) {
CLog.w(
"Failed to unpair device %s. It may not be an actual failure, instead"
+ " it may be due to trying to unpair an already unpaired device."
+ " This usually happens when device was first connected using LE"
+ " transport where both LE address and classic address are paired"
+ " and unpaired at the same time.",
address);
}
} catch (IOException e) {
CLog.e("Failed to unpair all Bluetooth devices, %s", e);
}
}
return getBondedDevices(device).isEmpty();
}
/**
* Connect primary device to secondary device on given Bluetooth profiles
*
* @param primary device to connect from
* @param secondary device to connect to
* @param profiles A set of Bluetooth profiles are required to be connected
* @return true if connection are successful
* @throws DeviceNotAvailableException
*/
public boolean connect(
ITestDevice primary, ITestDevice secondary, Set<BluetoothProfile> profiles)
throws DeviceNotAvailableException {
if (!isPaired(primary, secondary)) {
CLog.e("Primary device have not yet paired to secondary device");
return false;
}
CLog.d("Connecting to profiles: %s", profiles);
Sl4aClient primaryClient = getSl4aClient(primary);
String address = getAddress(secondary);
try {
primaryClient.rpcCall(
Commands.BLUETOOTH_START_CONNECTION_STATE_CHANGE_MONITOR, address);
primaryClient.rpcCall(Commands.BLUETOOTH_CONNECT_BONDED, address);
Set<BluetoothProfile> connectedProfiles =
waitForConnectedOrDisconnectedProfiles(
primary, address, BluetoothConnectionState.CONNECTED, profiles);
return waitForRemainingProfilesConnected(primary, address, connectedProfiles, profiles);
} catch (IOException | InterruptedException | JSONException e) {
CLog.e("Failed to connect to secondary device, %s", e);
}
return false;
}
/**
* Disconnect primary device from secondary device
*
* @param primary device to perform disconnect operation
* @param secondary device to be disconnected
* @param profiles Given set of Bluetooth profiles required to be disconnected
* @return true if disconnected successfully
* @throws DeviceNotAvailableException
*/
public boolean disconnect(
ITestDevice primary, ITestDevice secondary, Set<BluetoothProfile> profiles)
throws DeviceNotAvailableException {
CLog.d("Disconnecting to profiles: %s", profiles);
Sl4aClient primaryClient = getSl4aClient(primary);
String address = getAddress(secondary);
try {
primaryClient.rpcCall(
Commands.BLUETOOTH_START_CONNECTION_STATE_CHANGE_MONITOR, address);
primaryClient.rpcCall(
Commands.BLUETOOTH_DISCONNECT_CONNECTED_PROFILE,
address,
new JSONArray(
profiles.stream()
.map(profile -> profile.getProfile())
.collect(Collectors.toList())));
Set<BluetoothProfile> disconnectedProfiles =
waitForConnectedOrDisconnectedProfiles(
primary, address, BluetoothConnectionState.DISCONNECTED, profiles);
return waitForRemainingProfilesDisconnected(
primary, address, disconnectedProfiles, profiles);
} catch (IOException | JSONException | InterruptedException e) {
CLog.e("Failed to disconnect from secondary device, %s", e);
}
return false;
}
/**
* Enable Bluetooth snoop log
*
* @param device to enable snoop log
* @return true if enabled successfully
* @throws DeviceNotAvailableException
*/
public boolean enableBluetoothSnoopLog(ITestDevice device) throws DeviceNotAvailableException {
if (isQAndAbove(device)) {
device.executeShellCommand(String.format(BT_SNOOP_LOG_CMD, "full"));
} else {
device.executeShellCommand(String.format(BT_SNOOP_LOG_CMD_LEGACY, "true"));
}
return disable(device) && enable(device);
}
/**
* Disable Bluetooth snoop log
*
* @param device to disable snoop log
* @return true if disabled successfully
* @throws DeviceNotAvailableException
*/
public boolean disableBluetoothSnoopLog(ITestDevice device) throws DeviceNotAvailableException {
if (isQAndAbove(device)) {
device.executeShellCommand(String.format(BT_SNOOP_LOG_CMD, "disabled"));
} else {
device.executeShellCommand(String.format(BT_SNOOP_LOG_CMD_LEGACY, "false"));
}
return disable(device) && enable(device);
}
/**
* Change Bluetooth profile access permission of secondary device on primary device in order for
* secondary device to access primary device on the given profile
*
* @param primary device to change permission
* @param secondary device that accesses primary device on the given profile
* @param profile Bluetooth profile to access
* @param access level of access, see {@code BluetoothAccessLevel}
* @return true if permission changed successfully
* @throws DeviceNotAvailableException
*/
public boolean changeProfileAccessPermission(
ITestDevice primary,
ITestDevice secondary,
BluetoothProfile profile,
BluetoothAccessLevel access)
throws DeviceNotAvailableException {
Sl4aClient primaryClient = getSl4aClient(primary);
String secondaryAddress = getAddress(secondary);
try {
primaryClient.rpcCall(
Commands.BLUETOOTH_CHANGE_PROFILE_ACCESS_PERMISSION,
secondaryAddress,
profile.mProfile,
access.getAccess());
} catch (IOException e) {
CLog.e("Failed to set profile access level %s for profile %s, %s", access, profile, e);
return false;
}
return true;
}
/**
* Change priority setting of given profiles on primary device towards secondary device
*
* @param primary device to set priority on
* @param secondary device to set priority for
* @param profiles Bluetooth profiles to change priority setting
* @param priority level of priority
* @return true if set priority successfully
* @throws DeviceNotAvailableException
*/
public boolean setProfilePriority(
ITestDevice primary,
ITestDevice secondary,
Set<BluetoothProfile> profiles,
BluetoothPriorityLevel priority)
throws DeviceNotAvailableException {
Sl4aClient primaryClient = getSl4aClient(primary);
String secondaryAddress = getAddress(secondary);
for (BluetoothProfile profile : profiles) {
try {
switch (profile) {
case A2DP_SINK:
primaryClient.rpcCall(
Commands.BLUETOOTH_A2DP_SINK_SET_PRIORITY,
secondaryAddress,
priority.getPriority());
break;
case HEADSET_CLIENT:
primaryClient.rpcCall(
Commands.BLUETOOTH_HFP_CLIENT_SET_PRIORITY,
secondaryAddress,
priority.getPriority());
break;
case PBAP_CLIENT:
primaryClient.rpcCall(
Commands.BLUETOOTH_PBAP_CLIENT_SET_PRIORITY,
secondaryAddress,
priority.getPriority());
break;
default:
CLog.e("Profile %s is not yet supported for priority settings", profile);
return false;
}
} catch (IOException e) {
CLog.e("Failed to set profile %s with priority %s, %s", profile, priority, e);
return false;
}
}
return true;
}
private boolean toggleState(ITestDevice device, boolean targetState)
throws DeviceNotAvailableException {
Sl4aClient client = getSl4aClient(device);
try {
boolean currentState = (Boolean) client.rpcCall(Commands.BLUETOOTH_CHECK_STATE);
if (currentState == targetState) {
return true;
}
client.getEventDispatcher().clearAllEvents();
Boolean result = (Boolean) client.rpcCall(Commands.BLUETOOTH_TOGGLE_STATE, targetState);
if (!result) {
CLog.e(
"Error in sl4a when toggling %s Bluetooth state.",
targetState ? "ON" : "OFF");
return false;
}
String event =
targetState
? Events.BLUETOOTH_STATE_CHANGED_ON
: Events.BLUETOOTH_STATE_CHANGED_OFF;
EventSl4aObject response =
client.getEventDispatcher().popEvent(event, BT_STATE_CHANGE_TIMEOUT_MS);
if (response == null) {
CLog.e(
"Get null response after toggling %s Bluetooth state.",
targetState ? "ON" : "OFF");
return false;
}
} catch (IOException e) {
CLog.e("Error when toggling %s Bluetooth state, %s", targetState ? "ON" : "OFF", e);
return false;
}
return true;
}
private Sl4aClient getSl4aClient(ITestDevice device) throws DeviceNotAvailableException {
String serial = device.getSerialNumber();
if (!mSl4aClients.containsKey(serial)) {
Sl4aClient client = Sl4aClient.startSL4A(device, null);
mSl4aClients.put(serial, client);
}
return mSl4aClients.get(serial);
}
private boolean waitUntilPaired(ITestDevice primary, ITestDevice secondary)
throws InterruptedException, DeviceNotAvailableException {
long endTime = System.currentTimeMillis() + mBtPairTimeout.toMillis();
while (System.currentTimeMillis() < endTime) {
if (isPaired(primary, secondary)) {
return true;
}
Thread.sleep(BT_PAIRING_CHECK_INTERVAL_MS);
}
return false;
}
private boolean isPaired(ITestDevice primary, ITestDevice secondary)
throws DeviceNotAvailableException {
return getBondedDevices(primary).contains(getAddress(secondary));
}
private Set<BluetoothProfile> waitForConnectedOrDisconnectedProfiles(
ITestDevice primary,
String address,
BluetoothConnectionState targetState,
Set<BluetoothProfile> allProfiles)
throws DeviceNotAvailableException, JSONException {
Set<BluetoothProfile> targetProfiles = new HashSet<>();
Sl4aClient primaryClient = getSl4aClient(primary);
// Check connection broadcast receiver
while (!targetProfiles.containsAll(allProfiles)) {
EventSl4aObject event =
primaryClient
.getEventDispatcher()
.popEvent(
Events.BLUETOOTH_PROFILE_CONNECTION_STATE_CHANGED,
mBtConnectionTimeout.toMillis());
if (event == null) {
CLog.w("Timeout while waiting for connection state changes for all profiles");
return targetProfiles;
}
JSONObject profileData = new JSONObject(event.getData());
int profile = profileData.getInt("profile");
int state = profileData.getInt("state");
String actualAddress = profileData.getString("addr");
if (state == targetState.getState() && address.equals(actualAddress)) {
targetProfiles.add(BluetoothProfile.valueOfProfile(profile));
}
}
return targetProfiles;
}
private boolean waitForRemainingProfilesConnected(
ITestDevice primary,
String address,
Set<BluetoothProfile> connectedProfiles,
Set<BluetoothProfile> allProfiles)
throws InterruptedException, DeviceNotAvailableException, JSONException, IOException {
long endTime = System.currentTimeMillis() + mBtConnectionTimeout.toMillis();
while (System.currentTimeMillis() < endTime
&& !connectedProfiles.containsAll(allProfiles)) {
for (BluetoothProfile profile : allProfiles) {
if (connectedProfiles.contains(profile)) {
continue;
}
if (isProfileConnected(primary, address, profile)) {
connectedProfiles.add(profile);
}
CLog.d("Connected profiles for now: %s", connectedProfiles);
}
Thread.sleep(BT_CHECK_CONNECTION_INTERVAL_MS);
}
return connectedProfiles.containsAll(allProfiles);
}
private boolean waitForRemainingProfilesDisconnected(
ITestDevice primary,
String address,
Set<BluetoothProfile> disConnectedProfiles,
Set<BluetoothProfile> allProfiles)
throws InterruptedException, DeviceNotAvailableException, JSONException, IOException {
long endTime = System.currentTimeMillis() + mBtConnectionTimeout.toMillis();
while (System.currentTimeMillis() < endTime
&& !disConnectedProfiles.containsAll(allProfiles)) {
for (BluetoothProfile profile : allProfiles) {
if (disConnectedProfiles.contains(profile)) {
continue;
}
if (!isProfileConnected(primary, address, profile)) {
disConnectedProfiles.add(profile);
}
CLog.d("Disconnected profiles for now: %s", disConnectedProfiles);
}
Thread.sleep(BT_CHECK_CONNECTION_INTERVAL_MS);
}
return disConnectedProfiles.containsAll(allProfiles);
}
private boolean isProfileConnected(
ITestDevice primary, String address, BluetoothProfile profile)
throws DeviceNotAvailableException, IOException, JSONException {
switch (profile) {
case HEADSET_CLIENT:
return checkConnectedDevice(
primary, address, Commands.BLUETOOTH_HFP_CLIENT_GET_CONNECTED_DEVICES);
case A2DP:
return checkConnectedDevice(
primary, address, Commands.BLUETOOTH_A2DP_GET_CONNECTED_DEVICES);
case A2DP_SINK:
return checkConnectedDevice(
primary, address, Commands.BLUETOOTH_A2DP_SINK_GET_CONNECTED_DEVICES);
case PAN:
return checkConnectedDevice(
primary, address, Commands.BLUETOOTH_PAN_GET_CONNECTED_DEVICES);
case PBAP_CLIENT:
return checkConnectedDevice(
primary, address, Commands.BLUETOOTH_PBAP_CLIENT_GET_CONNECTED_DEVICES);
case MAP:
return checkConnectedDevice(
primary, address, Commands.BLUETOOTH_MAP_GET_CONNECTED_DEVICES);
case MAP_CLIENT:
return checkConnectedDevice(
primary, address, Commands.BLUETOOTH_MAP_CLIENT_GET_CONNECTED_DEVICES);
default:
CLog.e("Unsupported profile %s to check connection state", profile);
}
return false;
}
private boolean checkConnectedDevice(ITestDevice primary, String address, String sl4aCommand)
throws DeviceNotAvailableException, IOException, JSONException {
Sl4aClient primaryClient = getSl4aClient(primary);
JSONArray devices = (JSONArray) primaryClient.rpcCall(sl4aCommand);
if (devices == null) {
CLog.e("Empty response");
return false;
}
for (int i = 0; i < devices.length(); i++) {
JSONObject device = devices.getJSONObject(i);
if (device.has("address") && device.getString("address").equals(address)) {
return true;
}
}
return false;
}
private boolean isQAndAbove(ITestDevice device) throws DeviceNotAvailableException {
return device.getApiLevel() > 28;
}
}