BluetoothLeScanner.java revision 2d49752ee050ab7f1cd848933f6c62a73707e2d9
1/* 2 * Copyright (C) 2014 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); you may not 5 * use this file except in compliance with the License. You may obtain a copy of 6 * 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, WITHOUT 12 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 * License for the specific language governing permissions and limitations under 14 * the License. 15 */ 16 17package android.bluetooth.le; 18 19import android.Manifest; 20import android.annotation.RequiresPermission; 21import android.annotation.SystemApi; 22import android.bluetooth.BluetoothAdapter; 23import android.bluetooth.BluetoothGatt; 24import android.bluetooth.BluetoothGattCallbackWrapper; 25import android.bluetooth.IBluetoothGatt; 26import android.bluetooth.IBluetoothManager; 27import android.os.Handler; 28import android.os.Looper; 29import android.os.ParcelUuid; 30import android.os.RemoteException; 31import android.util.Log; 32 33import java.util.ArrayList; 34import java.util.HashMap; 35import java.util.List; 36import java.util.Map; 37import java.util.UUID; 38 39/** 40 * This class provides methods to perform scan related operations for Bluetooth LE devices. An 41 * application can scan for a particular type of Bluetooth LE devices using {@link ScanFilter}. It 42 * can also request different types of callbacks for delivering the result. 43 * <p> 44 * Use {@link BluetoothAdapter#getBluetoothLeScanner()} to get an instance of 45 * {@link BluetoothLeScanner}. 46 * <p> 47 * <b>Note:</b> Most of the scan methods here require 48 * {@link android.Manifest.permission#BLUETOOTH_ADMIN} permission. 49 * 50 * @see ScanFilter 51 */ 52public final class BluetoothLeScanner { 53 54 private static final String TAG = "BluetoothLeScanner"; 55 private static final boolean DBG = true; 56 private static final boolean VDBG = false; 57 58 private final IBluetoothManager mBluetoothManager; 59 private final Handler mHandler; 60 private BluetoothAdapter mBluetoothAdapter; 61 private final Map<ScanCallback, BleScanCallbackWrapper> mLeScanClients; 62 63 /** 64 * Use {@link BluetoothAdapter#getBluetoothLeScanner()} instead. 65 * 66 * @param bluetoothManager BluetoothManager that conducts overall Bluetooth Management. 67 * @hide 68 */ 69 public BluetoothLeScanner(IBluetoothManager bluetoothManager) { 70 mBluetoothManager = bluetoothManager; 71 mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter(); 72 mHandler = new Handler(Looper.getMainLooper()); 73 mLeScanClients = new HashMap<ScanCallback, BleScanCallbackWrapper>(); 74 } 75 76 /** 77 * Start Bluetooth LE scan with default parameters and no filters. The scan results will be 78 * delivered through {@code callback}. 79 * <p> 80 * Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN} permission. 81 * 82 * @param callback Callback used to deliver scan results. 83 * @throws IllegalArgumentException If {@code callback} is null. 84 */ 85 @RequiresPermission(Manifest.permission.BLUETOOTH_ADMIN) 86 public void startScan(final ScanCallback callback) { 87 if (callback == null) { 88 throw new IllegalArgumentException("callback is null"); 89 } 90 startScan(null, new ScanSettings.Builder().build(), callback); 91 } 92 93 /** 94 * Start Bluetooth LE scan. The scan results will be delivered through {@code callback}. 95 * <p> 96 * Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN} permission. 97 * 98 * @param filters {@link ScanFilter}s for finding exact BLE devices. 99 * @param settings Settings for the scan. 100 * @param callback Callback used to deliver scan results. 101 * @throws IllegalArgumentException If {@code settings} or {@code callback} is null. 102 */ 103 @RequiresPermission(Manifest.permission.BLUETOOTH_ADMIN) 104 public void startScan(List<ScanFilter> filters, ScanSettings settings, 105 final ScanCallback callback) { 106 startScan(filters, settings, callback, null); 107 } 108 109 private void startScan(List<ScanFilter> filters, ScanSettings settings, 110 final ScanCallback callback, List<List<ResultStorageDescriptor>> resultStorages) { 111 BluetoothLeUtils.checkAdapterStateOn(mBluetoothAdapter); 112 if (settings == null || callback == null) { 113 throw new IllegalArgumentException("settings or callback is null"); 114 } 115 synchronized (mLeScanClients) { 116 if (mLeScanClients.containsKey(callback)) { 117 postCallbackError(callback, ScanCallback.SCAN_FAILED_ALREADY_STARTED); 118 return; 119 } 120 IBluetoothGatt gatt; 121 try { 122 gatt = mBluetoothManager.getBluetoothGatt(); 123 } catch (RemoteException e) { 124 gatt = null; 125 } 126 if (gatt == null) { 127 postCallbackError(callback, ScanCallback.SCAN_FAILED_INTERNAL_ERROR); 128 return; 129 } 130 if (!isSettingsConfigAllowedForScan(settings)) { 131 postCallbackError(callback, 132 ScanCallback.SCAN_FAILED_FEATURE_UNSUPPORTED); 133 return; 134 } 135 if (!isHardwareResourcesAvailableForScan(settings)) { 136 postCallbackError(callback, 137 ScanCallback.SCAN_FAILED_OUT_OF_HARDWARE_RESOURCES); 138 return; 139 } 140 if (!isSettingsAndFilterComboAllowed(settings, filters)) { 141 postCallbackError(callback, 142 ScanCallback.SCAN_FAILED_FEATURE_UNSUPPORTED); 143 return; 144 } 145 BleScanCallbackWrapper wrapper = new BleScanCallbackWrapper(gatt, filters, 146 settings, callback, resultStorages); 147 wrapper.startRegisteration(); 148 } 149 } 150 151 /** 152 * Stops an ongoing Bluetooth LE scan. 153 * <p> 154 * Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN} permission. 155 * 156 * @param callback 157 */ 158 @RequiresPermission(Manifest.permission.BLUETOOTH_ADMIN) 159 public void stopScan(ScanCallback callback) { 160 BluetoothLeUtils.checkAdapterStateOn(mBluetoothAdapter); 161 synchronized (mLeScanClients) { 162 BleScanCallbackWrapper wrapper = mLeScanClients.remove(callback); 163 if (wrapper == null) { 164 if (DBG) Log.d(TAG, "could not find callback wrapper"); 165 return; 166 } 167 wrapper.stopLeScan(); 168 } 169 } 170 171 /** 172 * Flush pending batch scan results stored in Bluetooth controller. This will return Bluetooth 173 * LE scan results batched on bluetooth controller. Returns immediately, batch scan results data 174 * will be delivered through the {@code callback}. 175 * 176 * @param callback Callback of the Bluetooth LE Scan, it has to be the same instance as the one 177 * used to start scan. 178 */ 179 public void flushPendingScanResults(ScanCallback callback) { 180 BluetoothLeUtils.checkAdapterStateOn(mBluetoothAdapter); 181 if (callback == null) { 182 throw new IllegalArgumentException("callback cannot be null!"); 183 } 184 synchronized (mLeScanClients) { 185 BleScanCallbackWrapper wrapper = mLeScanClients.get(callback); 186 if (wrapper == null) { 187 return; 188 } 189 wrapper.flushPendingBatchResults(); 190 } 191 } 192 193 /** 194 * Start truncated scan. 195 * 196 * @hide 197 */ 198 @SystemApi 199 public void startTruncatedScan(List<TruncatedFilter> truncatedFilters, ScanSettings settings, 200 final ScanCallback callback) { 201 int filterSize = truncatedFilters.size(); 202 List<ScanFilter> scanFilters = new ArrayList<ScanFilter>(filterSize); 203 List<List<ResultStorageDescriptor>> scanStorages = 204 new ArrayList<List<ResultStorageDescriptor>>(filterSize); 205 for (TruncatedFilter filter : truncatedFilters) { 206 scanFilters.add(filter.getFilter()); 207 scanStorages.add(filter.getStorageDescriptors()); 208 } 209 startScan(scanFilters, settings, callback, scanStorages); 210 } 211 212 /** 213 * Cleans up scan clients. Should be called when bluetooth is down. 214 * 215 * @hide 216 */ 217 public void cleanup() { 218 mLeScanClients.clear(); 219 } 220 221 /** 222 * Bluetooth GATT interface callbacks 223 */ 224 private class BleScanCallbackWrapper extends BluetoothGattCallbackWrapper { 225 private static final int REGISTRATION_CALLBACK_TIMEOUT_MILLIS = 2000; 226 227 private final ScanCallback mScanCallback; 228 private final List<ScanFilter> mFilters; 229 private ScanSettings mSettings; 230 private IBluetoothGatt mBluetoothGatt; 231 private List<List<ResultStorageDescriptor>> mResultStorages; 232 233 // mLeHandle 0: not registered 234 // -1: scan stopped 235 // > 0: registered and scan started 236 private int mClientIf; 237 238 public BleScanCallbackWrapper(IBluetoothGatt bluetoothGatt, 239 List<ScanFilter> filters, ScanSettings settings, 240 ScanCallback scanCallback, List<List<ResultStorageDescriptor>> resultStorages) { 241 mBluetoothGatt = bluetoothGatt; 242 mFilters = filters; 243 mSettings = settings; 244 mScanCallback = scanCallback; 245 mClientIf = 0; 246 mResultStorages = resultStorages; 247 } 248 249 public void startRegisteration() { 250 synchronized (this) { 251 // Scan stopped. 252 if (mClientIf == -1) return; 253 try { 254 UUID uuid = UUID.randomUUID(); 255 mBluetoothGatt.registerClient(new ParcelUuid(uuid), this); 256 wait(REGISTRATION_CALLBACK_TIMEOUT_MILLIS); 257 } catch (InterruptedException | RemoteException e) { 258 Log.e(TAG, "application registeration exception", e); 259 postCallbackError(mScanCallback, ScanCallback.SCAN_FAILED_INTERNAL_ERROR); 260 } 261 if (mClientIf > 0) { 262 mLeScanClients.put(mScanCallback, this); 263 } else { 264 postCallbackError(mScanCallback, 265 ScanCallback.SCAN_FAILED_APPLICATION_REGISTRATION_FAILED); 266 } 267 } 268 } 269 270 public void stopLeScan() { 271 synchronized (this) { 272 if (mClientIf <= 0) { 273 Log.e(TAG, "Error state, mLeHandle: " + mClientIf); 274 return; 275 } 276 try { 277 mBluetoothGatt.stopScan(mClientIf, false); 278 mBluetoothGatt.unregisterClient(mClientIf); 279 } catch (RemoteException e) { 280 Log.e(TAG, "Failed to stop scan and unregister", e); 281 } 282 mClientIf = -1; 283 } 284 } 285 286 void flushPendingBatchResults() { 287 synchronized (this) { 288 if (mClientIf <= 0) { 289 Log.e(TAG, "Error state, mLeHandle: " + mClientIf); 290 return; 291 } 292 try { 293 mBluetoothGatt.flushPendingBatchResults(mClientIf, false); 294 } catch (RemoteException e) { 295 Log.e(TAG, "Failed to get pending scan results", e); 296 } 297 } 298 } 299 300 /** 301 * Application interface registered - app is ready to go 302 */ 303 @Override 304 public void onClientRegistered(int status, int clientIf) { 305 Log.d(TAG, "onClientRegistered() - status=" + status + 306 " clientIf=" + clientIf); 307 synchronized (this) { 308 if (mClientIf == -1) { 309 if (DBG) Log.d(TAG, "onClientRegistered LE scan canceled"); 310 } 311 312 if (status == BluetoothGatt.GATT_SUCCESS) { 313 mClientIf = clientIf; 314 try { 315 mBluetoothGatt.startScan(mClientIf, false, mSettings, mFilters, 316 mResultStorages); 317 } catch (RemoteException e) { 318 Log.e(TAG, "fail to start le scan: " + e); 319 mClientIf = -1; 320 } 321 } else { 322 // registration failed 323 mClientIf = -1; 324 } 325 notifyAll(); 326 } 327 } 328 329 /** 330 * Callback reporting an LE scan result. 331 * 332 * @hide 333 */ 334 @Override 335 public void onScanResult(final ScanResult scanResult) { 336 if (VDBG) Log.d(TAG, "onScanResult() - " + scanResult.toString()); 337 338 // Check null in case the scan has been stopped 339 synchronized (this) { 340 if (mClientIf <= 0) return; 341 } 342 Handler handler = new Handler(Looper.getMainLooper()); 343 handler.post(new Runnable() { 344 @Override 345 public void run() { 346 mScanCallback.onScanResult(ScanSettings.CALLBACK_TYPE_ALL_MATCHES, scanResult); 347 } 348 }); 349 350 } 351 352 @Override 353 public void onBatchScanResults(final List<ScanResult> results) { 354 Handler handler = new Handler(Looper.getMainLooper()); 355 handler.post(new Runnable() { 356 @Override 357 public void run() { 358 mScanCallback.onBatchScanResults(results); 359 } 360 }); 361 } 362 363 @Override 364 public void onFoundOrLost(final boolean onFound, final ScanResult scanResult) { 365 if (VDBG) { 366 Log.d(TAG, "onFoundOrLost() - onFound = " + onFound + 367 " " + scanResult.toString()); 368 } 369 370 // Check null in case the scan has been stopped 371 synchronized (this) { 372 if (mClientIf <= 0) 373 return; 374 } 375 Handler handler = new Handler(Looper.getMainLooper()); 376 handler.post(new Runnable() { 377 @Override 378 public void run() { 379 if (onFound) { 380 mScanCallback.onScanResult(ScanSettings.CALLBACK_TYPE_FIRST_MATCH, 381 scanResult); 382 } else { 383 mScanCallback.onScanResult(ScanSettings.CALLBACK_TYPE_MATCH_LOST, 384 scanResult); 385 } 386 } 387 }); 388 } 389 390 @Override 391 public void onScanManagerErrorCallback(final int errorCode) { 392 if (VDBG) { 393 Log.d(TAG, "onScanManagerErrorCallback() - errorCode = " + errorCode); 394 } 395 synchronized (this) { 396 if (mClientIf <= 0) 397 return; 398 } 399 postCallbackError(mScanCallback, errorCode); 400 } 401 } 402 403 private void postCallbackError(final ScanCallback callback, final int errorCode) { 404 mHandler.post(new Runnable() { 405 @Override 406 public void run() { 407 callback.onScanFailed(errorCode); 408 } 409 }); 410 } 411 412 private boolean isSettingsConfigAllowedForScan(ScanSettings settings) { 413 if (mBluetoothAdapter.isOffloadedFilteringSupported()) { 414 return true; 415 } 416 final int callbackType = settings.getCallbackType(); 417 // Only support regular scan if no offloaded filter support. 418 if (callbackType == ScanSettings.CALLBACK_TYPE_ALL_MATCHES 419 && settings.getReportDelayMillis() == 0) { 420 return true; 421 } 422 return false; 423 } 424 425 private boolean isSettingsAndFilterComboAllowed(ScanSettings settings, 426 List <ScanFilter> filterList) { 427 final int callbackType = settings.getCallbackType(); 428 // If onlost/onfound is requested, a non-empty filter is expected 429 if ((callbackType & (ScanSettings.CALLBACK_TYPE_FIRST_MATCH 430 | ScanSettings.CALLBACK_TYPE_MATCH_LOST)) != 0) { 431 if (filterList == null) { 432 return false; 433 } 434 for (ScanFilter filter : filterList) { 435 if (filter.isAllFieldsEmpty()) { 436 return false; 437 } 438 } 439 } 440 return true; 441 } 442 443 private boolean isHardwareResourcesAvailableForScan(ScanSettings settings) { 444 final int callbackType = settings.getCallbackType(); 445 if ((callbackType & ScanSettings.CALLBACK_TYPE_FIRST_MATCH) != 0 446 || (callbackType & ScanSettings.CALLBACK_TYPE_MATCH_LOST) != 0) { 447 // For onlost/onfound, we required hw support be available 448 return (mBluetoothAdapter.isOffloadedFilteringSupported() && 449 mBluetoothAdapter.isHardwareTrackingFiltersAvailable()); 450 } 451 return true; 452 } 453} 454