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