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