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