Wei Wang | 0b2b4b6 | 2015-01-15 12:07:37 -0800 | [diff] [blame] | 1 | /* |
| 2 | * Copyright (C) 2015 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.cts; |
| 18 | |
| 19 | import android.bluetooth.BluetoothAdapter; |
| 20 | import android.bluetooth.BluetoothManager; |
| 21 | import android.bluetooth.le.BluetoothLeScanner; |
| 22 | import android.bluetooth.le.ScanCallback; |
| 23 | import android.bluetooth.le.ScanFilter; |
Wei Wang | 4475498 | 2015-08-12 12:12:02 -0700 | [diff] [blame] | 24 | import android.bluetooth.le.ScanRecord; |
Wei Wang | 0b2b4b6 | 2015-01-15 12:07:37 -0800 | [diff] [blame] | 25 | import android.bluetooth.le.ScanResult; |
| 26 | import android.bluetooth.le.ScanSettings; |
| 27 | import android.content.Context; |
Wei Wang | 4475498 | 2015-08-12 12:12:02 -0700 | [diff] [blame] | 28 | import android.content.pm.PackageManager; |
| 29 | import android.os.ParcelUuid; |
Wei Wang | 0b2b4b6 | 2015-01-15 12:07:37 -0800 | [diff] [blame] | 30 | import android.os.SystemClock; |
| 31 | import android.test.AndroidTestCase; |
| 32 | import android.test.suitebuilder.annotation.MediumTest; |
| 33 | import android.util.Log; |
Wei Wang | 4475498 | 2015-08-12 12:12:02 -0700 | [diff] [blame] | 34 | import android.util.SparseArray; |
Wei Wang | 0b2b4b6 | 2015-01-15 12:07:37 -0800 | [diff] [blame] | 35 | |
| 36 | import java.util.ArrayList; |
| 37 | import java.util.Collection; |
| 38 | import java.util.Collections; |
Wei Wang | 4475498 | 2015-08-12 12:12:02 -0700 | [diff] [blame] | 39 | import java.util.Comparator; |
Wei Wang | 0b2b4b6 | 2015-01-15 12:07:37 -0800 | [diff] [blame] | 40 | import java.util.HashSet; |
| 41 | import java.util.List; |
Wei Wang | 4475498 | 2015-08-12 12:12:02 -0700 | [diff] [blame] | 42 | import java.util.Map; |
Wei Wang | 0b2b4b6 | 2015-01-15 12:07:37 -0800 | [diff] [blame] | 43 | import java.util.Set; |
| 44 | import java.util.concurrent.TimeUnit; |
| 45 | |
| 46 | /** |
| 47 | * Test cases for Bluetooth LE scans. |
| 48 | * <p> |
Wei Wang | 4475498 | 2015-08-12 12:12:02 -0700 | [diff] [blame] | 49 | * To run the test, the device must be placed in an environment that has at least 3 beacons, all |
| 50 | * placed less than 5 meters away from the DUT. |
Wei Wang | 0b2b4b6 | 2015-01-15 12:07:37 -0800 | [diff] [blame] | 51 | * <p> |
| 52 | * Run 'run cts --class android.bluetooth.cts.BluetoothLeScanTest' in cts-tradefed to run the test |
| 53 | * cases. |
| 54 | */ |
| 55 | public class BluetoothLeScanTest extends AndroidTestCase { |
| 56 | |
| 57 | private static final String TAG = "BluetoothLeScanTest"; |
| 58 | |
Wei Wang | 0b2b4b6 | 2015-01-15 12:07:37 -0800 | [diff] [blame] | 59 | private static final int SCAN_DURATION_MILLIS = 5000; |
Wei Wang | 4475498 | 2015-08-12 12:12:02 -0700 | [diff] [blame] | 60 | private static final int BATCH_SCAN_REPORT_DELAY_MILLIS = 20000; |
Wei Wang | 0b2b4b6 | 2015-01-15 12:07:37 -0800 | [diff] [blame] | 61 | |
Wei Wang | 4475498 | 2015-08-12 12:12:02 -0700 | [diff] [blame] | 62 | private BluetoothAdapter mBluetoothAdapter; |
Wei Wang | 0b2b4b6 | 2015-01-15 12:07:37 -0800 | [diff] [blame] | 63 | private BluetoothLeScanner mScanner; |
| 64 | |
| 65 | @Override |
| 66 | public void setUp() { |
Wei Wang | 4475498 | 2015-08-12 12:12:02 -0700 | [diff] [blame] | 67 | if (!isBleSupported()) |
| 68 | return; |
Wei Wang | 0b2b4b6 | 2015-01-15 12:07:37 -0800 | [diff] [blame] | 69 | BluetoothManager manager = (BluetoothManager) mContext.getSystemService( |
| 70 | Context.BLUETOOTH_SERVICE); |
Wei Wang | 4475498 | 2015-08-12 12:12:02 -0700 | [diff] [blame] | 71 | mBluetoothAdapter = manager.getAdapter(); |
| 72 | if (!mBluetoothAdapter.isEnabled()) { |
Wei Wang | 0b2b4b6 | 2015-01-15 12:07:37 -0800 | [diff] [blame] | 73 | // Note it's not reliable to listen for Adapter.ACTION_STATE_CHANGED broadcast and check |
| 74 | // bluetooth state. |
Wei Wang | 4475498 | 2015-08-12 12:12:02 -0700 | [diff] [blame] | 75 | mBluetoothAdapter.enable(); |
| 76 | sleep(3000); |
Wei Wang | 0b2b4b6 | 2015-01-15 12:07:37 -0800 | [diff] [blame] | 77 | } |
Wei Wang | 4475498 | 2015-08-12 12:12:02 -0700 | [diff] [blame] | 78 | mScanner = mBluetoothAdapter.getBluetoothLeScanner(); |
Wei Wang | 0b2b4b6 | 2015-01-15 12:07:37 -0800 | [diff] [blame] | 79 | } |
| 80 | |
| 81 | /** |
| 82 | * Basic test case for BLE scans. Checks BLE scan timestamp is within correct range. |
| 83 | */ |
| 84 | @MediumTest |
| 85 | public void testBasicBleScan() { |
Wei Wang | 4475498 | 2015-08-12 12:12:02 -0700 | [diff] [blame] | 86 | if (!isBleSupported()) |
| 87 | return; |
Wei Wang | 0b2b4b6 | 2015-01-15 12:07:37 -0800 | [diff] [blame] | 88 | long scanStartMillis = SystemClock.elapsedRealtime(); |
Wei Wang | 4475498 | 2015-08-12 12:12:02 -0700 | [diff] [blame] | 89 | Collection<ScanResult> scanResults = scan(); |
Wei Wang | 0b2b4b6 | 2015-01-15 12:07:37 -0800 | [diff] [blame] | 90 | long scanEndMillis = SystemClock.elapsedRealtime(); |
Wei Wang | 0b2b4b6 | 2015-01-15 12:07:37 -0800 | [diff] [blame] | 91 | assertTrue("Scan results shouldn't be empty", !scanResults.isEmpty()); |
| 92 | verifyTimestamp(scanResults, scanStartMillis, scanEndMillis); |
| 93 | } |
| 94 | |
| 95 | /** |
| 96 | * Test of scan filters. Ensures only beacons matching certain type of scan filters were |
| 97 | * reported. |
| 98 | */ |
| 99 | @MediumTest |
| 100 | public void testScanFilter() { |
Wei Wang | 4475498 | 2015-08-12 12:12:02 -0700 | [diff] [blame] | 101 | if (!isBleSupported()) |
| 102 | return; |
| 103 | |
Wei Wang | 0b2b4b6 | 2015-01-15 12:07:37 -0800 | [diff] [blame] | 104 | List<ScanFilter> filters = new ArrayList<ScanFilter>(); |
Wei Wang | 4475498 | 2015-08-12 12:12:02 -0700 | [diff] [blame] | 105 | ScanFilter filter = createScanFilter(); |
| 106 | if (filter == null) { |
| 107 | Log.d(TAG, "no appropriate filter can be set"); |
| 108 | return; |
| 109 | } |
| 110 | filters.add(filter); |
Wei Wang | 0b2b4b6 | 2015-01-15 12:07:37 -0800 | [diff] [blame] | 111 | |
| 112 | BleScanCallback filterLeScanCallback = new BleScanCallback(); |
| 113 | ScanSettings settings = new ScanSettings.Builder().setScanMode( |
| 114 | ScanSettings.SCAN_MODE_LOW_LATENCY).build(); |
| 115 | mScanner.startScan(filters, settings, filterLeScanCallback); |
| 116 | sleep(SCAN_DURATION_MILLIS); |
| 117 | mScanner.stopScan(filterLeScanCallback); |
Wei Wang | 4475498 | 2015-08-12 12:12:02 -0700 | [diff] [blame] | 118 | sleep(1000); |
Wei Wang | 0b2b4b6 | 2015-01-15 12:07:37 -0800 | [diff] [blame] | 119 | Collection<ScanResult> scanResults = filterLeScanCallback.getScanResults(); |
Wei Wang | 0b2b4b6 | 2015-01-15 12:07:37 -0800 | [diff] [blame] | 120 | for (ScanResult result : scanResults) { |
Wei Wang | 4475498 | 2015-08-12 12:12:02 -0700 | [diff] [blame] | 121 | assertTrue(filter.matches(result)); |
Wei Wang | 0b2b4b6 | 2015-01-15 12:07:37 -0800 | [diff] [blame] | 122 | } |
| 123 | } |
| 124 | |
Wei Wang | 4475498 | 2015-08-12 12:12:02 -0700 | [diff] [blame] | 125 | // Create a scan filter based on the nearby beacon with highest signal strength. |
| 126 | private ScanFilter createScanFilter() { |
| 127 | // Get a list of nearby beacons. |
| 128 | List<ScanResult> scanResults = new ArrayList<ScanResult>(scan()); |
| 129 | assertTrue("Scan results shouldn't be empty", !scanResults.isEmpty()); |
| 130 | // Find the beacon with strongest signal strength, which is the target device for filter |
| 131 | // scan. |
| 132 | Collections.sort(scanResults, new RssiComparator()); |
| 133 | ScanResult result = scanResults.get(0); |
| 134 | ScanRecord record = result.getScanRecord(); |
| 135 | if (record == null) { |
| 136 | return null; |
| 137 | } |
| 138 | Map<ParcelUuid, byte[]> serviceData = record.getServiceData(); |
| 139 | if (serviceData != null && !serviceData.isEmpty()) { |
| 140 | ParcelUuid uuid = serviceData.keySet().iterator().next(); |
| 141 | return new ScanFilter.Builder().setServiceData(uuid, new byte[] { 0 }, |
| 142 | new byte[] { 0 }).build(); |
| 143 | } |
| 144 | SparseArray<byte[]> manufacturerSpecificData = record.getManufacturerSpecificData(); |
| 145 | if (manufacturerSpecificData != null && manufacturerSpecificData.size() > 0) { |
| 146 | return new ScanFilter.Builder().setManufacturerData(manufacturerSpecificData.keyAt(0), |
| 147 | new byte[] { 0 }, new byte[] { 0 }).build(); |
| 148 | } |
| 149 | List<ParcelUuid> serviceUuids = record.getServiceUuids(); |
| 150 | if (serviceUuids != null && !serviceUuids.isEmpty()) { |
| 151 | return new ScanFilter.Builder().setServiceUuid(serviceUuids.get(0)).build(); |
| 152 | } |
| 153 | return null; |
| 154 | } |
| 155 | |
Wei Wang | 0b2b4b6 | 2015-01-15 12:07:37 -0800 | [diff] [blame] | 156 | /** |
| 157 | * Test of opportunistic BLE scans. |
| 158 | */ |
| 159 | @MediumTest |
| 160 | public void testOpportunisticScan() { |
Wei Wang | 4475498 | 2015-08-12 12:12:02 -0700 | [diff] [blame] | 161 | if (!isBleSupported()) |
| 162 | return; |
| 163 | ScanSettings opportunisticScanSettings = new ScanSettings.Builder() |
| 164 | .setScanMode(ScanSettings.SCAN_MODE_OPPORTUNISTIC) |
| 165 | .build(); |
Wei Wang | 0b2b4b6 | 2015-01-15 12:07:37 -0800 | [diff] [blame] | 166 | BleScanCallback emptyScanCallback = new BleScanCallback(); |
| 167 | |
| 168 | // No scans are really started with opportunistic scans only. |
| 169 | mScanner.startScan(Collections.<ScanFilter> emptyList(), opportunisticScanSettings, |
| 170 | emptyScanCallback); |
| 171 | sleep(SCAN_DURATION_MILLIS); |
| 172 | assertTrue(emptyScanCallback.getScanResults().isEmpty()); |
| 173 | |
| 174 | BleScanCallback regularScanCallback = new BleScanCallback(); |
Wei Wang | 4475498 | 2015-08-12 12:12:02 -0700 | [diff] [blame] | 175 | ScanSettings regularScanSettings = new ScanSettings.Builder() |
| 176 | .setScanMode(ScanSettings.SCAN_MODE_LOW_LATENCY).build(); |
Wei Wang | 0b2b4b6 | 2015-01-15 12:07:37 -0800 | [diff] [blame] | 177 | List<ScanFilter> filters = new ArrayList<>(); |
Wei Wang | 4475498 | 2015-08-12 12:12:02 -0700 | [diff] [blame] | 178 | ScanFilter filter = createScanFilter(); |
| 179 | if (filter != null) { |
| 180 | filters.add(filter); |
| 181 | } else { |
| 182 | Log.d(TAG, "no appropriate filter can be set"); |
| 183 | } |
Wei Wang | 0b2b4b6 | 2015-01-15 12:07:37 -0800 | [diff] [blame] | 184 | mScanner.startScan(filters, regularScanSettings, regularScanCallback); |
| 185 | sleep(SCAN_DURATION_MILLIS); |
| 186 | // With normal BLE scan client, opportunistic scan client will get scan results. |
| 187 | assertTrue("opportunistic scan results shouldn't be empty", |
| 188 | !emptyScanCallback.getScanResults().isEmpty()); |
Wei Wang | 0b2b4b6 | 2015-01-15 12:07:37 -0800 | [diff] [blame] | 189 | |
| 190 | // No more scan results for opportunistic scan clients once the normal BLE scan clients |
| 191 | // stops. |
| 192 | mScanner.stopScan(regularScanCallback); |
| 193 | // In case we got scan results before scan was completely stopped. |
| 194 | sleep(1000); |
| 195 | emptyScanCallback.clear(); |
| 196 | sleep(SCAN_DURATION_MILLIS); |
| 197 | assertTrue("opportunistic scan shouldn't have scan results", |
| 198 | emptyScanCallback.getScanResults().isEmpty()); |
| 199 | } |
| 200 | |
| 201 | /** |
| 202 | * Test case for BLE Batch scan. |
| 203 | */ |
| 204 | @MediumTest |
| 205 | public void testBatchScan() { |
Wei Wang | 4475498 | 2015-08-12 12:12:02 -0700 | [diff] [blame] | 206 | if (!isBleSupported() || !isBleBatchScanSupported()) { |
| 207 | Log.d(TAG, "BLE or BLE batching not suppported"); |
| 208 | return; |
| 209 | } |
Wei Wang | 0b2b4b6 | 2015-01-15 12:07:37 -0800 | [diff] [blame] | 210 | ScanSettings batchScanSettings = new ScanSettings.Builder() |
| 211 | .setScanMode(ScanSettings.SCAN_MODE_LOW_LATENCY) |
| 212 | .setReportDelay(BATCH_SCAN_REPORT_DELAY_MILLIS).build(); |
| 213 | BleScanCallback batchScanCallback = new BleScanCallback(); |
Wei Wang | 0b2b4b6 | 2015-01-15 12:07:37 -0800 | [diff] [blame] | 214 | mScanner.startScan(Collections.<ScanFilter> emptyList(), batchScanSettings, |
| 215 | batchScanCallback); |
| 216 | sleep(SCAN_DURATION_MILLIS); |
| 217 | mScanner.flushPendingScanResults(batchScanCallback); |
| 218 | sleep(1000); |
Wei Wang | 0b2b4b6 | 2015-01-15 12:07:37 -0800 | [diff] [blame] | 219 | List<ScanResult> results = batchScanCallback.getBatchScanResults(); |
| 220 | assertTrue(!results.isEmpty()); |
Wei Wang | 4475498 | 2015-08-12 12:12:02 -0700 | [diff] [blame] | 221 | long scanEndMillis = SystemClock.elapsedRealtime(); |
| 222 | mScanner.stopScan(batchScanCallback); |
| 223 | verifyTimestamp(results, 0, scanEndMillis); |
Wei Wang | 0b2b4b6 | 2015-01-15 12:07:37 -0800 | [diff] [blame] | 224 | } |
| 225 | |
| 226 | // Verify timestamp of all scan results are within [scanStartMillis, scanEndMillis]. |
| 227 | private void verifyTimestamp(Collection<ScanResult> results, long scanStartMillis, |
| 228 | long scanEndMillis) { |
| 229 | for (ScanResult result : results) { |
| 230 | long timestampMillis = TimeUnit.NANOSECONDS.toMillis(result.getTimestampNanos()); |
Wei Wang | 4475498 | 2015-08-12 12:12:02 -0700 | [diff] [blame] | 231 | assertTrue("Invalid timestamp: " + timestampMillis + " should be >= " + scanStartMillis, |
| 232 | timestampMillis >= scanStartMillis); |
| 233 | assertTrue("Invalid timestamp: " + timestampMillis + " should be <= " + scanEndMillis, |
| 234 | timestampMillis <= scanEndMillis); |
Wei Wang | 0b2b4b6 | 2015-01-15 12:07:37 -0800 | [diff] [blame] | 235 | } |
| 236 | } |
| 237 | |
| 238 | // Helper class for BLE scan callback. |
| 239 | private class BleScanCallback extends ScanCallback { |
| 240 | private Set<ScanResult> mResults = new HashSet<ScanResult>(); |
| 241 | private List<ScanResult> mBatchScanResults = new ArrayList<ScanResult>(); |
| 242 | |
| 243 | @Override |
| 244 | public void onScanResult(int callbackType, ScanResult result) { |
| 245 | if (callbackType == ScanSettings.CALLBACK_TYPE_ALL_MATCHES) { |
| 246 | mResults.add(result); |
| 247 | } |
| 248 | } |
| 249 | |
| 250 | @Override |
| 251 | public void onBatchScanResults(List<ScanResult> results) { |
Wei Wang | b67d21d | 2015-09-03 11:15:05 -0700 | [diff] [blame] | 252 | // In case onBatchScanResults are called due to buffer full, we want to collect all |
| 253 | // scan results. |
| 254 | mBatchScanResults.addAll(results); |
Wei Wang | 0b2b4b6 | 2015-01-15 12:07:37 -0800 | [diff] [blame] | 255 | } |
| 256 | |
| 257 | // Clear regular and batch scan results. |
| 258 | synchronized public void clear() { |
| 259 | mResults.clear(); |
| 260 | mBatchScanResults.clear(); |
| 261 | } |
| 262 | |
| 263 | // Return regular BLE scan results accumulated so far. |
Wei Wang | 4475498 | 2015-08-12 12:12:02 -0700 | [diff] [blame] | 264 | synchronized Set<ScanResult> getScanResults() { |
| 265 | return Collections.unmodifiableSet(mResults); |
Wei Wang | 0b2b4b6 | 2015-01-15 12:07:37 -0800 | [diff] [blame] | 266 | } |
| 267 | |
| 268 | // Return batch scan results. |
| 269 | synchronized List<ScanResult> getBatchScanResults() { |
| 270 | return Collections.unmodifiableList(mBatchScanResults); |
| 271 | } |
| 272 | } |
| 273 | |
Wei Wang | 4475498 | 2015-08-12 12:12:02 -0700 | [diff] [blame] | 274 | private class RssiComparator implements Comparator<ScanResult> { |
| 275 | |
| 276 | @Override |
| 277 | public int compare(ScanResult lhs, ScanResult rhs) { |
| 278 | return rhs.getRssi() - lhs.getRssi(); |
| 279 | } |
| 280 | |
| 281 | } |
| 282 | |
| 283 | // Perform a BLE scan to get results of nearby BLE devices. |
| 284 | private Set<ScanResult> scan() { |
| 285 | BleScanCallback regularLeScanCallback = new BleScanCallback(); |
| 286 | mScanner.startScan(regularLeScanCallback); |
| 287 | sleep(SCAN_DURATION_MILLIS); |
| 288 | mScanner.stopScan(regularLeScanCallback); |
| 289 | sleep(1000); |
| 290 | return regularLeScanCallback.getScanResults(); |
| 291 | } |
| 292 | |
Wei Wang | 0b2b4b6 | 2015-01-15 12:07:37 -0800 | [diff] [blame] | 293 | // Put the current thread to sleep. |
| 294 | private void sleep(int sleepMillis) { |
| 295 | try { |
| 296 | Thread.sleep(sleepMillis); |
| 297 | } catch (InterruptedException e) { |
| 298 | Log.e(TAG, "interrupted", e); |
| 299 | } |
| 300 | } |
| 301 | |
Wei Wang | 4475498 | 2015-08-12 12:12:02 -0700 | [diff] [blame] | 302 | // Check if Bluetooth LE feature is supported on DUT. |
| 303 | private boolean isBleSupported() { |
| 304 | return getContext().getPackageManager() |
| 305 | .hasSystemFeature(PackageManager.FEATURE_BLUETOOTH_LE); |
| 306 | } |
| 307 | |
| 308 | // Returns whether offloaded scan batching is supported. |
| 309 | private boolean isBleBatchScanSupported() { |
| 310 | return mBluetoothAdapter.isOffloadedScanBatchingSupported(); |
| 311 | } |
| 312 | |
Wei Wang | 0b2b4b6 | 2015-01-15 12:07:37 -0800 | [diff] [blame] | 313 | } |