BluetoothLeScanner.java revision 890b46a0d5c220bffedcf27520befb34bf8830ea
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.bluetooth.BluetoothAdapter;
20import android.bluetooth.BluetoothDevice;
21import android.bluetooth.BluetoothGatt;
22import android.bluetooth.IBluetoothGatt;
23import android.bluetooth.IBluetoothGattCallback;
24import android.bluetooth.IBluetoothManager;
25import android.os.Handler;
26import android.os.Looper;
27import android.os.ParcelUuid;
28import android.os.RemoteException;
29import android.os.SystemClock;
30import android.util.Log;
31
32import java.util.HashMap;
33import java.util.List;
34import java.util.Map;
35import 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}.
40 * It 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 */
50public 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     * @param bluetoothManager BluetoothManager that conducts overall Bluetooth Management
63     * @hide
64     */
65    public BluetoothLeScanner(IBluetoothManager bluetoothManager) {
66        mBluetoothManager = bluetoothManager;
67        mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
68        mHandler = new Handler(Looper.getMainLooper());
69        mLeScanClients = new HashMap<ScanCallback, BleScanCallbackWrapper>();
70    }
71
72    /**
73     * Start Bluetooth LE scan with default parameters and no filters.
74     * The scan results will be delivered through {@code callback}.
75     * <p>
76     * Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN} permission.
77     *
78     * @param callback Callback used to deliver scan results.
79     * @throws IllegalArgumentException If {@code callback} is null.
80     */
81    public void startScan(final ScanCallback callback) {
82        if (callback == null) {
83            throw new IllegalArgumentException("callback is null");
84        }
85        this.startScan(null, new ScanSettings.Builder().build(), callback);
86    }
87
88    /**
89     * Start Bluetooth LE scan. The scan results will be delivered through {@code callback}.
90     * <p>
91     * Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN} permission.
92     *
93     * @param filters {@link ScanFilter}s for finding exact BLE devices.
94     * @param settings Settings for the scan.
95     * @param callback Callback used to deliver scan results.
96     * @throws IllegalArgumentException If {@code settings} or {@code callback} is null.
97     */
98    public void startScan(List<ScanFilter> filters, ScanSettings settings,
99            final ScanCallback callback) {
100        if (settings == null || callback == null) {
101            throw new IllegalArgumentException("settings or callback is null");
102        }
103        synchronized (mLeScanClients) {
104            if (mLeScanClients.containsKey(callback)) {
105                postCallbackError(callback, ScanCallback.SCAN_FAILED_ALREADY_STARTED);
106                return;
107            }
108            IBluetoothGatt gatt;
109            try {
110                gatt = mBluetoothManager.getBluetoothGatt();
111            } catch (RemoteException e) {
112                gatt = null;
113            }
114            if (gatt == null) {
115                postCallbackError(callback, ScanCallback.SCAN_FAILED_INTERNAL_ERROR);
116                return;
117            }
118            if (!isSettingsConfigAllowedForScan(settings)) {
119                postCallbackError(callback,
120                        ScanCallback.SCAN_FAILED_FEATURE_UNSUPPORTED);
121                return;
122            }
123            BleScanCallbackWrapper wrapper = new BleScanCallbackWrapper(gatt, filters,
124                    settings, callback);
125            try {
126                UUID uuid = UUID.randomUUID();
127                gatt.registerClient(new ParcelUuid(uuid), wrapper);
128                if (wrapper.scanStarted()) {
129                    mLeScanClients.put(callback, wrapper);
130                } else {
131                    postCallbackError(callback,
132                            ScanCallback.SCAN_FAILED_APPLICATION_REGISTRATION_FAILED);
133                    return;
134                }
135            } catch (RemoteException e) {
136                Log.e(TAG, "GATT service exception when starting scan", e);
137                postCallbackError(callback, ScanCallback.SCAN_FAILED_INTERNAL_ERROR);
138            }
139        }
140    }
141
142    /**
143     * Stops an ongoing Bluetooth LE scan.
144     * <p>
145     * Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN} permission.
146     *
147     * @param callback
148     */
149    public void stopScan(ScanCallback callback) {
150        synchronized (mLeScanClients) {
151            BleScanCallbackWrapper wrapper = mLeScanClients.remove(callback);
152            if (wrapper == null) {
153                return;
154            }
155            wrapper.stopLeScan();
156        }
157    }
158
159    /**
160     * Flush pending batch scan results stored in Bluetooth controller. This will return Bluetooth
161     * LE scan results batched on bluetooth controller. Returns immediately, batch scan results data
162     * will be delivered through the {@code callback}.
163     *
164     * @param callback Callback of the Bluetooth LE Scan, it has to be the same instance as the one
165     *            used to start scan.
166     */
167    public void flushPendingScanResults(ScanCallback callback) {
168        if (callback == null) {
169            throw new IllegalArgumentException("callback cannot be null!");
170        }
171        synchronized (mLeScanClients) {
172            BleScanCallbackWrapper wrapper = mLeScanClients.get(callback);
173            if (wrapper == null) {
174                return;
175            }
176            wrapper.flushPendingBatchResults();
177        }
178    }
179
180    /**
181     * Bluetooth GATT interface callbacks
182     */
183    private static class BleScanCallbackWrapper extends IBluetoothGattCallback.Stub {
184        private static final int REGISTRATION_CALLBACK_TIMEOUT_SECONDS = 5;
185
186        private final ScanCallback mScanCallback;
187        private final List<ScanFilter> mFilters;
188        private ScanSettings mSettings;
189        private IBluetoothGatt mBluetoothGatt;
190
191        // mLeHandle 0: not registered
192        // -1: scan stopped
193        // > 0: registered and scan started
194        private int mLeHandle;
195
196        public BleScanCallbackWrapper(IBluetoothGatt bluetoothGatt,
197                List<ScanFilter> filters, ScanSettings settings,
198                ScanCallback scanCallback) {
199            mBluetoothGatt = bluetoothGatt;
200            mFilters = filters;
201            mSettings = settings;
202            mScanCallback = scanCallback;
203            mLeHandle = 0;
204        }
205
206        public boolean scanStarted() {
207            synchronized (this) {
208                if (mLeHandle == -1) {
209                    return false;
210                }
211                try {
212                    wait(REGISTRATION_CALLBACK_TIMEOUT_SECONDS);
213                } catch (InterruptedException e) {
214                    Log.e(TAG, "Callback reg wait interrupted: " + e);
215                }
216            }
217            return mLeHandle > 0;
218        }
219
220        public void stopLeScan() {
221            synchronized (this) {
222                if (mLeHandle <= 0) {
223                    Log.e(TAG, "Error state, mLeHandle: " + mLeHandle);
224                    return;
225                }
226                try {
227                    mBluetoothGatt.stopScan(mLeHandle, false);
228                    mBluetoothGatt.unregisterClient(mLeHandle);
229                } catch (RemoteException e) {
230                    Log.e(TAG, "Failed to stop scan and unregister", e);
231                }
232                mLeHandle = -1;
233                notifyAll();
234            }
235        }
236
237        void flushPendingBatchResults() {
238            synchronized (this) {
239                if (mLeHandle <= 0) {
240                    Log.e(TAG, "Error state, mLeHandle: " + mLeHandle);
241                    return;
242                }
243                try {
244                    mBluetoothGatt.flushPendingBatchResults(mLeHandle, false);
245                } catch (RemoteException e) {
246                    Log.e(TAG, "Failed to get pending scan results", e);
247                }
248            }
249        }
250
251        /**
252         * Application interface registered - app is ready to go
253         */
254        @Override
255        public void onClientRegistered(int status, int clientIf) {
256            Log.d(TAG, "onClientRegistered() - status=" + status +
257                    " clientIf=" + clientIf);
258
259            synchronized (this) {
260                if (mLeHandle == -1) {
261                    if (DBG)
262                        Log.d(TAG, "onClientRegistered LE scan canceled");
263                }
264
265                if (status == BluetoothGatt.GATT_SUCCESS) {
266                    mLeHandle = clientIf;
267                    try {
268                        mBluetoothGatt.startScanWithFilters(mLeHandle, false, mSettings, mFilters);
269                    } catch (RemoteException e) {
270                        Log.e(TAG, "fail to start le scan: " + e);
271                        mLeHandle = -1;
272                    }
273                } else {
274                    // registration failed
275                    mLeHandle = -1;
276                }
277                notifyAll();
278            }
279        }
280
281        @Override
282        public void onClientConnectionState(int status, int clientIf,
283                boolean connected, String address) {
284            // no op
285        }
286
287        /**
288         * Callback reporting an LE scan result.
289         *
290         * @hide
291         */
292        @Override
293        public void onScanResult(String address, int rssi, byte[] advData) {
294            if (DBG)
295                Log.d(TAG, "onScanResult() - Device=" + address + " RSSI=" + rssi);
296
297            // Check null in case the scan has been stopped
298            synchronized (this) {
299                if (mLeHandle <= 0)
300                    return;
301            }
302            BluetoothDevice device = BluetoothAdapter.getDefaultAdapter().getRemoteDevice(
303                    address);
304            long scanNanos = SystemClock.elapsedRealtimeNanos();
305            final ScanResult result = new ScanResult(device, advData, rssi,
306                    scanNanos);
307            Handler handler = new Handler(Looper.getMainLooper());
308            handler.post(new Runnable() {
309                    @Override
310                public void run() {
311                    mScanCallback.onScanResult(ScanSettings.CALLBACK_TYPE_ALL_MATCHES, result);
312                }
313            });
314
315        }
316
317        @Override
318        public void onBatchScanResults(final List<ScanResult> results) {
319            Handler handler = new Handler(Looper.getMainLooper());
320            handler.post(new Runnable() {
321                    @Override
322                public void run() {
323                    mScanCallback.onBatchScanResults(results);
324                }
325            });
326        }
327
328        @Override
329        public void onGetService(String address, int srvcType,
330                int srvcInstId, ParcelUuid srvcUuid) {
331            // no op
332        }
333
334        @Override
335        public void onGetIncludedService(String address, int srvcType,
336                int srvcInstId, ParcelUuid srvcUuid,
337                int inclSrvcType, int inclSrvcInstId,
338                ParcelUuid inclSrvcUuid) {
339            // no op
340        }
341
342        @Override
343        public void onGetCharacteristic(String address, int srvcType,
344                int srvcInstId, ParcelUuid srvcUuid,
345                int charInstId, ParcelUuid charUuid,
346                int charProps) {
347            // no op
348        }
349
350        @Override
351        public void onGetDescriptor(String address, int srvcType,
352                int srvcInstId, ParcelUuid srvcUuid,
353                int charInstId, ParcelUuid charUuid,
354                int descInstId, ParcelUuid descUuid) {
355            // no op
356        }
357
358        @Override
359        public void onSearchComplete(String address, int status) {
360            // no op
361        }
362
363        @Override
364        public void onCharacteristicRead(String address, int status, int srvcType,
365                int srvcInstId, ParcelUuid srvcUuid,
366                int charInstId, ParcelUuid charUuid, byte[] value) {
367            // no op
368        }
369
370        @Override
371        public void onCharacteristicWrite(String address, int status, int srvcType,
372                int srvcInstId, ParcelUuid srvcUuid,
373                int charInstId, ParcelUuid charUuid) {
374            // no op
375        }
376
377        @Override
378        public void onNotify(String address, int srvcType,
379                int srvcInstId, ParcelUuid srvcUuid,
380                int charInstId, ParcelUuid charUuid,
381                byte[] value) {
382            // no op
383        }
384
385        @Override
386        public void onDescriptorRead(String address, int status, int srvcType,
387                int srvcInstId, ParcelUuid srvcUuid,
388                int charInstId, ParcelUuid charUuid,
389                int descInstId, ParcelUuid descrUuid, byte[] value) {
390            // no op
391        }
392
393        @Override
394        public void onDescriptorWrite(String address, int status, int srvcType,
395                int srvcInstId, ParcelUuid srvcUuid,
396                int charInstId, ParcelUuid charUuid,
397                int descInstId, ParcelUuid descrUuid) {
398            // no op
399        }
400
401        @Override
402        public void onExecuteWrite(String address, int status) {
403            // no op
404        }
405
406        @Override
407        public void onReadRemoteRssi(String address, int rssi, int status) {
408            // no op
409        }
410
411        @Override
412        public void onMultiAdvertiseCallback(int status) {
413            // no op
414        }
415
416        @Override
417        public void onConfigureMTU(String address, int mtu, int status) {
418            // no op
419        }
420
421        @Override
422        public void onConnectionCongested(String address, boolean congested) {
423            // no op
424        }
425
426        @Override
427        public void onFoundOrLost(boolean onFound, String address, int rssi,
428                byte[] advData) {
429            if (DBG) {
430                Log.d(TAG, "onFoundOrLost() - Device=" + address);
431            }
432            // ToDo: Fix issue with underlying reporting from chipset
433            BluetoothDevice device = BluetoothAdapter.getDefaultAdapter().getRemoteDevice(
434                    address);
435            long scanNanos = SystemClock.elapsedRealtimeNanos();
436            ScanResult result = new ScanResult(device, advData, rssi,
437                    scanNanos);
438            if (onFound) {
439                mScanCallback.onScanResult(ScanSettings.CALLBACK_TYPE_FIRST_MATCH, result);
440            } else {
441                mScanCallback.onScanResult(ScanSettings.CALLBACK_TYPE_MATCH_LOST, result);
442            }
443        }
444    }
445
446    private void postCallbackError(final ScanCallback callback, final int errorCode) {
447        mHandler.post(new Runnable() {
448                @Override
449            public void run() {
450                callback.onScanFailed(errorCode);
451            }
452        });
453    }
454
455    private boolean isSettingsConfigAllowedForScan(ScanSettings settings) {
456        final int callbackType = settings.getCallbackType();
457        if ((  callbackType != ScanSettings.CALLBACK_TYPE_ALL_MATCHES
458           || (callbackType == ScanSettings.CALLBACK_TYPE_ALL_MATCHES
459           && settings.getReportDelaySeconds() > 0))
460           && !mBluetoothAdapter.isOffloadedFilteringSupported()) {
461            return false;
462        }
463        return true;
464    }
465}
466