blob: bfaf557194b8313bb2bba86eaf98a82e286cb0b2 [file] [log] [blame]
/*
* Copyright (C) 2022 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 android.bluetooth.cts;
import static android.Manifest.permission.BLUETOOTH_CONNECT;
import static android.Manifest.permission.BLUETOOTH_PRIVILEGED;
import static org.junit.Assert.assertArrayEquals;
import android.app.UiAutomation;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothManager;
import android.bluetooth.BluetoothStatusCodes;
import android.bluetooth.BluetoothUuid;
import android.bluetooth.OobData;
import android.bluetooth.le.AdvertiseSettings;
import android.bluetooth.le.AdvertisingSetParameters;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.PackageManager;
import android.os.ParcelUuid;
import android.provider.Settings;
import android.test.AndroidTestCase;
import android.util.Log;
import androidx.annotation.NonNull;
import androidx.test.InstrumentationRegistry;
import java.util.List;
import java.util.concurrent.Executor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;
public class SystemBluetoothTest extends AndroidTestCase {
private static final String TAG = SystemBluetoothTest.class.getSimpleName();
private static final long DEFAULT_DISCOVERY_TIMEOUT_MS = 12800;
private static final int DISCOVERY_START_TIMEOUT = 500;
private static final String BLE_SCAN_ALWAYS_AVAILABLE = "ble_scan_always_enabled";
private boolean mHasBluetooth;
private BluetoothAdapter mAdapter;
private UiAutomation mUiAutomation;
private ReentrantLock mDiscoveryStartedLock;
private Condition mConditionDiscoveryStarted;
private boolean mIsDiscoveryStarted;
@Override
public void setUp() throws Exception {
super.setUp();
mHasBluetooth = getContext().getPackageManager().hasSystemFeature(
PackageManager.FEATURE_BLUETOOTH);
if (!mHasBluetooth) return;
mUiAutomation = InstrumentationRegistry.getInstrumentation().getUiAutomation();
mUiAutomation.adoptShellPermissionIdentity(BLUETOOTH_CONNECT, BLUETOOTH_PRIVILEGED);
BluetoothManager manager = getContext().getSystemService(BluetoothManager.class);
mAdapter = manager.getAdapter();
assertTrue(BTAdapterUtils.disableAdapter(mAdapter, mContext));
}
@Override
public void tearDown() throws Exception {
super.tearDown();
}
/**
* Test enable/disable silence mode and check whether the device is in correct state.
*/
public void testSilenceMode() {
if (!mHasBluetooth) {
return;
}
assertTrue(BTAdapterUtils.enableAdapter(mAdapter, mContext));
BluetoothDevice device = mAdapter.getRemoteDevice("00:11:22:AA:BB:CC");
assertTrue(device.setSilenceMode(true));
assertFalse(device.isInSilenceMode());
assertTrue(device.setSilenceMode(false));
assertFalse(device.isInSilenceMode());
}
/**
* Test whether the metadata would be stored in Bluetooth storage successfully,
* also test whether OnMetadataChangedListener would callback correct values when
* metadata is changed..
*/
public void testSetGetMetadata() {
if (!mHasBluetooth) {
return;
}
assertTrue(BTAdapterUtils.enableAdapter(mAdapter, mContext));
byte[] testByteData = "Test Data".getBytes();
BluetoothDevice device = mAdapter.getRemoteDevice("00:11:22:AA:BB:CC");
BluetoothAdapter.OnMetadataChangedListener listener =
new BluetoothAdapter.OnMetadataChangedListener() {
@Override
public void onMetadataChanged(BluetoothDevice dev, int key, byte[] value) {
assertEquals(dev, device);
assertEquals(key, BluetoothDevice.METADATA_MANUFACTURER_NAME);
assertArrayEquals(value, testByteData);
}
};
BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
assertTrue(adapter.addOnMetadataChangedListener(device,
getContext().getMainExecutor(), listener));
assertTrue(device.setMetadata(
BluetoothDevice.METADATA_MANUFACTURER_NAME, testByteData));
assertArrayEquals(device.getMetadata(
BluetoothDevice.METADATA_MANUFACTURER_NAME), testByteData);
assertTrue(adapter.removeOnMetadataChangedListener(device, listener));
}
public void testDiscoveryEndMillis() {
if (!mHasBluetooth) {
return;
}
assertTrue(BTAdapterUtils.enableAdapter(mAdapter, mContext));
mDiscoveryStartedLock = new ReentrantLock();
mConditionDiscoveryStarted = mDiscoveryStartedLock.newCondition();
IntentFilter filter = new IntentFilter();
filter.addAction(BluetoothAdapter.ACTION_DISCOVERY_STARTED);
filter.setPriority(IntentFilter.SYSTEM_HIGH_PRIORITY);
mContext.registerReceiver(mDiscoveryStartedReceiver, filter);
mAdapter.startDiscovery();
assertTrue(waitForDiscoveryStart());
long discoveryEndTime = mAdapter.getDiscoveryEndMillis();
long currentTime = System.currentTimeMillis();
assertTrue(discoveryEndTime > currentTime);
assertTrue(discoveryEndTime - currentTime < DEFAULT_DISCOVERY_TIMEOUT_MS);
mContext.unregisterReceiver(mDiscoveryStartedReceiver);
}
/**
* Tests whether the static function BluetoothUuid#containsAnyUuid properly identifies whether
* the ParcelUuid arrays have at least one common element.
*/
public void testContainsAnyUuid() {
if (!mHasBluetooth) {
return;
}
assertTrue(BTAdapterUtils.enableAdapter(mAdapter, mContext));
ParcelUuid[] deviceAUuids = new ParcelUuid[]{BluetoothUuid.A2DP_SOURCE, BluetoothUuid.HFP,
BluetoothUuid.ADV_AUDIO_DIST, BluetoothUuid.AVRCP_CONTROLLER,
BluetoothUuid.BASE_UUID, BluetoothUuid.HID, BluetoothUuid.HEARING_AID};
ParcelUuid[] deviceBUuids = new ParcelUuid[]{BluetoothUuid.A2DP_SINK, BluetoothUuid.BNEP,
BluetoothUuid.AVRCP_TARGET, BluetoothUuid.HFP_AG,
BluetoothUuid.HOGP, BluetoothUuid.HSP_AG};
ParcelUuid[] deviceCUuids = new ParcelUuid[]{BluetoothUuid.HSP, BluetoothUuid.MAP,
BluetoothUuid.MAS, BluetoothUuid.MNS, BluetoothUuid.NAP,
BluetoothUuid.OBEX_OBJECT_PUSH, BluetoothUuid.PANU, BluetoothUuid.PBAP_PCE,
BluetoothUuid.PBAP_PSE, BluetoothUuid.SAP, BluetoothUuid.A2DP_SOURCE};
assertTrue(BluetoothUuid.containsAnyUuid(null, null));
assertTrue(BluetoothUuid.containsAnyUuid(new ParcelUuid[]{}, null));
assertTrue(BluetoothUuid.containsAnyUuid(null, new ParcelUuid[]{}));
assertFalse(BluetoothUuid.containsAnyUuid(null, deviceAUuids));
assertFalse(BluetoothUuid.containsAnyUuid(deviceAUuids, null));
assertFalse(BluetoothUuid.containsAnyUuid(deviceAUuids, deviceBUuids));
assertTrue(BluetoothUuid.containsAnyUuid(deviceAUuids, deviceCUuids));
assertTrue(BluetoothUuid.containsAnyUuid(deviceBUuids, deviceBUuids));
}
public void testParseUuidFrom() {
if (!mHasBluetooth) {
return;
}
byte[] uuid16 = new byte[]{0x0B, 0x11};
assertEquals(BluetoothUuid.A2DP_SINK, BluetoothUuid.parseUuidFrom(uuid16));
byte[] uuid32 = new byte[]{(byte) 0xF0, (byte) 0xFD, 0x00, 0x00};
assertEquals(BluetoothUuid.HEARING_AID, BluetoothUuid.parseUuidFrom(uuid32));
byte[] uuid128 = new byte[]{(byte) 0xFB, 0x34, (byte) 0x9B, 0x5F, (byte) 0x80, 0x00, 0x00,
(byte) 0x80, 0x00, 0x10, 0x00, 0x00, 0x1F, 0x11, 0x00, 0x00};
assertEquals(BluetoothUuid.HFP_AG, BluetoothUuid.parseUuidFrom(uuid128));
}
public void testCanBondWithoutDialog() {
if (!mHasBluetooth) {
return;
}
assertTrue(BTAdapterUtils.enableAdapter(mAdapter, mContext));
// Verify the method returns false on a device that doesn't meet the criteria
BluetoothDevice testDevice = mAdapter.getRemoteDevice("00:11:22:AA:BB:CC");
assertFalse(testDevice.canBondWithoutDialog());
}
public void testBleOnlyMode() {
if (!mHasBluetooth) {
return;
}
int originalScanAlwaysAvailableValue = 0;
try {
originalScanAlwaysAvailableValue = Settings.Global.getInt(mContext.getContentResolver(),
BLE_SCAN_ALWAYS_AVAILABLE);
} catch (Settings.SettingNotFoundException e) { // Uses 0 or not available as original
}
assertTrue(BTAdapterUtils.enableAdapter(mAdapter, mContext));
// Allows BLE scanning to be performed even if the adapter is off
Settings.Global.putInt(mContext.getContentResolver(), BLE_SCAN_ALWAYS_AVAILABLE, 1);
assertTrue(BTAdapterUtils.disableAdapter(mAdapter, mContext));
assertFalse(mAdapter.isEnabled());
assertTrue(BTAdapterUtils.enableBLE(mAdapter, mContext));
assertTrue(BTAdapterUtils.disableBLE(mAdapter, mContext));
Settings.Global.putInt(mContext.getContentResolver(), BLE_SCAN_ALWAYS_AVAILABLE,
originalScanAlwaysAvailableValue);
}
public void testSetGetOwnAddressType() {
if (!mHasBluetooth) {
return;
}
AdvertisingSetParameters.Builder paramsBuilder = new AdvertisingSetParameters.Builder();
assertEquals(paramsBuilder,
paramsBuilder.setOwnAddressType(AdvertisingSetParameters.ADDRESS_TYPE_DEFAULT));
assertEquals(AdvertisingSetParameters.ADDRESS_TYPE_DEFAULT,
paramsBuilder.build().getOwnAddressType());
assertEquals(paramsBuilder,
paramsBuilder.setOwnAddressType(AdvertisingSetParameters.ADDRESS_TYPE_PUBLIC));
assertEquals(AdvertisingSetParameters.ADDRESS_TYPE_PUBLIC,
paramsBuilder.build().getOwnAddressType());
assertEquals(paramsBuilder,
paramsBuilder.setOwnAddressType(AdvertisingSetParameters.ADDRESS_TYPE_RANDOM));
assertEquals(AdvertisingSetParameters.ADDRESS_TYPE_RANDOM,
paramsBuilder.build().getOwnAddressType());
AdvertiseSettings.Builder settingsBuilder = new AdvertiseSettings.Builder();
assertEquals(settingsBuilder,
settingsBuilder.setOwnAddressType(AdvertisingSetParameters.ADDRESS_TYPE_DEFAULT));
assertEquals(AdvertisingSetParameters.ADDRESS_TYPE_DEFAULT,
settingsBuilder.build().getOwnAddressType());
assertEquals(settingsBuilder,
settingsBuilder.setOwnAddressType(AdvertisingSetParameters.ADDRESS_TYPE_PUBLIC));
assertEquals(AdvertisingSetParameters.ADDRESS_TYPE_PUBLIC,
settingsBuilder.build().getOwnAddressType());
assertEquals(settingsBuilder,
settingsBuilder.setOwnAddressType(AdvertisingSetParameters.ADDRESS_TYPE_RANDOM));
assertEquals(AdvertisingSetParameters.ADDRESS_TYPE_RANDOM,
settingsBuilder.build().getOwnAddressType());
}
public void testGetSupportedProfiles() {
assertTrue(BTAdapterUtils.enableAdapter(mAdapter, mContext));
List<Integer> profiles = mAdapter.getSupportedProfiles();
assertNotNull(profiles);
}
public void testEnableNoAutoConnect() {
if (!mHasBluetooth) {
return;
}
// Assert that when Bluetooth is already enabled, the method immediately returns true
assertTrue(BTAdapterUtils.enableAdapter(mAdapter, mContext));
assertTrue(mAdapter.enableNoAutoConnect());
}
private boolean isBluetoothPersistedOff() {
// A value of "0" in Settings.Global.BLUETOOTH_ON means the OFF state was persisted
return (Settings.Global.getInt(
mContext.getContentResolver(), Settings.Global.BLUETOOTH_ON, -1) == 0);
}
public void testDisableBluetoothPersistFalse() {
if (!mHasBluetooth) {
return;
}
assertTrue(BTAdapterUtils.enableAdapter(mAdapter, mContext));
assertTrue(BTAdapterUtils.disableAdapter(mAdapter, /* persist= */ false, mContext));
assertFalse(isBluetoothPersistedOff());
}
public void testDisableBluetoothPersistTrue() {
if (!mHasBluetooth) {
return;
}
assertTrue(BTAdapterUtils.enableAdapter(mAdapter, mContext));
assertTrue(BTAdapterUtils.disableAdapter(mAdapter, /* persist= */ true, mContext));
assertTrue(isBluetoothPersistedOff());
}
public void testSetLowLatencyAudioAllowed() {
if (!mHasBluetooth) {
return;
}
BluetoothDevice device = mAdapter.getRemoteDevice("00:11:22:AA:BB:CC");
assertTrue(BTAdapterUtils.disableAdapter(mAdapter, mContext));
assertFalse(device.setLowLatencyAudioAllowed(true));
assertFalse(device.setLowLatencyAudioAllowed(false));
assertTrue(BTAdapterUtils.enableAdapter(mAdapter, mContext));
assertTrue(device.setLowLatencyAudioAllowed(true));
assertTrue(device.setLowLatencyAudioAllowed(false));
}
public void testGenerateLocalOobData() {
if (!mHasBluetooth) {
return;
}
Executor executor = new Executor() {
@Override
public void execute(Runnable command) {
}
};
BluetoothAdapter.OobDataCallback callback = new BluetoothAdapter.OobDataCallback() {
@Override
public void onOobData(int transport, @NonNull OobData oobData) {
fail("Should have failed to generate local oob data as Bluetooth is disabled");
}
@Override
public void onError(int errorCode) {
}
};
try {
mAdapter.generateLocalOobData(BluetoothDevice.TRANSPORT_AUTO, executor, callback);
fail("generateLocalOobData should throw an IllegalArgumentException due to invalid "
+ "transport");
} catch (IllegalArgumentException ignored) {
}
try {
mAdapter.generateLocalOobData(BluetoothDevice.TRANSPORT_BREDR, executor, null);
fail("generateLocalOobData should throw a NullPointerException due to passing a null "
+ "callback");
} catch (NullPointerException ignored) {
}
mAdapter.generateLocalOobData(BluetoothDevice.TRANSPORT_BREDR, executor, callback);
}
public void testSetScanMode() {
if (!mHasBluetooth) {
return;
}
assertEquals(BluetoothStatusCodes.ERROR_BLUETOOTH_NOT_ENABLED,
mAdapter.setScanMode(BluetoothAdapter.SCAN_MODE_CONNECTABLE));
assertTrue(BTAdapterUtils.enableAdapter(mAdapter, mContext));
try {
mAdapter.setScanMode(0);
fail("Invalid scan mode");
} catch (IllegalArgumentException ignored) {
}
/* TODO(rahulsabnis): Fix the callback system so these work as intended
assertEquals(BluetoothStatusCodes.SUCCESS,
mAdapter.setScanMode(BluetoothAdapter.SCAN_MODE_NONE));
assertEquals(BluetoothAdapter.SCAN_MODE_NONE, mAdapter.getScanMode());
assertEquals(BluetoothStatusCodes.SUCCESS,
mAdapter.setScanMode(BluetoothAdapter.SCAN_MODE_CONNECTABLE));
assertEquals(BluetoothAdapter.SCAN_MODE_CONNECTABLE, mAdapter.getScanMode());
assertEquals(BluetoothStatusCodes.SUCCESS,
mAdapter.setDiscoverableTimeout(Duration.ofSeconds(1)));
assertEquals(BluetoothStatusCodes.SUCCESS,
mAdapter.setScanMode(BluetoothAdapter.SCAN_MODE_CONNECTABLE_DISCOVERABLE));
assertEquals(BluetoothAdapter.SCAN_MODE_CONNECTABLE_DISCOVERABLE, mAdapter.getScanMode());
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
assertEquals(BluetoothAdapter.SCAN_MODE_CONNECTABLE, mAdapter.getScanMode());
*/
}
private boolean waitForDiscoveryStart() {
mDiscoveryStartedLock.lock();
try {
// Wait for discovery to be started
while (!mIsDiscoveryStarted) {
if (!mConditionDiscoveryStarted.await(
DISCOVERY_START_TIMEOUT, TimeUnit.MILLISECONDS)) {
Log.e(TAG, "Timeout while waiting for discovery to start");
break;
}
}
} catch (InterruptedException e) {
Log.e(TAG, "waitForDiscoveryStart: interrrupted");
} finally {
mDiscoveryStartedLock.unlock();
}
return mIsDiscoveryStarted;
}
private final BroadcastReceiver mDiscoveryStartedReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
if (action.equals(BluetoothAdapter.ACTION_DISCOVERY_STARTED)) {
Log.i(TAG, "Discovery started");
mDiscoveryStartedLock.lock();
mIsDiscoveryStarted = true;
try {
mConditionDiscoveryStarted.signal();
} catch (IllegalMonitorStateException ex) {
} finally {
mDiscoveryStartedLock.unlock();
}
}
}
};
}