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.annotation.SystemApi;
     20 import android.bluetooth.BluetoothAdapter;
     21 import android.bluetooth.BluetoothGatt;
     22 import android.bluetooth.BluetoothGattCallbackWrapper;
     23 import android.bluetooth.IBluetoothGatt;
     24 import android.bluetooth.IBluetoothManager;
     25 import android.os.Handler;
     26 import android.os.Looper;
     27 import android.os.ParcelUuid;
     28 import android.os.RemoteException;
     29 import android.util.Log;
     30 
     31 import java.util.ArrayList;
     32 import java.util.HashMap;
     33 import java.util.List;
     34 import java.util.Map;
     35 import java.util.UUID;
     36 
     37 /**
     38  * This class provides methods to perform scan related operations for Bluetooth LE devices. An
     39  * application can scan for a particular type of Bluetotoh LE devices using {@link ScanFilter}. It
     40  * can also request different types of callbacks for delivering the result.
     41  * <p>
     42  * Use {@link BluetoothAdapter#getBluetoothLeScanner()} to get an instance of
     43  * {@link BluetoothLeScanner}.
     44  * <p>
     45  * <b>Note:</b> Most of the scan methods here require
     46  * {@link android.Manifest.permission#BLUETOOTH_ADMIN} permission.
     47  *
     48  * @see ScanFilter
     49  */
     50 public final class BluetoothLeScanner {
     51 
     52     private static final String TAG = "BluetoothLeScanner";
     53     private static final boolean DBG = true;
     54 
     55     private final IBluetoothManager mBluetoothManager;
     56     private final Handler mHandler;
     57     private BluetoothAdapter mBluetoothAdapter;
     58     private final Map<ScanCallback, BleScanCallbackWrapper> mLeScanClients;
     59 
     60     /**
     61      * Use {@link BluetoothAdapter#getBluetoothLeScanner()} instead.
     62      *
     63      * @param bluetoothManager BluetoothManager that conducts overall Bluetooth Management.
     64      * @hide
     65      */
     66     public BluetoothLeScanner(IBluetoothManager bluetoothManager) {
     67         mBluetoothManager = bluetoothManager;
     68         mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
     69         mHandler = new Handler(Looper.getMainLooper());
     70         mLeScanClients = new HashMap<ScanCallback, BleScanCallbackWrapper>();
     71     }
     72 
     73     /**
     74      * Start Bluetooth LE scan with default parameters and no filters. The scan results will be
     75      * delivered through {@code callback}.
     76      * <p>
     77      * Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN} permission.
     78      *
     79      * @param callback Callback used to deliver scan results.
     80      * @throws IllegalArgumentException If {@code callback} is null.
     81      */
     82     public void startScan(final ScanCallback callback) {
     83         if (callback == null) {
     84             throw new IllegalArgumentException("callback is null");
     85         }
     86         startScan(null, new ScanSettings.Builder().build(), callback);
     87     }
     88 
     89     /**
     90      * Start Bluetooth LE scan. The scan results will be delivered through {@code callback}.
     91      * <p>
     92      * Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN} permission.
     93      *
     94      * @param filters {@link ScanFilter}s for finding exact BLE devices.
     95      * @param settings Settings for the scan.
     96      * @param callback Callback used to deliver scan results.
     97      * @throws IllegalArgumentException If {@code settings} or {@code callback} is null.
     98      */
     99     public void startScan(List<ScanFilter> filters, ScanSettings settings,
    100             final ScanCallback callback) {
    101         startScan(filters, settings, callback, null);
    102     }
    103 
    104     private void startScan(List<ScanFilter> filters, ScanSettings settings,
    105             final ScanCallback callback, List<List<ResultStorageDescriptor>> resultStorages) {
    106         BluetoothLeUtils.checkAdapterStateOn(mBluetoothAdapter);
    107         if (settings == null || callback == null) {
    108             throw new IllegalArgumentException("settings or callback is null");
    109         }
    110         synchronized (mLeScanClients) {
    111             if (mLeScanClients.containsKey(callback)) {
    112                 postCallbackError(callback, ScanCallback.SCAN_FAILED_ALREADY_STARTED);
    113                 return;
    114             }
    115             IBluetoothGatt gatt;
    116             try {
    117                 gatt = mBluetoothManager.getBluetoothGatt();
    118             } catch (RemoteException e) {
    119                 gatt = null;
    120             }
    121             if (gatt == null) {
    122                 postCallbackError(callback, ScanCallback.SCAN_FAILED_INTERNAL_ERROR);
    123                 return;
    124             }
    125             if (!isSettingsConfigAllowedForScan(settings)) {
    126                 postCallbackError(callback,
    127                         ScanCallback.SCAN_FAILED_FEATURE_UNSUPPORTED);
    128                 return;
    129             }
    130             BleScanCallbackWrapper wrapper = new BleScanCallbackWrapper(gatt, filters,
    131                     settings, callback, resultStorages);
    132             wrapper.startRegisteration();
    133         }
    134     }
    135 
    136     /**
    137      * Stops an ongoing Bluetooth LE scan.
    138      * <p>
    139      * Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN} permission.
    140      *
    141      * @param callback
    142      */
    143     public void stopScan(ScanCallback callback) {
    144         BluetoothLeUtils.checkAdapterStateOn(mBluetoothAdapter);
    145         synchronized (mLeScanClients) {
    146             BleScanCallbackWrapper wrapper = mLeScanClients.remove(callback);
    147             if (wrapper == null) {
    148                 if (DBG) Log.d(TAG, "could not find callback wrapper");
    149                 return;
    150             }
    151             wrapper.stopLeScan();
    152         }
    153     }
    154 
    155     /**
    156      * Flush pending batch scan results stored in Bluetooth controller. This will return Bluetooth
    157      * LE scan results batched on bluetooth controller. Returns immediately, batch scan results data
    158      * will be delivered through the {@code callback}.
    159      *
    160      * @param callback Callback of the Bluetooth LE Scan, it has to be the same instance as the one
    161      *            used to start scan.
    162      */
    163     public void flushPendingScanResults(ScanCallback callback) {
    164         BluetoothLeUtils.checkAdapterStateOn(mBluetoothAdapter);
    165         if (callback == null) {
    166             throw new IllegalArgumentException("callback cannot be null!");
    167         }
    168         synchronized (mLeScanClients) {
    169             BleScanCallbackWrapper wrapper = mLeScanClients.get(callback);
    170             if (wrapper == null) {
    171                 return;
    172             }
    173             wrapper.flushPendingBatchResults();
    174         }
    175     }
    176 
    177     /**
    178      * Start truncated scan.
    179      *
    180      * @hide
    181      */
    182     @SystemApi
    183     public void startTruncatedScan(List<TruncatedFilter> truncatedFilters, ScanSettings settings,
    184             final ScanCallback callback) {
    185         int filterSize = truncatedFilters.size();
    186         List<ScanFilter> scanFilters = new ArrayList<ScanFilter>(filterSize);
    187         List<List<ResultStorageDescriptor>> scanStorages =
    188                 new ArrayList<List<ResultStorageDescriptor>>(filterSize);
    189         for (TruncatedFilter filter : truncatedFilters) {
    190             scanFilters.add(filter.getFilter());
    191             scanStorages.add(filter.getStorageDescriptors());
    192         }
    193         startScan(scanFilters, settings, callback, scanStorages);
    194     }
    195 
    196     /**
    197      * Cleans up scan clients. Should be called when bluetooth is down.
    198      *
    199      * @hide
    200      */
    201     public void cleanup() {
    202         mLeScanClients.clear();
    203     }
    204 
    205     /**
    206      * Bluetooth GATT interface callbacks
    207      */
    208     private class BleScanCallbackWrapper extends BluetoothGattCallbackWrapper {
    209         private static final int REGISTRATION_CALLBACK_TIMEOUT_MILLIS = 2000;
    210 
    211         private final ScanCallback mScanCallback;
    212         private final List<ScanFilter> mFilters;
    213         private ScanSettings mSettings;
    214         private IBluetoothGatt mBluetoothGatt;
    215         private List<List<ResultStorageDescriptor>> mResultStorages;
    216 
    217         // mLeHandle 0: not registered
    218         // -1: scan stopped
    219         // > 0: registered and scan started
    220         private int mClientIf;
    221 
    222         public BleScanCallbackWrapper(IBluetoothGatt bluetoothGatt,
    223                 List<ScanFilter> filters, ScanSettings settings,
    224                 ScanCallback scanCallback, List<List<ResultStorageDescriptor>> resultStorages) {
    225             mBluetoothGatt = bluetoothGatt;
    226             mFilters = filters;
    227             mSettings = settings;
    228             mScanCallback = scanCallback;
    229             mClientIf = 0;
    230             mResultStorages = resultStorages;
    231         }
    232 
    233         public void startRegisteration() {
    234             synchronized (this) {
    235                 // Scan stopped.
    236                 if (mClientIf == -1) return;
    237                 try {
    238                     UUID uuid = UUID.randomUUID();
    239                     mBluetoothGatt.registerClient(new ParcelUuid(uuid), this);
    240                     wait(REGISTRATION_CALLBACK_TIMEOUT_MILLIS);
    241                 } catch (InterruptedException | RemoteException e) {
    242                     Log.e(TAG, "application registeration exception", e);
    243                     postCallbackError(mScanCallback, ScanCallback.SCAN_FAILED_INTERNAL_ERROR);
    244                 }
    245                 if (mClientIf > 0) {
    246                     mLeScanClients.put(mScanCallback, this);
    247                 } else {
    248                     postCallbackError(mScanCallback,
    249                             ScanCallback.SCAN_FAILED_APPLICATION_REGISTRATION_FAILED);
    250                 }
    251             }
    252         }
    253 
    254         public void stopLeScan() {
    255             synchronized (this) {
    256                 if (mClientIf <= 0) {
    257                     Log.e(TAG, "Error state, mLeHandle: " + mClientIf);
    258                     return;
    259                 }
    260                 try {
    261                     mBluetoothGatt.stopScan(mClientIf, false);
    262                     mBluetoothGatt.unregisterClient(mClientIf);
    263                 } catch (RemoteException e) {
    264                     Log.e(TAG, "Failed to stop scan and unregister", e);
    265                 }
    266                 mClientIf = -1;
    267             }
    268         }
    269 
    270         void flushPendingBatchResults() {
    271             synchronized (this) {
    272                 if (mClientIf <= 0) {
    273                     Log.e(TAG, "Error state, mLeHandle: " + mClientIf);
    274                     return;
    275                 }
    276                 try {
    277                     mBluetoothGatt.flushPendingBatchResults(mClientIf, false);
    278                 } catch (RemoteException e) {
    279                     Log.e(TAG, "Failed to get pending scan results", e);
    280                 }
    281             }
    282         }
    283 
    284         /**
    285          * Application interface registered - app is ready to go
    286          */
    287         @Override
    288         public void onClientRegistered(int status, int clientIf) {
    289             Log.d(TAG, "onClientRegistered() - status=" + status +
    290                     " clientIf=" + clientIf);
    291             synchronized (this) {
    292                 if (mClientIf == -1) {
    293                     if (DBG) Log.d(TAG, "onClientRegistered LE scan canceled");
    294                 }
    295 
    296                 if (status == BluetoothGatt.GATT_SUCCESS) {
    297                     mClientIf = clientIf;
    298                     try {
    299                         mBluetoothGatt.startScan(mClientIf, false, mSettings, mFilters,
    300                                 mResultStorages);
    301                     } catch (RemoteException e) {
    302                         Log.e(TAG, "fail to start le scan: " + e);
    303                         mClientIf = -1;
    304                     }
    305                 } else {
    306                     // registration failed
    307                     mClientIf = -1;
    308                 }
    309                 notifyAll();
    310             }
    311         }
    312 
    313         /**
    314          * Callback reporting an LE scan result.
    315          *
    316          * @hide
    317          */
    318         @Override
    319         public void onScanResult(final ScanResult scanResult) {
    320             if (DBG) Log.d(TAG, "onScanResult() - " + scanResult.toString());
    321 
    322             // Check null in case the scan has been stopped
    323             synchronized (this) {
    324                 if (mClientIf <= 0) return;
    325             }
    326             Handler handler = new Handler(Looper.getMainLooper());
    327             handler.post(new Runnable() {
    328                 @Override
    329                 public void run() {
    330                     mScanCallback.onScanResult(ScanSettings.CALLBACK_TYPE_ALL_MATCHES, scanResult);
    331                 }
    332             });
    333 
    334         }
    335 
    336         @Override
    337         public void onBatchScanResults(final List<ScanResult> results) {
    338             Handler handler = new Handler(Looper.getMainLooper());
    339             handler.post(new Runnable() {
    340                 @Override
    341                 public void run() {
    342                     mScanCallback.onBatchScanResults(results);
    343                 }
    344             });
    345         }
    346 
    347         @Override
    348         public void onFoundOrLost(final boolean onFound, final ScanResult scanResult) {
    349             if (DBG) {
    350                 Log.d(TAG, "onFoundOrLost() - onFound = " + onFound +
    351                         " " + scanResult.toString());
    352             }
    353 
    354             // Check null in case the scan has been stopped
    355             synchronized (this) {
    356                 if (mClientIf <= 0)
    357                     return;
    358             }
    359             Handler handler = new Handler(Looper.getMainLooper());
    360             handler.post(new Runnable() {
    361                     @Override
    362                 public void run() {
    363                     if (onFound) {
    364                         mScanCallback.onScanResult(ScanSettings.CALLBACK_TYPE_FIRST_MATCH,
    365                                 scanResult);
    366                     } else {
    367                         mScanCallback.onScanResult(ScanSettings.CALLBACK_TYPE_MATCH_LOST,
    368                                 scanResult);
    369                     }
    370                 }
    371             });
    372         }
    373     }
    374 
    375     private void postCallbackError(final ScanCallback callback, final int errorCode) {
    376         mHandler.post(new Runnable() {
    377             @Override
    378             public void run() {
    379                 callback.onScanFailed(errorCode);
    380             }
    381         });
    382     }
    383 
    384     private boolean isSettingsConfigAllowedForScan(ScanSettings settings) {
    385         if (mBluetoothAdapter.isOffloadedFilteringSupported()) {
    386             return true;
    387         }
    388         final int callbackType = settings.getCallbackType();
    389         // Only support regular scan if no offloaded filter support.
    390         if (callbackType == ScanSettings.CALLBACK_TYPE_ALL_MATCHES
    391                 && settings.getReportDelayMillis() == 0) {
    392             return true;
    393         }
    394         return false;
    395     }
    396 }
    397