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