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