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