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