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