• 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 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