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