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