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 17 package android.bluetooth.le; 18 19 import android.annotation.NonNull; 20 import android.annotation.Nullable; 21 import android.annotation.RequiresNoPermission; 22 import android.annotation.RequiresPermission; 23 import android.annotation.SuppressLint; 24 import android.annotation.SystemApi; 25 import android.app.PendingIntent; 26 import android.bluetooth.Attributable; 27 import android.bluetooth.BluetoothAdapter; 28 import android.bluetooth.BluetoothGatt; 29 import android.bluetooth.IBluetoothGatt; 30 import android.bluetooth.IBluetoothScan; 31 import android.bluetooth.annotations.RequiresBluetoothLocationPermission; 32 import android.bluetooth.annotations.RequiresBluetoothScanPermission; 33 import android.bluetooth.annotations.RequiresLegacyBluetoothAdminPermission; 34 import android.content.AttributionSource; 35 import android.os.Handler; 36 import android.os.Looper; 37 import android.os.RemoteException; 38 import android.os.WorkSource; 39 import android.util.Log; 40 41 import com.android.bluetooth.flags.Flags; 42 43 import java.util.ArrayList; 44 import java.util.HashMap; 45 import java.util.List; 46 import java.util.Map; 47 import java.util.Objects; 48 49 /** 50 * This class provides methods to perform scan related operations for Bluetooth LE devices. An 51 * application can scan for a particular type of Bluetooth LE devices using {@link ScanFilter}. It 52 * can also request different types of callbacks for delivering the result. 53 * 54 * <p>Use {@link BluetoothAdapter#getBluetoothLeScanner()} to get an instance of {@link 55 * BluetoothLeScanner}. 56 * 57 * @see ScanFilter 58 */ 59 public final class BluetoothLeScanner { 60 61 private static final String TAG = "BluetoothLeScanner"; 62 private static final boolean DBG = true; 63 private static final boolean VDBG = false; 64 65 /** 66 * Extra containing a list of ScanResults. It can have one or more results if there was no 67 * error. In case of error, {@link #EXTRA_ERROR_CODE} will contain the error code and this extra 68 * will not be available. 69 */ 70 public static final String EXTRA_LIST_SCAN_RESULT = 71 "android.bluetooth.le.extra.LIST_SCAN_RESULT"; 72 73 /** 74 * Optional extra indicating the error code, if any. The error code will be one of the 75 * SCAN_FAILED_* codes in {@link ScanCallback}. 76 */ 77 public static final String EXTRA_ERROR_CODE = "android.bluetooth.le.extra.ERROR_CODE"; 78 79 /** 80 * Optional extra indicating the callback type, which will be one of CALLBACK_TYPE_* constants 81 * in {@link ScanSettings}. 82 * 83 * @see ScanCallback#onScanResult(int, ScanResult) 84 */ 85 public static final String EXTRA_CALLBACK_TYPE = "android.bluetooth.le.extra.CALLBACK_TYPE"; 86 87 private final BluetoothAdapter mBluetoothAdapter; 88 private final AttributionSource mAttributionSource; 89 90 private final Handler mHandler; 91 private final Map<ScanCallback, BleScanCallbackWrapper> mLeScanClients; 92 93 /** 94 * Use {@link BluetoothAdapter#getBluetoothLeScanner()} instead. 95 * 96 * @hide 97 */ BluetoothLeScanner(BluetoothAdapter bluetoothAdapter)98 public BluetoothLeScanner(BluetoothAdapter bluetoothAdapter) { 99 mBluetoothAdapter = Objects.requireNonNull(bluetoothAdapter); 100 mAttributionSource = mBluetoothAdapter.getAttributionSource(); 101 mHandler = new Handler(Looper.getMainLooper()); 102 mLeScanClients = new HashMap<ScanCallback, BleScanCallbackWrapper>(); 103 } 104 105 /** 106 * Start Bluetooth LE scan with default parameters and no filters. The scan results will be 107 * delivered through {@code callback}. For unfiltered scans, scanning is stopped on screen off 108 * to save power. Scanning is resumed when screen is turned on again. To avoid this, use {@link 109 * #startScan(List, ScanSettings, ScanCallback)} with desired {@link ScanFilter}. 110 * 111 * <p>An app must have {@link android.Manifest.permission#ACCESS_COARSE_LOCATION 112 * ACCESS_COARSE_LOCATION} permission in order to get results. An App targeting Android Q or 113 * later must have {@link android.Manifest.permission#ACCESS_FINE_LOCATION ACCESS_FINE_LOCATION} 114 * permission in order to get results. 115 * 116 * @param callback Callback used to deliver scan results. 117 * @throws IllegalArgumentException If {@code callback} is null. 118 */ 119 @RequiresLegacyBluetoothAdminPermission 120 @RequiresBluetoothScanPermission 121 @RequiresBluetoothLocationPermission 122 @RequiresPermission(android.Manifest.permission.BLUETOOTH_SCAN) startScan(final ScanCallback callback)123 public void startScan(final ScanCallback callback) { 124 startScan(null, new ScanSettings.Builder().build(), callback); 125 } 126 127 /** 128 * Start Bluetooth LE scan. The scan results will be delivered through {@code callback}. For 129 * unfiltered scans, scanning is stopped on screen off to save power. Scanning is resumed when 130 * screen is turned on again. To avoid this, do filtered scanning by using proper {@link 131 * ScanFilter}. 132 * 133 * <p>An app must have {@link android.Manifest.permission#ACCESS_COARSE_LOCATION 134 * ACCESS_COARSE_LOCATION} permission in order to get results. An App targeting Android Q or 135 * later must have {@link android.Manifest.permission#ACCESS_FINE_LOCATION ACCESS_FINE_LOCATION} 136 * permission in order to get results. 137 * 138 * @param filters {@link ScanFilter}s for finding exact BLE devices. 139 * @param settings Settings for the scan. 140 * @param callback Callback used to deliver scan results. 141 * @throws IllegalArgumentException If {@code settings} or {@code callback} is null. 142 */ 143 @RequiresLegacyBluetoothAdminPermission 144 @RequiresBluetoothScanPermission 145 @RequiresBluetoothLocationPermission 146 @RequiresPermission(android.Manifest.permission.BLUETOOTH_SCAN) startScan( List<ScanFilter> filters, ScanSettings settings, final ScanCallback callback)147 public void startScan( 148 List<ScanFilter> filters, ScanSettings settings, final ScanCallback callback) { 149 startScan(filters, settings, null, callback, /* callbackIntent= */ null); 150 } 151 152 /** 153 * Start Bluetooth LE scan using a {@link PendingIntent}. The scan results will be delivered via 154 * the PendingIntent. Use this method of scanning if your process is not always running and it 155 * should be started when scan results are available. 156 * 157 * <p>An app must have {@link android.Manifest.permission#ACCESS_COARSE_LOCATION 158 * ACCESS_COARSE_LOCATION} permission in order to get results. An App targeting Android Q or 159 * later must have {@link android.Manifest.permission#ACCESS_FINE_LOCATION ACCESS_FINE_LOCATION} 160 * permission in order to get results. 161 * 162 * <p>When the PendingIntent is delivered, the Intent passed to the receiver or activity will 163 * contain one or more of the extras {@link #EXTRA_CALLBACK_TYPE}, {@link #EXTRA_ERROR_CODE} and 164 * {@link #EXTRA_LIST_SCAN_RESULT} to indicate the result of the scan. 165 * 166 * @param filters Optional list of ScanFilters for finding exact BLE devices. 167 * @param settings Optional settings for the scan. 168 * @param callbackIntent The PendingIntent to deliver the result to. 169 * @return Returns 0 for success or an error code from {@link ScanCallback} if the scan request 170 * could not be sent. 171 * @see #stopScan(PendingIntent) 172 */ 173 @RequiresLegacyBluetoothAdminPermission 174 @RequiresBluetoothScanPermission 175 @RequiresBluetoothLocationPermission 176 @RequiresPermission(android.Manifest.permission.BLUETOOTH_SCAN) startScan( @ullable List<ScanFilter> filters, @Nullable ScanSettings settings, @NonNull PendingIntent callbackIntent)177 public int startScan( 178 @Nullable List<ScanFilter> filters, 179 @Nullable ScanSettings settings, 180 @NonNull PendingIntent callbackIntent) { 181 return startScan( 182 filters, 183 settings != null ? settings : new ScanSettings.Builder().build(), 184 null, 185 null, 186 callbackIntent); 187 } 188 189 /** 190 * Start Bluetooth LE scan. Same as {@link #startScan(ScanCallback)} but allows the caller to 191 * specify on behalf of which application(s) the work is being done. 192 * 193 * @param workSource {@link WorkSource} identifying the application(s) for which to blame for 194 * the scan. 195 * @param callback Callback used to deliver scan results. 196 * @hide 197 */ 198 @SystemApi 199 @RequiresLegacyBluetoothAdminPermission 200 @RequiresBluetoothScanPermission 201 @RequiresBluetoothLocationPermission 202 @RequiresPermission( 203 allOf = { 204 android.Manifest.permission.BLUETOOTH_SCAN, 205 android.Manifest.permission.UPDATE_DEVICE_STATS 206 }) startScanFromSource(final WorkSource workSource, final ScanCallback callback)207 public void startScanFromSource(final WorkSource workSource, final ScanCallback callback) { 208 startScanFromSource(null, new ScanSettings.Builder().build(), workSource, callback); 209 } 210 211 /** 212 * Start Bluetooth LE scan. Same as {@link #startScan(List, ScanSettings, ScanCallback)} but 213 * allows the caller to specify on behalf of which application(s) the work is being done. 214 * 215 * @param filters {@link ScanFilter}s for finding exact BLE devices. 216 * @param settings Settings for the scan. 217 * @param workSource {@link WorkSource} identifying the application(s) for which to blame for 218 * the scan. 219 * @param callback Callback used to deliver scan results. 220 * @hide 221 */ 222 @SystemApi 223 @RequiresLegacyBluetoothAdminPermission 224 @RequiresBluetoothScanPermission 225 @RequiresBluetoothLocationPermission 226 @RequiresPermission( 227 allOf = { 228 android.Manifest.permission.BLUETOOTH_SCAN, 229 android.Manifest.permission.UPDATE_DEVICE_STATS 230 }) 231 @SuppressLint("AndroidFrameworkRequiresPermission") startScanFromSource( List<ScanFilter> filters, ScanSettings settings, final WorkSource workSource, final ScanCallback callback)232 public void startScanFromSource( 233 List<ScanFilter> filters, 234 ScanSettings settings, 235 final WorkSource workSource, 236 final ScanCallback callback) { 237 startScan(filters, settings, workSource, callback, null); 238 } 239 240 @RequiresPermission(android.Manifest.permission.BLUETOOTH_SCAN) startScan( List<ScanFilter> filters, ScanSettings settings, final WorkSource workSource, final ScanCallback callback, final PendingIntent callbackIntent)241 private int startScan( 242 List<ScanFilter> filters, 243 ScanSettings settings, 244 final WorkSource workSource, 245 final ScanCallback callback, 246 final PendingIntent callbackIntent) { 247 BluetoothLeUtils.checkAdapterStateOn(mBluetoothAdapter); 248 if (callback == null && callbackIntent == null) { 249 throw new IllegalArgumentException("callback is null"); 250 } 251 if (settings == null) { 252 throw new IllegalArgumentException("settings is null"); 253 } 254 synchronized (mLeScanClients) { 255 if (callback != null && mLeScanClients.containsKey(callback)) { 256 return postCallbackErrorOrReturn( 257 callback, ScanCallback.SCAN_FAILED_ALREADY_STARTED); 258 } 259 IBluetoothScan scan = null; 260 IBluetoothGatt gatt = null; 261 if (Flags.scanManagerRefactor()) { 262 scan = mBluetoothAdapter.getBluetoothScan(); 263 if (scan == null) { 264 return postCallbackErrorOrReturn( 265 callback, ScanCallback.SCAN_FAILED_INTERNAL_ERROR); 266 } 267 } else { 268 gatt = mBluetoothAdapter.getBluetoothGatt(); 269 if (gatt == null) { 270 return postCallbackErrorOrReturn( 271 callback, ScanCallback.SCAN_FAILED_INTERNAL_ERROR); 272 } 273 } 274 if (!isSettingsConfigAllowedForScan(settings)) { 275 return postCallbackErrorOrReturn( 276 callback, ScanCallback.SCAN_FAILED_FEATURE_UNSUPPORTED); 277 } 278 if (!isHardwareResourcesAvailableForScan(settings)) { 279 return postCallbackErrorOrReturn( 280 callback, ScanCallback.SCAN_FAILED_OUT_OF_HARDWARE_RESOURCES); 281 } 282 if (!isSettingsAndFilterComboAllowed(settings, filters)) { 283 return postCallbackErrorOrReturn( 284 callback, ScanCallback.SCAN_FAILED_FEATURE_UNSUPPORTED); 285 } 286 if (callback != null) { 287 BleScanCallbackWrapper wrapper = 288 new BleScanCallbackWrapper( 289 gatt, scan, filters, settings, workSource, callback); 290 wrapper.startRegistration(); 291 } else { 292 try { 293 if (Flags.scanManagerRefactor()) { 294 scan.startScanForIntent( 295 callbackIntent, settings, filters, mAttributionSource); 296 } else { 297 gatt.startScanForIntent( 298 callbackIntent, settings, filters, mAttributionSource); 299 } 300 } catch (RemoteException e) { 301 return ScanCallback.SCAN_FAILED_INTERNAL_ERROR; 302 } 303 } 304 } 305 return ScanCallback.NO_ERROR; 306 } 307 308 /** Stops an ongoing Bluetooth LE scan. */ 309 @RequiresLegacyBluetoothAdminPermission 310 @RequiresBluetoothScanPermission 311 @RequiresPermission(android.Manifest.permission.BLUETOOTH_SCAN) stopScan(ScanCallback callback)312 public void stopScan(ScanCallback callback) { 313 BluetoothLeUtils.checkAdapterStateOn(mBluetoothAdapter); 314 synchronized (mLeScanClients) { 315 BleScanCallbackWrapper wrapper = mLeScanClients.remove(callback); 316 if (wrapper == null) { 317 if (DBG) Log.d(TAG, "could not find callback wrapper"); 318 return; 319 } 320 wrapper.stopLeScan(); 321 } 322 } 323 324 /** 325 * Stops an ongoing Bluetooth LE scan started using a PendingIntent. When creating the 326 * PendingIntent parameter, please do not use the FLAG_CANCEL_CURRENT flag. Otherwise, the stop 327 * scan may have no effect. 328 * 329 * @param callbackIntent The PendingIntent that was used to start the scan. 330 * @see #startScan(List, ScanSettings, PendingIntent) 331 */ 332 @RequiresLegacyBluetoothAdminPermission 333 @RequiresBluetoothScanPermission 334 @RequiresPermission(android.Manifest.permission.BLUETOOTH_SCAN) stopScan(PendingIntent callbackIntent)335 public void stopScan(PendingIntent callbackIntent) { 336 BluetoothLeUtils.checkAdapterStateOn(mBluetoothAdapter); 337 try { 338 if (Flags.scanManagerRefactor()) { 339 IBluetoothScan scan = mBluetoothAdapter.getBluetoothScan(); 340 if (scan == null) { 341 Log.w(TAG, "stopScan called after bluetooth has been turned off"); 342 return; 343 } 344 scan.stopScanForIntent(callbackIntent, mAttributionSource); 345 } else { 346 IBluetoothGatt gatt = mBluetoothAdapter.getBluetoothGatt(); 347 if (gatt == null) { 348 Log.w(TAG, "stopScan called after bluetooth has been turned off"); 349 return; 350 } 351 gatt.stopScanForIntent(callbackIntent, mAttributionSource); 352 } 353 } catch (RemoteException e) { 354 Log.e(TAG, "Failed to stop scan", e); 355 } 356 } 357 358 /** 359 * Flush pending batch scan results stored in Bluetooth controller. This will return Bluetooth 360 * LE scan results batched on bluetooth controller. Returns immediately, batch scan results data 361 * will be delivered through the {@code callback}. 362 * 363 * @param callback Callback of the Bluetooth LE Scan, it has to be the same instance as the one 364 * used to start scan. 365 */ 366 @RequiresLegacyBluetoothAdminPermission 367 @RequiresBluetoothScanPermission 368 @RequiresPermission(android.Manifest.permission.BLUETOOTH_SCAN) flushPendingScanResults(ScanCallback callback)369 public void flushPendingScanResults(ScanCallback callback) { 370 BluetoothLeUtils.checkAdapterStateOn(mBluetoothAdapter); 371 if (callback == null) { 372 throw new IllegalArgumentException("callback cannot be null!"); 373 } 374 synchronized (mLeScanClients) { 375 BleScanCallbackWrapper wrapper = mLeScanClients.get(callback); 376 if (wrapper == null) { 377 return; 378 } 379 wrapper.flushPendingBatchResults(); 380 } 381 } 382 383 /** 384 * Start truncated scan. 385 * 386 * @deprecated this is not used anywhere 387 * @hide 388 */ 389 @Deprecated 390 @SystemApi 391 @RequiresBluetoothScanPermission 392 @RequiresPermission(android.Manifest.permission.BLUETOOTH_SCAN) startTruncatedScan( List<TruncatedFilter> truncatedFilters, ScanSettings settings, final ScanCallback callback)393 public void startTruncatedScan( 394 List<TruncatedFilter> truncatedFilters, 395 ScanSettings settings, 396 final ScanCallback callback) { 397 int filterSize = truncatedFilters.size(); 398 List<ScanFilter> scanFilters = new ArrayList<ScanFilter>(filterSize); 399 for (TruncatedFilter filter : truncatedFilters) { 400 scanFilters.add(filter.getFilter()); 401 } 402 startScan(scanFilters, settings, null, callback, null); 403 } 404 405 /** 406 * Cleans up scan clients. Should be called when bluetooth is down. 407 * 408 * @hide 409 */ 410 @RequiresNoPermission cleanup()411 public void cleanup() { 412 mLeScanClients.clear(); 413 } 414 415 /** Bluetooth GATT interface callbacks */ 416 @SuppressLint("AndroidFrameworkRequiresPermission") 417 private class BleScanCallbackWrapper extends IScannerCallback.Stub { 418 private static final int REGISTRATION_CALLBACK_TIMEOUT_MILLIS = 2000; 419 420 private final ScanCallback mScanCallback; 421 private final List<ScanFilter> mFilters; 422 private final WorkSource mWorkSource; 423 private ScanSettings mSettings; 424 private IBluetoothGatt mBluetoothGatt; 425 private IBluetoothScan mBluetoothScan; 426 427 // mLeHandle 0: not registered 428 // -2: registration failed because app is scanning to frequently 429 // -1: scan stopped or registration failed 430 // > 0: registered and scan started 431 private int mScannerId; 432 BleScanCallbackWrapper( IBluetoothGatt bluetoothGatt, IBluetoothScan bluetoothScan, List<ScanFilter> filters, ScanSettings settings, WorkSource workSource, ScanCallback scanCallback)433 public BleScanCallbackWrapper( 434 IBluetoothGatt bluetoothGatt, 435 IBluetoothScan bluetoothScan, 436 List<ScanFilter> filters, 437 ScanSettings settings, 438 WorkSource workSource, 439 ScanCallback scanCallback) { 440 mBluetoothGatt = bluetoothGatt; 441 mBluetoothScan = bluetoothScan; 442 mFilters = filters; 443 mSettings = settings; 444 mWorkSource = workSource; 445 mScanCallback = scanCallback; 446 mScannerId = 0; 447 } 448 449 @SuppressWarnings("WaitNotInLoop") // TODO(b/314811467) startRegistration()450 public void startRegistration() { 451 synchronized (this) { 452 // Scan stopped. 453 if (mScannerId == -1 || mScannerId == -2) return; 454 try { 455 if (Flags.scanManagerRefactor()) { 456 mBluetoothScan.registerScanner(this, mWorkSource, mAttributionSource); 457 } else { 458 mBluetoothGatt.registerScanner(this, mWorkSource, mAttributionSource); 459 } 460 wait(REGISTRATION_CALLBACK_TIMEOUT_MILLIS); 461 } catch (InterruptedException | RemoteException e) { 462 Log.e(TAG, "application registration exception", e); 463 postCallbackError(mScanCallback, ScanCallback.SCAN_FAILED_INTERNAL_ERROR); 464 } 465 if (mScannerId > 0) { 466 mLeScanClients.put(mScanCallback, this); 467 } else { 468 // Registration timed out or got exception, reset scannerId to -1 so no 469 // subsequent operations can proceed. 470 if (mScannerId == 0) mScannerId = -1; 471 472 // If scanning too frequently, don't report anything to the app. 473 if (mScannerId == -2) { 474 Log.e(TAG, "registration failed because app is scanning too frequently"); 475 return; 476 } 477 478 postCallbackError( 479 mScanCallback, 480 ScanCallback.SCAN_FAILED_APPLICATION_REGISTRATION_FAILED); 481 } 482 } 483 } 484 485 @RequiresPermission(android.Manifest.permission.BLUETOOTH_SCAN) stopLeScan()486 public void stopLeScan() { 487 synchronized (this) { 488 if (mScannerId <= 0) { 489 Log.e(TAG, "Error state, mLeHandle: " + mScannerId); 490 return; 491 } 492 try { 493 if (Flags.scanManagerRefactor()) { 494 mBluetoothScan.stopScan(mScannerId, mAttributionSource); 495 496 mBluetoothScan.unregisterScanner(mScannerId, mAttributionSource); 497 } else { 498 mBluetoothGatt.stopScan(mScannerId, mAttributionSource); 499 500 mBluetoothGatt.unregisterScanner(mScannerId, mAttributionSource); 501 } 502 } catch (RemoteException e) { 503 Log.e(TAG, "Failed to stop scan and unregister", e); 504 } 505 mScannerId = -1; 506 } 507 } 508 509 @RequiresPermission(android.Manifest.permission.BLUETOOTH_SCAN) flushPendingBatchResults()510 void flushPendingBatchResults() { 511 synchronized (this) { 512 if (mScannerId <= 0) { 513 Log.e(TAG, "Error state, mLeHandle: " + mScannerId); 514 return; 515 } 516 try { 517 if (Flags.scanManagerRefactor()) { 518 mBluetoothScan.flushPendingBatchResults(mScannerId, mAttributionSource); 519 } else { 520 mBluetoothGatt.flushPendingBatchResults(mScannerId, mAttributionSource); 521 } 522 } catch (RemoteException e) { 523 Log.e(TAG, "Failed to get pending scan results", e); 524 } 525 } 526 } 527 528 /** Application interface registered - app is ready to go */ 529 @Override onScannerRegistered(int status, int scannerId)530 public void onScannerRegistered(int status, int scannerId) { 531 Log.d( 532 TAG, 533 "onScannerRegistered() - status=" 534 + status 535 + " scannerId=" 536 + scannerId 537 + " mScannerId=" 538 + mScannerId); 539 synchronized (this) { 540 if (status == BluetoothGatt.GATT_SUCCESS) { 541 try { 542 if (mScannerId == -1) { 543 // Registration succeeds after timeout, unregister scanner. 544 if (Flags.scanManagerRefactor()) { 545 mBluetoothScan.unregisterScanner(scannerId, mAttributionSource); 546 } else { 547 mBluetoothGatt.unregisterScanner(scannerId, mAttributionSource); 548 } 549 } else { 550 mScannerId = scannerId; 551 if (Flags.scanManagerRefactor()) { 552 mBluetoothScan.startScan( 553 mScannerId, mSettings, mFilters, mAttributionSource); 554 } else { 555 mBluetoothGatt.startScan( 556 mScannerId, mSettings, mFilters, mAttributionSource); 557 } 558 } 559 } catch (RemoteException e) { 560 Log.e(TAG, "fail to start le scan: " + e); 561 mScannerId = -1; 562 } 563 } else if (status == ScanCallback.SCAN_FAILED_SCANNING_TOO_FREQUENTLY) { 564 // application was scanning too frequently 565 mScannerId = -2; 566 } else { 567 // registration failed 568 mScannerId = -1; 569 } 570 notifyAll(); 571 } 572 } 573 574 /** 575 * Callback reporting an LE scan result. 576 * 577 * @hide 578 */ 579 @Override onScanResult(final ScanResult scanResult)580 public void onScanResult(final ScanResult scanResult) { 581 Attributable.setAttributionSource(scanResult, mAttributionSource); 582 if (Log.isLoggable(TAG, Log.DEBUG)) { 583 Log.d(TAG, "onScanResult() - mScannerId=" + mScannerId); 584 } 585 if (VDBG) Log.d(TAG, "onScanResult() - " + scanResult.toString()); 586 587 // Check null in case the scan has been stopped 588 synchronized (this) { 589 if (mScannerId <= 0) { 590 if (Log.isLoggable(TAG, Log.DEBUG)) { 591 Log.d(TAG, "Ignoring result as scan stopped."); 592 } 593 return; 594 } 595 ; 596 } 597 Handler handler = new Handler(Looper.getMainLooper()); 598 handler.post( 599 new Runnable() { 600 @Override 601 public void run() { 602 if (Log.isLoggable(TAG, Log.DEBUG)) { 603 Log.d(TAG, "onScanResult() - handler run"); 604 } 605 mScanCallback.onScanResult( 606 ScanSettings.CALLBACK_TYPE_ALL_MATCHES, scanResult); 607 } 608 }); 609 } 610 611 @Override onBatchScanResults(final List<ScanResult> results)612 public void onBatchScanResults(final List<ScanResult> results) { 613 Attributable.setAttributionSource(results, mAttributionSource); 614 Handler handler = new Handler(Looper.getMainLooper()); 615 handler.post( 616 new Runnable() { 617 @Override 618 public void run() { 619 mScanCallback.onBatchScanResults(results); 620 } 621 }); 622 } 623 624 @Override onFoundOrLost(final boolean onFound, final ScanResult scanResult)625 public void onFoundOrLost(final boolean onFound, final ScanResult scanResult) { 626 Attributable.setAttributionSource(scanResult, mAttributionSource); 627 if (VDBG) { 628 Log.d(TAG, "onFoundOrLost() - onFound = " + onFound + " " + scanResult.toString()); 629 } 630 631 // Check null in case the scan has been stopped 632 synchronized (this) { 633 if (mScannerId <= 0) { 634 return; 635 } 636 } 637 Handler handler = new Handler(Looper.getMainLooper()); 638 handler.post( 639 new Runnable() { 640 @Override 641 public void run() { 642 if (onFound) { 643 mScanCallback.onScanResult( 644 ScanSettings.CALLBACK_TYPE_FIRST_MATCH, scanResult); 645 } else { 646 mScanCallback.onScanResult( 647 ScanSettings.CALLBACK_TYPE_MATCH_LOST, scanResult); 648 } 649 } 650 }); 651 } 652 653 @Override onScanManagerErrorCallback(final int errorCode)654 public void onScanManagerErrorCallback(final int errorCode) { 655 if (VDBG) { 656 Log.d(TAG, "onScanManagerErrorCallback() - errorCode = " + errorCode); 657 } 658 synchronized (this) { 659 if (mScannerId <= 0) { 660 return; 661 } 662 } 663 postCallbackError(mScanCallback, errorCode); 664 } 665 } 666 postCallbackErrorOrReturn(final ScanCallback callback, final int errorCode)667 private int postCallbackErrorOrReturn(final ScanCallback callback, final int errorCode) { 668 if (callback == null) { 669 return errorCode; 670 } else { 671 postCallbackError(callback, errorCode); 672 return ScanCallback.NO_ERROR; 673 } 674 } 675 676 @SuppressLint("AndroidFrameworkBluetoothPermission") postCallbackError(final ScanCallback callback, final int errorCode)677 private void postCallbackError(final ScanCallback callback, final int errorCode) { 678 mHandler.post( 679 new Runnable() { 680 @Override 681 public void run() { 682 callback.onScanFailed(errorCode); 683 } 684 }); 685 } 686 isSettingsConfigAllowedForScan(ScanSettings settings)687 private boolean isSettingsConfigAllowedForScan(ScanSettings settings) { 688 if (mBluetoothAdapter.isOffloadedFilteringSupported()) { 689 return true; 690 } 691 final int callbackType = settings.getCallbackType(); 692 // Only support regular scan if no offloaded filter support. 693 if (callbackType == ScanSettings.CALLBACK_TYPE_ALL_MATCHES 694 && settings.getReportDelayMillis() == 0) { 695 return true; 696 } 697 return false; 698 } 699 isSettingsAndFilterComboAllowed( ScanSettings settings, List<ScanFilter> filterList)700 private boolean isSettingsAndFilterComboAllowed( 701 ScanSettings settings, List<ScanFilter> filterList) { 702 final int callbackType = settings.getCallbackType(); 703 // If onlost/onfound is requested, a non-empty filter is expected 704 if ((callbackType 705 & (ScanSettings.CALLBACK_TYPE_FIRST_MATCH 706 | ScanSettings.CALLBACK_TYPE_MATCH_LOST)) 707 != 0) { 708 if (filterList == null) { 709 return false; 710 } 711 for (ScanFilter filter : filterList) { 712 if (filter.isAllFieldsEmpty()) { 713 return false; 714 } 715 } 716 } 717 return true; 718 } 719 720 @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) isHardwareResourcesAvailableForScan(ScanSettings settings)721 private boolean isHardwareResourcesAvailableForScan(ScanSettings settings) { 722 final int callbackType = settings.getCallbackType(); 723 if ((callbackType & ScanSettings.CALLBACK_TYPE_FIRST_MATCH) != 0 724 || (callbackType & ScanSettings.CALLBACK_TYPE_MATCH_LOST) != 0) { 725 // For onlost/onfound, we required hw support be available 726 return (mBluetoothAdapter.isOffloadedFilteringSupported() 727 && mBluetoothAdapter.isHardwareTrackingFiltersAvailable()); 728 } 729 return true; 730 } 731 } 732