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