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 17 package android.bluetooth.le; 18 19 import android.Manifest; 20 import android.annotation.RequiresPermission; 21 import android.annotation.SystemApi; 22 import android.app.ActivityThread; 23 import android.bluetooth.BluetoothAdapter; 24 import android.bluetooth.BluetoothGatt; 25 import android.bluetooth.BluetoothGattCallbackWrapper; 26 import android.bluetooth.IBluetoothGatt; 27 import android.bluetooth.IBluetoothManager; 28 import android.os.Handler; 29 import android.os.Looper; 30 import android.os.ParcelUuid; 31 import android.os.RemoteException; 32 import android.util.Log; 33 34 import java.util.ArrayList; 35 import java.util.HashMap; 36 import java.util.List; 37 import java.util.Map; 38 import java.util.UUID; 39 40 /** 41 * This class provides methods to perform scan related operations for Bluetooth LE devices. An 42 * application can scan for a particular type of Bluetooth LE devices using {@link ScanFilter}. It 43 * can also request different types of callbacks for delivering the result. 44 * <p> 45 * Use {@link BluetoothAdapter#getBluetoothLeScanner()} to get an instance of 46 * {@link BluetoothLeScanner}. 47 * <p> 48 * <b>Note:</b> Most of the scan methods here require 49 * {@link android.Manifest.permission#BLUETOOTH_ADMIN} permission. 50 * 51 * @see ScanFilter 52 */ 53 public final class BluetoothLeScanner { 54 55 private static final String TAG = "BluetoothLeScanner"; 56 private static final boolean DBG = true; 57 private static final boolean VDBG = false; 58 59 private final IBluetoothManager mBluetoothManager; 60 private final Handler mHandler; 61 private BluetoothAdapter mBluetoothAdapter; 62 private final Map<ScanCallback, BleScanCallbackWrapper> mLeScanClients; 63 64 /** 65 * Use {@link BluetoothAdapter#getBluetoothLeScanner()} instead. 66 * 67 * @param bluetoothManager BluetoothManager that conducts overall Bluetooth Management. 68 * @hide 69 */ BluetoothLeScanner(IBluetoothManager bluetoothManager)70 public BluetoothLeScanner(IBluetoothManager bluetoothManager) { 71 mBluetoothManager = bluetoothManager; 72 mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter(); 73 mHandler = new Handler(Looper.getMainLooper()); 74 mLeScanClients = new HashMap<ScanCallback, BleScanCallbackWrapper>(); 75 } 76 77 /** 78 * Start Bluetooth LE scan with default parameters and no filters. The scan results will be 79 * delivered through {@code callback}. 80 * <p> 81 * Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN} permission. 82 * An app must hold 83 * {@link android.Manifest.permission#ACCESS_COARSE_LOCATION ACCESS_COARSE_LOCATION} or 84 * {@link android.Manifest.permission#ACCESS_FINE_LOCATION ACCESS_FINE_LOCATION} permission 85 * in order to get results. 86 * 87 * @param callback Callback used to deliver scan results. 88 * @throws IllegalArgumentException If {@code callback} is null. 89 */ 90 @RequiresPermission(Manifest.permission.BLUETOOTH_ADMIN) startScan(final ScanCallback callback)91 public void startScan(final ScanCallback callback) { 92 if (callback == null) { 93 throw new IllegalArgumentException("callback is null"); 94 } 95 startScan(null, new ScanSettings.Builder().build(), callback); 96 } 97 98 /** 99 * Start Bluetooth LE scan. The scan results will be delivered through {@code callback}. 100 * <p> 101 * Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN} permission. 102 * An app must hold 103 * {@link android.Manifest.permission#ACCESS_COARSE_LOCATION ACCESS_COARSE_LOCATION} or 104 * {@link android.Manifest.permission#ACCESS_FINE_LOCATION ACCESS_FINE_LOCATION} permission 105 * in order to get results. 106 * 107 * @param filters {@link ScanFilter}s for finding exact BLE devices. 108 * @param settings Settings for the scan. 109 * @param callback Callback used to deliver scan results. 110 * @throws IllegalArgumentException If {@code settings} or {@code callback} is null. 111 */ 112 @RequiresPermission(Manifest.permission.BLUETOOTH_ADMIN) startScan(List<ScanFilter> filters, ScanSettings settings, final ScanCallback callback)113 public void startScan(List<ScanFilter> filters, ScanSettings settings, 114 final ScanCallback callback) { 115 startScan(filters, settings, callback, null); 116 } 117 startScan(List<ScanFilter> filters, ScanSettings settings, final ScanCallback callback, List<List<ResultStorageDescriptor>> resultStorages)118 private void startScan(List<ScanFilter> filters, ScanSettings settings, 119 final ScanCallback callback, List<List<ResultStorageDescriptor>> resultStorages) { 120 BluetoothLeUtils.checkAdapterStateOn(mBluetoothAdapter); 121 if (settings == null || callback == null) { 122 throw new IllegalArgumentException("settings or callback is null"); 123 } 124 synchronized (mLeScanClients) { 125 if (mLeScanClients.containsKey(callback)) { 126 postCallbackError(callback, ScanCallback.SCAN_FAILED_ALREADY_STARTED); 127 return; 128 } 129 IBluetoothGatt gatt; 130 try { 131 gatt = mBluetoothManager.getBluetoothGatt(); 132 } catch (RemoteException e) { 133 gatt = null; 134 } 135 if (gatt == null) { 136 postCallbackError(callback, ScanCallback.SCAN_FAILED_INTERNAL_ERROR); 137 return; 138 } 139 if (!isSettingsConfigAllowedForScan(settings)) { 140 postCallbackError(callback, 141 ScanCallback.SCAN_FAILED_FEATURE_UNSUPPORTED); 142 return; 143 } 144 if (!isHardwareResourcesAvailableForScan(settings)) { 145 postCallbackError(callback, 146 ScanCallback.SCAN_FAILED_OUT_OF_HARDWARE_RESOURCES); 147 return; 148 } 149 if (!isSettingsAndFilterComboAllowed(settings, filters)) { 150 postCallbackError(callback, 151 ScanCallback.SCAN_FAILED_FEATURE_UNSUPPORTED); 152 return; 153 } 154 BleScanCallbackWrapper wrapper = new BleScanCallbackWrapper(gatt, filters, 155 settings, callback, resultStorages); 156 wrapper.startRegisteration(); 157 } 158 } 159 160 /** 161 * Stops an ongoing Bluetooth LE scan. 162 * <p> 163 * Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN} permission. 164 * 165 * @param callback 166 */ 167 @RequiresPermission(Manifest.permission.BLUETOOTH_ADMIN) stopScan(ScanCallback callback)168 public void stopScan(ScanCallback callback) { 169 BluetoothLeUtils.checkAdapterStateOn(mBluetoothAdapter); 170 synchronized (mLeScanClients) { 171 BleScanCallbackWrapper wrapper = mLeScanClients.remove(callback); 172 if (wrapper == null) { 173 if (DBG) Log.d(TAG, "could not find callback wrapper"); 174 return; 175 } 176 wrapper.stopLeScan(); 177 } 178 } 179 180 /** 181 * Flush pending batch scan results stored in Bluetooth controller. This will return Bluetooth 182 * LE scan results batched on bluetooth controller. Returns immediately, batch scan results data 183 * will be delivered through the {@code callback}. 184 * 185 * @param callback Callback of the Bluetooth LE Scan, it has to be the same instance as the one 186 * used to start scan. 187 */ flushPendingScanResults(ScanCallback callback)188 public void flushPendingScanResults(ScanCallback callback) { 189 BluetoothLeUtils.checkAdapterStateOn(mBluetoothAdapter); 190 if (callback == null) { 191 throw new IllegalArgumentException("callback cannot be null!"); 192 } 193 synchronized (mLeScanClients) { 194 BleScanCallbackWrapper wrapper = mLeScanClients.get(callback); 195 if (wrapper == null) { 196 return; 197 } 198 wrapper.flushPendingBatchResults(); 199 } 200 } 201 202 /** 203 * Start truncated scan. 204 * 205 * @hide 206 */ 207 @SystemApi startTruncatedScan(List<TruncatedFilter> truncatedFilters, ScanSettings settings, final ScanCallback callback)208 public void startTruncatedScan(List<TruncatedFilter> truncatedFilters, ScanSettings settings, 209 final ScanCallback callback) { 210 int filterSize = truncatedFilters.size(); 211 List<ScanFilter> scanFilters = new ArrayList<ScanFilter>(filterSize); 212 List<List<ResultStorageDescriptor>> scanStorages = 213 new ArrayList<List<ResultStorageDescriptor>>(filterSize); 214 for (TruncatedFilter filter : truncatedFilters) { 215 scanFilters.add(filter.getFilter()); 216 scanStorages.add(filter.getStorageDescriptors()); 217 } 218 startScan(scanFilters, settings, callback, scanStorages); 219 } 220 221 /** 222 * Cleans up scan clients. Should be called when bluetooth is down. 223 * 224 * @hide 225 */ cleanup()226 public void cleanup() { 227 mLeScanClients.clear(); 228 } 229 230 /** 231 * Bluetooth GATT interface callbacks 232 */ 233 private class BleScanCallbackWrapper extends BluetoothGattCallbackWrapper { 234 private static final int REGISTRATION_CALLBACK_TIMEOUT_MILLIS = 2000; 235 236 private final ScanCallback mScanCallback; 237 private final List<ScanFilter> mFilters; 238 private ScanSettings mSettings; 239 private IBluetoothGatt mBluetoothGatt; 240 private List<List<ResultStorageDescriptor>> mResultStorages; 241 242 // mLeHandle 0: not registered 243 // -1: scan stopped 244 // > 0: registered and scan started 245 private int mClientIf; 246 BleScanCallbackWrapper(IBluetoothGatt bluetoothGatt, List<ScanFilter> filters, ScanSettings settings, ScanCallback scanCallback, List<List<ResultStorageDescriptor>> resultStorages)247 public BleScanCallbackWrapper(IBluetoothGatt bluetoothGatt, 248 List<ScanFilter> filters, ScanSettings settings, 249 ScanCallback scanCallback, List<List<ResultStorageDescriptor>> resultStorages) { 250 mBluetoothGatt = bluetoothGatt; 251 mFilters = filters; 252 mSettings = settings; 253 mScanCallback = scanCallback; 254 mClientIf = 0; 255 mResultStorages = resultStorages; 256 } 257 startRegisteration()258 public void startRegisteration() { 259 synchronized (this) { 260 // Scan stopped. 261 if (mClientIf == -1) return; 262 try { 263 UUID uuid = UUID.randomUUID(); 264 mBluetoothGatt.registerClient(new ParcelUuid(uuid), this); 265 wait(REGISTRATION_CALLBACK_TIMEOUT_MILLIS); 266 } catch (InterruptedException | RemoteException e) { 267 Log.e(TAG, "application registeration exception", e); 268 postCallbackError(mScanCallback, ScanCallback.SCAN_FAILED_INTERNAL_ERROR); 269 } 270 if (mClientIf > 0) { 271 mLeScanClients.put(mScanCallback, this); 272 } else { 273 postCallbackError(mScanCallback, 274 ScanCallback.SCAN_FAILED_APPLICATION_REGISTRATION_FAILED); 275 } 276 } 277 } 278 stopLeScan()279 public void stopLeScan() { 280 synchronized (this) { 281 if (mClientIf <= 0) { 282 Log.e(TAG, "Error state, mLeHandle: " + mClientIf); 283 return; 284 } 285 try { 286 mBluetoothGatt.stopScan(mClientIf, false); 287 mBluetoothGatt.unregisterClient(mClientIf); 288 } catch (RemoteException e) { 289 Log.e(TAG, "Failed to stop scan and unregister", e); 290 } 291 mClientIf = -1; 292 } 293 } 294 flushPendingBatchResults()295 void flushPendingBatchResults() { 296 synchronized (this) { 297 if (mClientIf <= 0) { 298 Log.e(TAG, "Error state, mLeHandle: " + mClientIf); 299 return; 300 } 301 try { 302 mBluetoothGatt.flushPendingBatchResults(mClientIf, false); 303 } catch (RemoteException e) { 304 Log.e(TAG, "Failed to get pending scan results", e); 305 } 306 } 307 } 308 309 /** 310 * Application interface registered - app is ready to go 311 */ 312 @Override onClientRegistered(int status, int clientIf)313 public void onClientRegistered(int status, int clientIf) { 314 Log.d(TAG, "onClientRegistered() - status=" + status + 315 " clientIf=" + clientIf); 316 synchronized (this) { 317 if (mClientIf == -1) { 318 if (DBG) Log.d(TAG, "onClientRegistered LE scan canceled"); 319 } 320 321 if (status == BluetoothGatt.GATT_SUCCESS) { 322 mClientIf = clientIf; 323 try { 324 mBluetoothGatt.startScan(mClientIf, false, mSettings, mFilters, 325 mResultStorages, ActivityThread.currentOpPackageName()); 326 } catch (RemoteException e) { 327 Log.e(TAG, "fail to start le scan: " + e); 328 mClientIf = -1; 329 } 330 } else { 331 // registration failed 332 mClientIf = -1; 333 } 334 notifyAll(); 335 } 336 } 337 338 /** 339 * Callback reporting an LE scan result. 340 * 341 * @hide 342 */ 343 @Override onScanResult(final ScanResult scanResult)344 public void onScanResult(final ScanResult scanResult) { 345 if (VDBG) Log.d(TAG, "onScanResult() - " + scanResult.toString()); 346 347 // Check null in case the scan has been stopped 348 synchronized (this) { 349 if (mClientIf <= 0) return; 350 } 351 Handler handler = new Handler(Looper.getMainLooper()); 352 handler.post(new Runnable() { 353 @Override 354 public void run() { 355 mScanCallback.onScanResult(ScanSettings.CALLBACK_TYPE_ALL_MATCHES, scanResult); 356 } 357 }); 358 359 } 360 361 @Override onBatchScanResults(final List<ScanResult> results)362 public void onBatchScanResults(final List<ScanResult> results) { 363 Handler handler = new Handler(Looper.getMainLooper()); 364 handler.post(new Runnable() { 365 @Override 366 public void run() { 367 mScanCallback.onBatchScanResults(results); 368 } 369 }); 370 } 371 372 @Override onFoundOrLost(final boolean onFound, final ScanResult scanResult)373 public void onFoundOrLost(final boolean onFound, final ScanResult scanResult) { 374 if (VDBG) { 375 Log.d(TAG, "onFoundOrLost() - onFound = " + onFound + 376 " " + scanResult.toString()); 377 } 378 379 // Check null in case the scan has been stopped 380 synchronized (this) { 381 if (mClientIf <= 0) 382 return; 383 } 384 Handler handler = new Handler(Looper.getMainLooper()); 385 handler.post(new Runnable() { 386 @Override 387 public void run() { 388 if (onFound) { 389 mScanCallback.onScanResult(ScanSettings.CALLBACK_TYPE_FIRST_MATCH, 390 scanResult); 391 } else { 392 mScanCallback.onScanResult(ScanSettings.CALLBACK_TYPE_MATCH_LOST, 393 scanResult); 394 } 395 } 396 }); 397 } 398 399 @Override onScanManagerErrorCallback(final int errorCode)400 public void onScanManagerErrorCallback(final int errorCode) { 401 if (VDBG) { 402 Log.d(TAG, "onScanManagerErrorCallback() - errorCode = " + errorCode); 403 } 404 synchronized (this) { 405 if (mClientIf <= 0) 406 return; 407 } 408 postCallbackError(mScanCallback, errorCode); 409 } 410 } 411 postCallbackError(final ScanCallback callback, final int errorCode)412 private void postCallbackError(final ScanCallback callback, final int errorCode) { 413 mHandler.post(new Runnable() { 414 @Override 415 public void run() { 416 callback.onScanFailed(errorCode); 417 } 418 }); 419 } 420 isSettingsConfigAllowedForScan(ScanSettings settings)421 private boolean isSettingsConfigAllowedForScan(ScanSettings settings) { 422 if (mBluetoothAdapter.isOffloadedFilteringSupported()) { 423 return true; 424 } 425 final int callbackType = settings.getCallbackType(); 426 // Only support regular scan if no offloaded filter support. 427 if (callbackType == ScanSettings.CALLBACK_TYPE_ALL_MATCHES 428 && settings.getReportDelayMillis() == 0) { 429 return true; 430 } 431 return false; 432 } 433 isSettingsAndFilterComboAllowed(ScanSettings settings, List <ScanFilter> filterList)434 private boolean isSettingsAndFilterComboAllowed(ScanSettings settings, 435 List <ScanFilter> filterList) { 436 final int callbackType = settings.getCallbackType(); 437 // If onlost/onfound is requested, a non-empty filter is expected 438 if ((callbackType & (ScanSettings.CALLBACK_TYPE_FIRST_MATCH 439 | ScanSettings.CALLBACK_TYPE_MATCH_LOST)) != 0) { 440 if (filterList == null) { 441 return false; 442 } 443 for (ScanFilter filter : filterList) { 444 if (filter.isAllFieldsEmpty()) { 445 return false; 446 } 447 } 448 } 449 return true; 450 } 451 isHardwareResourcesAvailableForScan(ScanSettings settings)452 private boolean isHardwareResourcesAvailableForScan(ScanSettings settings) { 453 final int callbackType = settings.getCallbackType(); 454 if ((callbackType & ScanSettings.CALLBACK_TYPE_FIRST_MATCH) != 0 455 || (callbackType & ScanSettings.CALLBACK_TYPE_MATCH_LOST) != 0) { 456 // For onlost/onfound, we required hw support be available 457 return (mBluetoothAdapter.isOffloadedFilteringSupported() && 458 mBluetoothAdapter.isHardwareTrackingFiltersAvailable()); 459 } 460 return true; 461 } 462 } 463