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