blob: cc444a6f42353c523f7554b4af2b3ae0830c72b1 [file] [log] [blame]
Wei Wang0b2b4b62015-01-15 12:07:37 -08001/*
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
17package android.bluetooth.cts;
18
19import android.bluetooth.BluetoothAdapter;
20import android.bluetooth.BluetoothManager;
21import android.bluetooth.le.BluetoothLeScanner;
22import android.bluetooth.le.ScanCallback;
23import android.bluetooth.le.ScanFilter;
Wei Wang44754982015-08-12 12:12:02 -070024import android.bluetooth.le.ScanRecord;
Wei Wang0b2b4b62015-01-15 12:07:37 -080025import android.bluetooth.le.ScanResult;
26import android.bluetooth.le.ScanSettings;
27import android.content.Context;
Wei Wang44754982015-08-12 12:12:02 -070028import android.content.pm.PackageManager;
29import android.os.ParcelUuid;
Wei Wang0b2b4b62015-01-15 12:07:37 -080030import android.os.SystemClock;
31import android.test.AndroidTestCase;
32import android.test.suitebuilder.annotation.MediumTest;
33import android.util.Log;
Wei Wang44754982015-08-12 12:12:02 -070034import android.util.SparseArray;
Wei Wang0b2b4b62015-01-15 12:07:37 -080035
36import java.util.ArrayList;
37import java.util.Collection;
38import java.util.Collections;
Wei Wang44754982015-08-12 12:12:02 -070039import java.util.Comparator;
Wei Wang0b2b4b62015-01-15 12:07:37 -080040import java.util.HashSet;
41import java.util.List;
Wei Wang44754982015-08-12 12:12:02 -070042import java.util.Map;
Wei Wang0b2b4b62015-01-15 12:07:37 -080043import java.util.Set;
44import java.util.concurrent.TimeUnit;
45
46/**
47 * Test cases for Bluetooth LE scans.
48 * <p>
Wei Wang44754982015-08-12 12:12:02 -070049 * 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 Wang0b2b4b62015-01-15 12:07:37 -080051 * <p>
52 * Run 'run cts --class android.bluetooth.cts.BluetoothLeScanTest' in cts-tradefed to run the test
53 * cases.
54 */
55public class BluetoothLeScanTest extends AndroidTestCase {
56
57 private static final String TAG = "BluetoothLeScanTest";
58
Wei Wang0b2b4b62015-01-15 12:07:37 -080059 private static final int SCAN_DURATION_MILLIS = 5000;
Wei Wang44754982015-08-12 12:12:02 -070060 private static final int BATCH_SCAN_REPORT_DELAY_MILLIS = 20000;
Wei Wang0b2b4b62015-01-15 12:07:37 -080061
Wei Wang44754982015-08-12 12:12:02 -070062 private BluetoothAdapter mBluetoothAdapter;
Wei Wang0b2b4b62015-01-15 12:07:37 -080063 private BluetoothLeScanner mScanner;
64
65 @Override
66 public void setUp() {
Wei Wang44754982015-08-12 12:12:02 -070067 if (!isBleSupported())
68 return;
Wei Wang0b2b4b62015-01-15 12:07:37 -080069 BluetoothManager manager = (BluetoothManager) mContext.getSystemService(
70 Context.BLUETOOTH_SERVICE);
Wei Wang44754982015-08-12 12:12:02 -070071 mBluetoothAdapter = manager.getAdapter();
72 if (!mBluetoothAdapter.isEnabled()) {
Wei Wang0b2b4b62015-01-15 12:07:37 -080073 // Note it's not reliable to listen for Adapter.ACTION_STATE_CHANGED broadcast and check
74 // bluetooth state.
Wei Wang44754982015-08-12 12:12:02 -070075 mBluetoothAdapter.enable();
76 sleep(3000);
Wei Wang0b2b4b62015-01-15 12:07:37 -080077 }
Wei Wang44754982015-08-12 12:12:02 -070078 mScanner = mBluetoothAdapter.getBluetoothLeScanner();
Wei Wang0b2b4b62015-01-15 12:07:37 -080079 }
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 Wang44754982015-08-12 12:12:02 -070086 if (!isBleSupported())
87 return;
Wei Wang0b2b4b62015-01-15 12:07:37 -080088 long scanStartMillis = SystemClock.elapsedRealtime();
Wei Wang44754982015-08-12 12:12:02 -070089 Collection<ScanResult> scanResults = scan();
Wei Wang0b2b4b62015-01-15 12:07:37 -080090 long scanEndMillis = SystemClock.elapsedRealtime();
Wei Wang0b2b4b62015-01-15 12:07:37 -080091 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 Wang44754982015-08-12 12:12:02 -0700101 if (!isBleSupported())
102 return;
103
Wei Wang0b2b4b62015-01-15 12:07:37 -0800104 List<ScanFilter> filters = new ArrayList<ScanFilter>();
Wei Wang44754982015-08-12 12:12:02 -0700105 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 Wang0b2b4b62015-01-15 12:07:37 -0800111
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 Wang44754982015-08-12 12:12:02 -0700118 sleep(1000);
Wei Wang0b2b4b62015-01-15 12:07:37 -0800119 Collection<ScanResult> scanResults = filterLeScanCallback.getScanResults();
Wei Wang0b2b4b62015-01-15 12:07:37 -0800120 for (ScanResult result : scanResults) {
Wei Wang44754982015-08-12 12:12:02 -0700121 assertTrue(filter.matches(result));
Wei Wang0b2b4b62015-01-15 12:07:37 -0800122 }
123 }
124
Wei Wang44754982015-08-12 12:12:02 -0700125 // 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 Wang0b2b4b62015-01-15 12:07:37 -0800156 /**
157 * Test of opportunistic BLE scans.
158 */
159 @MediumTest
160 public void testOpportunisticScan() {
Wei Wang44754982015-08-12 12:12:02 -0700161 if (!isBleSupported())
162 return;
163 ScanSettings opportunisticScanSettings = new ScanSettings.Builder()
164 .setScanMode(ScanSettings.SCAN_MODE_OPPORTUNISTIC)
165 .build();
Wei Wang0b2b4b62015-01-15 12:07:37 -0800166 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 Wang44754982015-08-12 12:12:02 -0700175 ScanSettings regularScanSettings = new ScanSettings.Builder()
176 .setScanMode(ScanSettings.SCAN_MODE_LOW_LATENCY).build();
Wei Wang0b2b4b62015-01-15 12:07:37 -0800177 List<ScanFilter> filters = new ArrayList<>();
Wei Wang44754982015-08-12 12:12:02 -0700178 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 Wang0b2b4b62015-01-15 12:07:37 -0800184 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 Wang0b2b4b62015-01-15 12:07:37 -0800189
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 Wang44754982015-08-12 12:12:02 -0700206 if (!isBleSupported() || !isBleBatchScanSupported()) {
207 Log.d(TAG, "BLE or BLE batching not suppported");
208 return;
209 }
Wei Wang0b2b4b62015-01-15 12:07:37 -0800210 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 Wang0b2b4b62015-01-15 12:07:37 -0800214 mScanner.startScan(Collections.<ScanFilter> emptyList(), batchScanSettings,
215 batchScanCallback);
216 sleep(SCAN_DURATION_MILLIS);
217 mScanner.flushPendingScanResults(batchScanCallback);
218 sleep(1000);
Wei Wang0b2b4b62015-01-15 12:07:37 -0800219 List<ScanResult> results = batchScanCallback.getBatchScanResults();
220 assertTrue(!results.isEmpty());
Wei Wang44754982015-08-12 12:12:02 -0700221 long scanEndMillis = SystemClock.elapsedRealtime();
222 mScanner.stopScan(batchScanCallback);
223 verifyTimestamp(results, 0, scanEndMillis);
Wei Wang0b2b4b62015-01-15 12:07:37 -0800224 }
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 Wang44754982015-08-12 12:12:02 -0700231 assertTrue("Invalid timestamp: " + timestampMillis + " should be >= " + scanStartMillis,
232 timestampMillis >= scanStartMillis);
233 assertTrue("Invalid timestamp: " + timestampMillis + " should be <= " + scanEndMillis,
234 timestampMillis <= scanEndMillis);
Wei Wang0b2b4b62015-01-15 12:07:37 -0800235 }
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 Wangb67d21d2015-09-03 11:15:05 -0700252 // In case onBatchScanResults are called due to buffer full, we want to collect all
253 // scan results.
254 mBatchScanResults.addAll(results);
Wei Wang0b2b4b62015-01-15 12:07:37 -0800255 }
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 Wang44754982015-08-12 12:12:02 -0700264 synchronized Set<ScanResult> getScanResults() {
265 return Collections.unmodifiableSet(mResults);
Wei Wang0b2b4b62015-01-15 12:07:37 -0800266 }
267
268 // Return batch scan results.
269 synchronized List<ScanResult> getBatchScanResults() {
270 return Collections.unmodifiableList(mBatchScanResults);
271 }
272 }
273
Wei Wang44754982015-08-12 12:12:02 -0700274 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 Wang0b2b4b62015-01-15 12:07:37 -0800293 // 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 Wang44754982015-08-12 12:12:02 -0700302 // 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 Wang0b2b4b62015-01-15 12:07:37 -0800313}