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 static android.Manifest.permission.BLUETOOTH_SCAN; 20 import static android.Manifest.permission.UPDATE_DEVICE_STATS; 21 22 import static java.util.Objects.requireNonNull; 23 24 import android.annotation.NonNull; 25 import android.annotation.Nullable; 26 import android.annotation.RequiresNoPermission; 27 import android.annotation.RequiresPermission; 28 import android.annotation.SuppressLint; 29 import android.annotation.SystemApi; 30 import android.app.PendingIntent; 31 import android.bluetooth.Attributable; 32 import android.bluetooth.BluetoothAdapter; 33 import android.bluetooth.BluetoothGatt; 34 import android.bluetooth.IBluetoothScan; 35 import android.bluetooth.annotations.RequiresBluetoothLocationPermission; 36 import android.bluetooth.annotations.RequiresBluetoothScanPermission; 37 import android.bluetooth.annotations.RequiresLegacyBluetoothAdminPermission; 38 import android.content.AttributionSource; 39 import android.os.Handler; 40 import android.os.Looper; 41 import android.os.RemoteException; 42 import android.os.WorkSource; 43 import android.util.Log; 44 45 import java.util.ArrayList; 46 import java.util.HashMap; 47 import java.util.List; 48 import java.util.Map; 49 50 /** 51 * This class provides methods to perform scan related operations for Bluetooth LE devices. An 52 * application can scan for a particular type of Bluetooth LE devices using {@link ScanFilter}. It 53 * can also request different types of callbacks for delivering the result. 54 * 55 * <p>Use {@link BluetoothAdapter#getBluetoothLeScanner()} to get an instance of {@link 56 * BluetoothLeScanner}. 57 * 58 * @see ScanFilter 59 */ 60 public final class BluetoothLeScanner { 61 private static final String TAG = BluetoothLeScanner.class.getSimpleName(); 62 63 private static final boolean DBG = true; 64 private static final boolean VDBG = false; 65 66 /** 67 * Extra containing a list of ScanResults. It can have one or more results if there was no 68 * error. In case of error, {@link #EXTRA_ERROR_CODE} will contain the error code and this extra 69 * will not be available. 70 */ 71 public static final String EXTRA_LIST_SCAN_RESULT = 72 "android.bluetooth.le.extra.LIST_SCAN_RESULT"; 73 74 /** 75 * Optional extra indicating the error code, if any. The error code will be one of the 76 * SCAN_FAILED_* codes in {@link ScanCallback}. 77 */ 78 public static final String EXTRA_ERROR_CODE = "android.bluetooth.le.extra.ERROR_CODE"; 79 80 /** 81 * Optional extra indicating the callback type, which will be one of CALLBACK_TYPE_* constants 82 * in {@link ScanSettings}. 83 * 84 * @see ScanCallback#onScanResult(int, ScanResult) 85 */ 86 public static final String EXTRA_CALLBACK_TYPE = "android.bluetooth.le.extra.CALLBACK_TYPE"; 87 88 private final BluetoothAdapter mBluetoothAdapter; 89 private final AttributionSource mAttributionSource; 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 = 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(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(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(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(allOf = {BLUETOOTH_SCAN, UPDATE_DEVICE_STATS}) startScanFromSource(final WorkSource workSource, final ScanCallback callback)203 public void startScanFromSource(final WorkSource workSource, final ScanCallback callback) { 204 startScanFromSource(null, new ScanSettings.Builder().build(), workSource, callback); 205 } 206 207 /** 208 * Start Bluetooth LE scan. Same as {@link #startScan(List, ScanSettings, ScanCallback)} but 209 * allows the caller to specify on behalf of which application(s) the work is being done. 210 * 211 * @param filters {@link ScanFilter}s for finding exact BLE devices. 212 * @param settings Settings for the scan. 213 * @param workSource {@link WorkSource} identifying the application(s) for which to blame for 214 * the scan. 215 * @param callback Callback used to deliver scan results. 216 * @hide 217 */ 218 @SystemApi 219 @RequiresLegacyBluetoothAdminPermission 220 @RequiresBluetoothScanPermission 221 @RequiresBluetoothLocationPermission 222 @RequiresPermission(allOf = {BLUETOOTH_SCAN, UPDATE_DEVICE_STATS}) 223 @SuppressLint("AndroidFrameworkRequiresPermission") startScanFromSource( List<ScanFilter> filters, ScanSettings settings, final WorkSource workSource, final ScanCallback callback)224 public void startScanFromSource( 225 List<ScanFilter> filters, 226 ScanSettings settings, 227 final WorkSource workSource, 228 final ScanCallback callback) { 229 startScan(filters, settings, workSource, callback, null); 230 } 231 232 @RequiresPermission(BLUETOOTH_SCAN) startScan( List<ScanFilter> filters, ScanSettings settings, final WorkSource workSource, final ScanCallback callback, final PendingIntent callbackIntent)233 private int startScan( 234 List<ScanFilter> filters, 235 ScanSettings settings, 236 final WorkSource workSource, 237 final ScanCallback callback, 238 final PendingIntent callbackIntent) { 239 BluetoothLeUtils.checkAdapterStateOn(mBluetoothAdapter); 240 if (callback == null && callbackIntent == null) { 241 throw new IllegalArgumentException("callback is null"); 242 } 243 if (settings == null) { 244 throw new IllegalArgumentException("settings is null"); 245 } 246 synchronized (mLeScanClients) { 247 if (callback != null && mLeScanClients.containsKey(callback)) { 248 return postCallbackErrorOrReturn( 249 callback, ScanCallback.SCAN_FAILED_ALREADY_STARTED); 250 } 251 IBluetoothScan scan = mBluetoothAdapter.getBluetoothScan(); 252 if (scan == null) { 253 return postCallbackErrorOrReturn(callback, ScanCallback.SCAN_FAILED_INTERNAL_ERROR); 254 } 255 if (!isSettingsConfigAllowedForScan(settings)) { 256 return postCallbackErrorOrReturn( 257 callback, ScanCallback.SCAN_FAILED_FEATURE_UNSUPPORTED); 258 } 259 if (!isHardwareResourcesAvailableForScan(settings)) { 260 return postCallbackErrorOrReturn( 261 callback, ScanCallback.SCAN_FAILED_OUT_OF_HARDWARE_RESOURCES); 262 } 263 if (!isSettingsAndFilterComboAllowed(settings, filters)) { 264 return postCallbackErrorOrReturn( 265 callback, ScanCallback.SCAN_FAILED_FEATURE_UNSUPPORTED); 266 } 267 if (callback != null) { 268 BleScanCallbackWrapper wrapper = 269 new BleScanCallbackWrapper(scan, filters, settings, workSource, callback); 270 wrapper.startRegistration(); 271 } else { 272 try { 273 scan.startScanForIntent(callbackIntent, settings, filters, mAttributionSource); 274 } catch (RemoteException e) { 275 return ScanCallback.SCAN_FAILED_INTERNAL_ERROR; 276 } 277 } 278 } 279 return ScanCallback.NO_ERROR; 280 } 281 282 /** Stops an ongoing Bluetooth LE scan. */ 283 @RequiresLegacyBluetoothAdminPermission 284 @RequiresBluetoothScanPermission 285 @RequiresPermission(BLUETOOTH_SCAN) stopScan(ScanCallback callback)286 public void stopScan(ScanCallback callback) { 287 BluetoothLeUtils.checkAdapterStateOn(mBluetoothAdapter); 288 synchronized (mLeScanClients) { 289 BleScanCallbackWrapper wrapper = mLeScanClients.remove(callback); 290 if (wrapper == null) { 291 if (DBG) Log.d(TAG, "could not find callback wrapper"); 292 return; 293 } 294 wrapper.stopLeScan(); 295 } 296 } 297 298 /** 299 * Stops an ongoing Bluetooth LE scan started using a PendingIntent. When creating the 300 * PendingIntent parameter, please do not use the FLAG_CANCEL_CURRENT flag. Otherwise, the stop 301 * scan may have no effect. 302 * 303 * @param callbackIntent The PendingIntent that was used to start the scan. 304 * @see #startScan(List, ScanSettings, PendingIntent) 305 */ 306 @RequiresLegacyBluetoothAdminPermission 307 @RequiresBluetoothScanPermission 308 @RequiresPermission(BLUETOOTH_SCAN) stopScan(PendingIntent callbackIntent)309 public void stopScan(PendingIntent callbackIntent) { 310 BluetoothLeUtils.checkAdapterStateOn(mBluetoothAdapter); 311 try { 312 IBluetoothScan scan = mBluetoothAdapter.getBluetoothScan(); 313 if (scan == null) { 314 Log.w(TAG, "stopScan called after bluetooth has been turned off"); 315 return; 316 } 317 scan.stopScanForIntent(callbackIntent, mAttributionSource); 318 } catch (RemoteException e) { 319 Log.e(TAG, "Failed to stop scan", e); 320 } 321 } 322 323 /** 324 * Flush pending batch scan results stored in Bluetooth controller. This will return Bluetooth 325 * LE scan results batched on bluetooth controller. Returns immediately, batch scan results data 326 * will be delivered through the {@code callback}. 327 * 328 * @param callback Callback of the Bluetooth LE Scan, it has to be the same instance as the one 329 * used to start scan. 330 */ 331 @RequiresLegacyBluetoothAdminPermission 332 @RequiresBluetoothScanPermission 333 @RequiresPermission(BLUETOOTH_SCAN) flushPendingScanResults(ScanCallback callback)334 public void flushPendingScanResults(ScanCallback callback) { 335 BluetoothLeUtils.checkAdapterStateOn(mBluetoothAdapter); 336 if (callback == null) { 337 throw new IllegalArgumentException("callback cannot be null!"); 338 } 339 synchronized (mLeScanClients) { 340 BleScanCallbackWrapper wrapper = mLeScanClients.get(callback); 341 if (wrapper == null) { 342 return; 343 } 344 wrapper.flushPendingBatchResults(); 345 } 346 } 347 348 /** 349 * Start truncated scan. 350 * 351 * @removed this is not used anywhere 352 * @hide 353 */ 354 @Deprecated 355 @SystemApi 356 @RequiresBluetoothScanPermission 357 @RequiresPermission(BLUETOOTH_SCAN) startTruncatedScan( List<TruncatedFilter> truncatedFilters, ScanSettings settings, final ScanCallback callback)358 public void startTruncatedScan( 359 List<TruncatedFilter> truncatedFilters, 360 ScanSettings settings, 361 final ScanCallback callback) { 362 int filterSize = truncatedFilters.size(); 363 List<ScanFilter> scanFilters = new ArrayList<ScanFilter>(filterSize); 364 for (TruncatedFilter filter : truncatedFilters) { 365 scanFilters.add(filter.getFilter()); 366 } 367 startScan(scanFilters, settings, null, callback, null); 368 } 369 370 /** 371 * Cleans up scan clients. Should be called when bluetooth is down. 372 * 373 * @hide 374 */ 375 @RequiresNoPermission cleanup()376 public void cleanup() { 377 mLeScanClients.clear(); 378 } 379 380 /** Bluetooth GATT interface callbacks */ 381 @SuppressLint("AndroidFrameworkRequiresPermission") 382 private final class BleScanCallbackWrapper extends IScannerCallback.Stub { 383 private static final int REGISTRATION_CALLBACK_TIMEOUT_MILLIS = 2000; 384 385 private final ScanCallback mScanCallback; 386 private final List<ScanFilter> mFilters; 387 private final WorkSource mWorkSource; 388 private final ScanSettings mSettings; 389 private final IBluetoothScan mBluetoothScan; 390 391 // mLeHandle 0: not registered 392 // -2: registration failed because app is scanning to frequently 393 // -1: scan stopped or registration failed 394 // > 0: registered and scan started 395 private int mScannerId; 396 BleScanCallbackWrapper( IBluetoothScan bluetoothScan, List<ScanFilter> filters, ScanSettings settings, WorkSource workSource, ScanCallback scanCallback)397 BleScanCallbackWrapper( 398 IBluetoothScan bluetoothScan, 399 List<ScanFilter> filters, 400 ScanSettings settings, 401 WorkSource workSource, 402 ScanCallback scanCallback) { 403 mBluetoothScan = bluetoothScan; 404 mFilters = filters; 405 mSettings = settings; 406 mWorkSource = workSource; 407 mScanCallback = scanCallback; 408 mScannerId = 0; 409 } 410 411 @SuppressWarnings("WaitNotInLoop") // TODO(b/314811467) startRegistration()412 void startRegistration() { 413 synchronized (this) { 414 // Scan stopped. 415 if (mScannerId == -1 || mScannerId == -2) return; 416 try { 417 mBluetoothScan.registerScanner(this, mWorkSource, mAttributionSource); 418 wait(REGISTRATION_CALLBACK_TIMEOUT_MILLIS); 419 } catch (InterruptedException | RemoteException e) { 420 Log.e(TAG, "application registration exception", e); 421 postCallbackError(mScanCallback, ScanCallback.SCAN_FAILED_INTERNAL_ERROR); 422 } 423 if (mScannerId > 0) { 424 mLeScanClients.put(mScanCallback, this); 425 } else { 426 // Registration timed out or got exception, reset scannerId to -1 so no 427 // subsequent operations can proceed. 428 if (mScannerId == 0) mScannerId = -1; 429 430 // If scanning too frequently, don't report anything to the app. 431 if (mScannerId == -2) { 432 Log.e(TAG, "registration failed because app is scanning too frequently"); 433 return; 434 } 435 436 postCallbackError( 437 mScanCallback, 438 ScanCallback.SCAN_FAILED_APPLICATION_REGISTRATION_FAILED); 439 } 440 } 441 } 442 443 @RequiresPermission(BLUETOOTH_SCAN) stopLeScan()444 void stopLeScan() { 445 synchronized (this) { 446 if (mScannerId <= 0) { 447 Log.e(TAG, "Error state, mLeHandle: " + mScannerId); 448 return; 449 } 450 try { 451 mBluetoothScan.stopScan(mScannerId, mAttributionSource); 452 mBluetoothScan.unregisterScanner(mScannerId, mAttributionSource); 453 } catch (RemoteException e) { 454 Log.e(TAG, "Failed to stop scan and unregister", e); 455 } 456 mScannerId = -1; 457 } 458 } 459 460 @RequiresPermission(BLUETOOTH_SCAN) flushPendingBatchResults()461 void flushPendingBatchResults() { 462 synchronized (this) { 463 if (mScannerId <= 0) { 464 Log.e(TAG, "Error state, mLeHandle: " + mScannerId); 465 return; 466 } 467 try { 468 mBluetoothScan.flushPendingBatchResults(mScannerId, mAttributionSource); 469 } catch (RemoteException e) { 470 Log.e(TAG, "Failed to get pending scan results", e); 471 } 472 } 473 } 474 475 /** Application interface registered - app is ready to go */ 476 @Override onScannerRegistered(int status, int scannerId)477 public void onScannerRegistered(int status, int scannerId) { 478 Log.d( 479 TAG, 480 "onScannerRegistered() - status=" 481 + status 482 + " scannerId=" 483 + scannerId 484 + " mScannerId=" 485 + mScannerId); 486 synchronized (this) { 487 if (status == BluetoothGatt.GATT_SUCCESS) { 488 try { 489 if (mScannerId == -1) { 490 // Registration succeeds after timeout, unregister scanner. 491 mBluetoothScan.unregisterScanner(scannerId, mAttributionSource); 492 } else { 493 mScannerId = scannerId; 494 mBluetoothScan.startScan( 495 mScannerId, mSettings, mFilters, mAttributionSource); 496 } 497 } catch (RemoteException e) { 498 Log.e(TAG, "fail to start le scan: " + e); 499 mScannerId = -1; 500 } 501 } else if (status == ScanCallback.SCAN_FAILED_SCANNING_TOO_FREQUENTLY) { 502 // application was scanning too frequently 503 mScannerId = -2; 504 } else { 505 // registration failed 506 mScannerId = -1; 507 } 508 notifyAll(); 509 } 510 } 511 512 /** 513 * Callback reporting an LE scan result. 514 * 515 * @hide 516 */ 517 @Override onScanResult(final ScanResult scanResult)518 public void onScanResult(final ScanResult scanResult) { 519 Attributable.setAttributionSource(scanResult, mAttributionSource); 520 if (Log.isLoggable(TAG, Log.DEBUG)) { 521 Log.d(TAG, "onScanResult() - mScannerId=" + mScannerId); 522 } 523 if (VDBG) Log.d(TAG, "onScanResult() - " + scanResult.toString()); 524 525 // Check null in case the scan has been stopped 526 synchronized (this) { 527 if (mScannerId <= 0) { 528 if (Log.isLoggable(TAG, Log.DEBUG)) { 529 Log.d(TAG, "Ignoring result as scan stopped."); 530 } 531 return; 532 } 533 ; 534 } 535 mHandler.post( 536 () -> { 537 if (Log.isLoggable(TAG, Log.DEBUG)) { 538 Log.d(TAG, "onScanResult() - handler run"); 539 } 540 mScanCallback.onScanResult( 541 ScanSettings.CALLBACK_TYPE_ALL_MATCHES, scanResult); 542 }); 543 } 544 545 @Override onBatchScanResults(final List<ScanResult> results)546 public void onBatchScanResults(final List<ScanResult> results) { 547 Attributable.setAttributionSource(results, mAttributionSource); 548 mHandler.post(() -> mScanCallback.onBatchScanResults(results)); 549 } 550 551 @Override onFoundOrLost(final boolean onFound, final ScanResult scanResult)552 public void onFoundOrLost(final boolean onFound, final ScanResult scanResult) { 553 Attributable.setAttributionSource(scanResult, mAttributionSource); 554 if (VDBG) { 555 Log.d(TAG, "onFoundOrLost() - onFound = " + onFound + " " + scanResult.toString()); 556 } 557 558 // Check null in case the scan has been stopped 559 synchronized (this) { 560 if (mScannerId <= 0) { 561 return; 562 } 563 } 564 int callbackType = 565 onFound 566 ? ScanSettings.CALLBACK_TYPE_FIRST_MATCH 567 : ScanSettings.CALLBACK_TYPE_MATCH_LOST; 568 mHandler.post(() -> mScanCallback.onScanResult(callbackType, scanResult)); 569 } 570 571 @Override onScanManagerErrorCallback(final int errorCode)572 public void onScanManagerErrorCallback(final int errorCode) { 573 if (VDBG) { 574 Log.d(TAG, "onScanManagerErrorCallback() - errorCode = " + errorCode); 575 } 576 synchronized (this) { 577 if (mScannerId <= 0) { 578 return; 579 } 580 } 581 postCallbackError(mScanCallback, errorCode); 582 } 583 } 584 postCallbackErrorOrReturn(final ScanCallback callback, final int errorCode)585 private int postCallbackErrorOrReturn(final ScanCallback callback, final int errorCode) { 586 if (callback == null) { 587 return errorCode; 588 } else { 589 postCallbackError(callback, errorCode); 590 return ScanCallback.NO_ERROR; 591 } 592 } 593 594 @SuppressLint("AndroidFrameworkBluetoothPermission") postCallbackError(final ScanCallback callback, final int errorCode)595 private void postCallbackError(final ScanCallback callback, final int errorCode) { 596 mHandler.post(() -> callback.onScanFailed(errorCode)); 597 } 598 isSettingsConfigAllowedForScan(ScanSettings settings)599 private boolean isSettingsConfigAllowedForScan(ScanSettings settings) { 600 if (mBluetoothAdapter.isOffloadedFilteringSupported()) { 601 return true; 602 } 603 final int callbackType = settings.getCallbackType(); 604 // Only support regular scan if no offloaded filter support. 605 if (callbackType == ScanSettings.CALLBACK_TYPE_ALL_MATCHES 606 && settings.getReportDelayMillis() == 0) { 607 return true; 608 } 609 return false; 610 } 611 isSettingsAndFilterComboAllowed( ScanSettings settings, List<ScanFilter> filterList)612 private static boolean isSettingsAndFilterComboAllowed( 613 ScanSettings settings, List<ScanFilter> filterList) { 614 final int callbackType = settings.getCallbackType(); 615 // If onlost/onfound is requested, a non-empty filter is expected 616 if ((callbackType 617 & (ScanSettings.CALLBACK_TYPE_FIRST_MATCH 618 | ScanSettings.CALLBACK_TYPE_MATCH_LOST)) 619 != 0) { 620 if (filterList == null) { 621 return false; 622 } 623 for (ScanFilter filter : filterList) { 624 if (filter.isAllFieldsEmpty()) { 625 return false; 626 } 627 } 628 } 629 return true; 630 } 631 632 @RequiresPermission(BLUETOOTH_SCAN) isHardwareResourcesAvailableForScan(ScanSettings settings)633 private boolean isHardwareResourcesAvailableForScan(ScanSettings settings) { 634 final int callbackType = settings.getCallbackType(); 635 if ((callbackType & ScanSettings.CALLBACK_TYPE_FIRST_MATCH) != 0 636 || (callbackType & ScanSettings.CALLBACK_TYPE_MATCH_LOST) != 0) { 637 // For onlost/onfound, we required hw support be available 638 return (mBluetoothAdapter.isOffloadedFilteringSupported() 639 && mBluetoothAdapter.isHardwareTrackingFiltersAvailable()); 640 } 641 return true; 642 } 643 } 644