Make BLE CTS tests beacon-agnostic.
Also only run BLE tests on BLE enabled devices.
Bug:20224949
Bug:23218008
Change-Id: I78ec66fa7afcbebb05aa61007d2c400790408f24
diff --git a/tests/tests/bluetooth/AndroidManifest.xml b/tests/tests/bluetooth/AndroidManifest.xml
index c9ad122..12838f3 100644
--- a/tests/tests/bluetooth/AndroidManifest.xml
+++ b/tests/tests/bluetooth/AndroidManifest.xml
@@ -19,6 +19,7 @@
<uses-permission android:name="android.permission.BLUETOOTH" />
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
+ <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<uses-permission android:name="android.permission.DISABLE_KEYGUARD" />
<application>
diff --git a/tests/tests/bluetooth/src/android/bluetooth/cts/BluetoothLeScanTest.java b/tests/tests/bluetooth/src/android/bluetooth/cts/BluetoothLeScanTest.java
index 08bbacd..a79df42 100644
--- a/tests/tests/bluetooth/src/android/bluetooth/cts/BluetoothLeScanTest.java
+++ b/tests/tests/bluetooth/src/android/bluetooth/cts/BluetoothLeScanTest.java
@@ -21,28 +21,33 @@
import android.bluetooth.le.BluetoothLeScanner;
import android.bluetooth.le.ScanCallback;
import android.bluetooth.le.ScanFilter;
+import android.bluetooth.le.ScanRecord;
import android.bluetooth.le.ScanResult;
import android.bluetooth.le.ScanSettings;
import android.content.Context;
+import android.content.pm.PackageManager;
+import android.os.ParcelUuid;
import android.os.SystemClock;
import android.test.AndroidTestCase;
import android.test.suitebuilder.annotation.MediumTest;
import android.util.Log;
+import android.util.SparseArray;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
+import java.util.Comparator;
import java.util.HashSet;
import java.util.List;
+import java.util.Map;
import java.util.Set;
import java.util.concurrent.TimeUnit;
/**
* Test cases for Bluetooth LE scans.
* <p>
- * To run the test, the device must be placed in an environment that has at least 5 BLE beacons
- * broadcasting no slower than 1 HZ. The BLE beacons should be a combination of iBeacon devices and
- * non-iBeacon devices.
+ * To run the test, the device must be placed in an environment that has at least 3 beacons, all
+ * placed less than 5 meters away from the DUT.
* <p>
* Run 'run cts --class android.bluetooth.cts.BluetoothLeScanTest' in cts-tradefed to run the test
* cases.
@@ -51,25 +56,26 @@
private static final String TAG = "BluetoothLeScanTest";
- private static final ScanFilter MANUFACTURER_DATA_FILTER =
- new ScanFilter.Builder().setManufacturerData(0x004C, new byte[0], new byte[0]).build();
private static final int SCAN_DURATION_MILLIS = 5000;
- private static final int BATCH_SCAN_REPORT_DELAY_MILLIS = 10000;
+ private static final int BATCH_SCAN_REPORT_DELAY_MILLIS = 20000;
+ private BluetoothAdapter mBluetoothAdapter;
private BluetoothLeScanner mScanner;
@Override
public void setUp() {
+ if (!isBleSupported())
+ return;
BluetoothManager manager = (BluetoothManager) mContext.getSystemService(
Context.BLUETOOTH_SERVICE);
- BluetoothAdapter adapter = manager.getAdapter();
- if (!adapter.isEnabled()) {
- adapter.enable();
+ mBluetoothAdapter = manager.getAdapter();
+ if (!mBluetoothAdapter.isEnabled()) {
// Note it's not reliable to listen for Adapter.ACTION_STATE_CHANGED broadcast and check
// bluetooth state.
- sleep(2000);
+ mBluetoothAdapter.enable();
+ sleep(3000);
}
- mScanner = adapter.getBluetoothLeScanner();
+ mScanner = mBluetoothAdapter.getBluetoothLeScanner();
}
/**
@@ -77,13 +83,11 @@
*/
@MediumTest
public void testBasicBleScan() {
- BleScanCallback regularLeScanCallback = new BleScanCallback();
+ if (!isBleSupported())
+ return;
long scanStartMillis = SystemClock.elapsedRealtime();
- mScanner.startScan(regularLeScanCallback);
- sleep(SCAN_DURATION_MILLIS);
- mScanner.stopScan(regularLeScanCallback);
+ Collection<ScanResult> scanResults = scan();
long scanEndMillis = SystemClock.elapsedRealtime();
- Collection<ScanResult> scanResults = regularLeScanCallback.getScanResults();
assertTrue("Scan results shouldn't be empty", !scanResults.isEmpty());
verifyTimestamp(scanResults, scanStartMillis, scanEndMillis);
}
@@ -94,8 +98,16 @@
*/
@MediumTest
public void testScanFilter() {
+ if (!isBleSupported())
+ return;
+
List<ScanFilter> filters = new ArrayList<ScanFilter>();
- filters.add(MANUFACTURER_DATA_FILTER);
+ ScanFilter filter = createScanFilter();
+ if (filter == null) {
+ Log.d(TAG, "no appropriate filter can be set");
+ return;
+ }
+ filters.add(filter);
BleScanCallback filterLeScanCallback = new BleScanCallback();
ScanSettings settings = new ScanSettings.Builder().setScanMode(
@@ -103,22 +115,54 @@
mScanner.startScan(filters, settings, filterLeScanCallback);
sleep(SCAN_DURATION_MILLIS);
mScanner.stopScan(filterLeScanCallback);
+ sleep(1000);
Collection<ScanResult> scanResults = filterLeScanCallback.getScanResults();
- assertTrue("No scan results", !scanResults.isEmpty());
for (ScanResult result : scanResults) {
- assertTrue(MANUFACTURER_DATA_FILTER.matches(result));
+ assertTrue(filter.matches(result));
}
}
+ // Create a scan filter based on the nearby beacon with highest signal strength.
+ private ScanFilter createScanFilter() {
+ // Get a list of nearby beacons.
+ List<ScanResult> scanResults = new ArrayList<ScanResult>(scan());
+ assertTrue("Scan results shouldn't be empty", !scanResults.isEmpty());
+ // Find the beacon with strongest signal strength, which is the target device for filter
+ // scan.
+ Collections.sort(scanResults, new RssiComparator());
+ ScanResult result = scanResults.get(0);
+ ScanRecord record = result.getScanRecord();
+ if (record == null) {
+ return null;
+ }
+ Map<ParcelUuid, byte[]> serviceData = record.getServiceData();
+ if (serviceData != null && !serviceData.isEmpty()) {
+ ParcelUuid uuid = serviceData.keySet().iterator().next();
+ return new ScanFilter.Builder().setServiceData(uuid, new byte[] { 0 },
+ new byte[] { 0 }).build();
+ }
+ SparseArray<byte[]> manufacturerSpecificData = record.getManufacturerSpecificData();
+ if (manufacturerSpecificData != null && manufacturerSpecificData.size() > 0) {
+ return new ScanFilter.Builder().setManufacturerData(manufacturerSpecificData.keyAt(0),
+ new byte[] { 0 }, new byte[] { 0 }).build();
+ }
+ List<ParcelUuid> serviceUuids = record.getServiceUuids();
+ if (serviceUuids != null && !serviceUuids.isEmpty()) {
+ return new ScanFilter.Builder().setServiceUuid(serviceUuids.get(0)).build();
+ }
+ return null;
+ }
+
/**
* Test of opportunistic BLE scans.
*/
@MediumTest
public void testOpportunisticScan() {
- ScanSettings opportunisticScanSettings =
- new ScanSettings.Builder()
- .setScanMode(-1) // TODO: use constants in ScanSettings once it's unhiden.
- .build();
+ if (!isBleSupported())
+ return;
+ ScanSettings opportunisticScanSettings = new ScanSettings.Builder()
+ .setScanMode(ScanSettings.SCAN_MODE_OPPORTUNISTIC)
+ .build();
BleScanCallback emptyScanCallback = new BleScanCallback();
// No scans are really started with opportunistic scans only.
@@ -128,18 +172,20 @@
assertTrue(emptyScanCallback.getScanResults().isEmpty());
BleScanCallback regularScanCallback = new BleScanCallback();
- ScanSettings regularScanSettings =
- new ScanSettings.Builder().setScanMode(ScanSettings.SCAN_MODE_LOW_LATENCY).build();
+ ScanSettings regularScanSettings = new ScanSettings.Builder()
+ .setScanMode(ScanSettings.SCAN_MODE_LOW_LATENCY).build();
List<ScanFilter> filters = new ArrayList<>();
- filters.add(MANUFACTURER_DATA_FILTER);
+ ScanFilter filter = createScanFilter();
+ if (filter != null) {
+ filters.add(filter);
+ } else {
+ Log.d(TAG, "no appropriate filter can be set");
+ }
mScanner.startScan(filters, regularScanSettings, regularScanCallback);
sleep(SCAN_DURATION_MILLIS);
// With normal BLE scan client, opportunistic scan client will get scan results.
assertTrue("opportunistic scan results shouldn't be empty",
!emptyScanCallback.getScanResults().isEmpty());
- assertTrue("opportunistic scan should see more results",
- emptyScanCallback.getScanResults().size() >
- regularScanCallback.getScanResults().size());
// No more scan results for opportunistic scan clients once the normal BLE scan clients
// stops.
@@ -157,20 +203,24 @@
*/
@MediumTest
public void testBatchScan() {
+ if (!isBleSupported() || !isBleBatchScanSupported()) {
+ Log.d(TAG, "BLE or BLE batching not suppported");
+ return;
+ }
ScanSettings batchScanSettings = new ScanSettings.Builder()
.setScanMode(ScanSettings.SCAN_MODE_LOW_LATENCY)
.setReportDelay(BATCH_SCAN_REPORT_DELAY_MILLIS).build();
BleScanCallback batchScanCallback = new BleScanCallback();
- long scanStartMillis = SystemClock.elapsedRealtime();
mScanner.startScan(Collections.<ScanFilter> emptyList(), batchScanSettings,
batchScanCallback);
sleep(SCAN_DURATION_MILLIS);
mScanner.flushPendingScanResults(batchScanCallback);
sleep(1000);
- long scanEndMillis = SystemClock.elapsedRealtime();
List<ScanResult> results = batchScanCallback.getBatchScanResults();
assertTrue(!results.isEmpty());
- verifyTimestamp(results, scanStartMillis, scanEndMillis);
+ long scanEndMillis = SystemClock.elapsedRealtime();
+ mScanner.stopScan(batchScanCallback);
+ verifyTimestamp(results, 0, scanEndMillis);
}
// Verify timestamp of all scan results are within [scanStartMillis, scanEndMillis].
@@ -178,8 +228,10 @@
long scanEndMillis) {
for (ScanResult result : results) {
long timestampMillis = TimeUnit.NANOSECONDS.toMillis(result.getTimestampNanos());
- assertTrue("Invalid timestamp", timestampMillis >= scanStartMillis);
- assertTrue("Invalid timestamp", timestampMillis <= scanEndMillis);
+ assertTrue("Invalid timestamp: " + timestampMillis + " should be >= " + scanStartMillis,
+ timestampMillis >= scanStartMillis);
+ assertTrue("Invalid timestamp: " + timestampMillis + " should be <= " + scanEndMillis,
+ timestampMillis <= scanEndMillis);
}
}
@@ -207,8 +259,8 @@
}
// Return regular BLE scan results accumulated so far.
- synchronized Collection<ScanResult> getScanResults() {
- return Collections.unmodifiableCollection(mResults);
+ synchronized Set<ScanResult> getScanResults() {
+ return Collections.unmodifiableSet(mResults);
}
// Return batch scan results.
@@ -217,6 +269,25 @@
}
}
+ private class RssiComparator implements Comparator<ScanResult> {
+
+ @Override
+ public int compare(ScanResult lhs, ScanResult rhs) {
+ return rhs.getRssi() - lhs.getRssi();
+ }
+
+ }
+
+ // Perform a BLE scan to get results of nearby BLE devices.
+ private Set<ScanResult> scan() {
+ BleScanCallback regularLeScanCallback = new BleScanCallback();
+ mScanner.startScan(regularLeScanCallback);
+ sleep(SCAN_DURATION_MILLIS);
+ mScanner.stopScan(regularLeScanCallback);
+ sleep(1000);
+ return regularLeScanCallback.getScanResults();
+ }
+
// Put the current thread to sleep.
private void sleep(int sleepMillis) {
try {
@@ -226,4 +297,15 @@
}
}
+ // Check if Bluetooth LE feature is supported on DUT.
+ private boolean isBleSupported() {
+ return getContext().getPackageManager()
+ .hasSystemFeature(PackageManager.FEATURE_BLUETOOTH_LE);
+ }
+
+ // Returns whether offloaded scan batching is supported.
+ private boolean isBleBatchScanSupported() {
+ return mBluetoothAdapter.isOffloadedScanBatchingSupported();
+ }
+
}