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