BluetoothLeScanner.java revision 0d0df3ce258f569c76dc7c4b5250c4e50029d6e6
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.annotation.SystemApi; 20import android.bluetooth.BluetoothAdapter; 21import android.bluetooth.BluetoothGatt; 22import android.bluetooth.BluetoothGattCallbackWrapper; 23import android.bluetooth.IBluetoothGatt; 24import android.bluetooth.IBluetoothManager; 25import android.os.Handler; 26import android.os.Looper; 27import android.os.ParcelUuid; 28import android.os.RemoteException; 29import android.util.Log; 30 31import java.util.ArrayList; 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}. It 40 * 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 * 63 * @param bluetoothManager BluetoothManager that conducts overall Bluetooth Management. 64 * @hide 65 */ 66 public BluetoothLeScanner(IBluetoothManager bluetoothManager) { 67 mBluetoothManager = bluetoothManager; 68 mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter(); 69 mHandler = new Handler(Looper.getMainLooper()); 70 mLeScanClients = new HashMap<ScanCallback, BleScanCallbackWrapper>(); 71 } 72 73 /** 74 * Start Bluetooth LE scan with default parameters and no filters. The scan results will be 75 * delivered through {@code callback}. 76 * <p> 77 * Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN} permission. 78 * 79 * @param callback Callback used to deliver scan results. 80 * @throws IllegalArgumentException If {@code callback} is null. 81 */ 82 public void startScan(final ScanCallback callback) { 83 checkAdapterState(); 84 if (callback == null) { 85 throw new IllegalArgumentException("callback is null"); 86 } 87 this.startScan(null, new ScanSettings.Builder().build(), callback); 88 } 89 90 /** 91 * Start Bluetooth LE scan. The scan results will be delivered through {@code callback}. 92 * <p> 93 * Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN} permission. 94 * 95 * @param filters {@link ScanFilter}s for finding exact BLE devices. 96 * @param settings Settings for the scan. 97 * @param callback Callback used to deliver scan results. 98 * @throws IllegalArgumentException If {@code settings} or {@code callback} is null. 99 */ 100 public void startScan(List<ScanFilter> filters, ScanSettings settings, 101 final ScanCallback callback) { 102 startScan(filters, settings, callback, null); 103 } 104 105 private void startScan(List<ScanFilter> filters, ScanSettings settings, 106 final ScanCallback callback, List<List<ResultStorageDescriptor>> resultStorages) { 107 checkAdapterState(); 108 if (settings == null || callback == null) { 109 throw new IllegalArgumentException("settings or callback is null"); 110 } 111 synchronized (mLeScanClients) { 112 if (mLeScanClients.containsKey(callback)) { 113 postCallbackError(callback, ScanCallback.SCAN_FAILED_ALREADY_STARTED); 114 return; 115 } 116 IBluetoothGatt gatt; 117 try { 118 gatt = mBluetoothManager.getBluetoothGatt(); 119 } catch (RemoteException e) { 120 gatt = null; 121 } 122 if (gatt == null) { 123 postCallbackError(callback, ScanCallback.SCAN_FAILED_INTERNAL_ERROR); 124 return; 125 } 126 if (!isSettingsConfigAllowedForScan(settings)) { 127 postCallbackError(callback, 128 ScanCallback.SCAN_FAILED_FEATURE_UNSUPPORTED); 129 return; 130 } 131 BleScanCallbackWrapper wrapper = new BleScanCallbackWrapper(gatt, filters, 132 settings, callback, resultStorages); 133 try { 134 UUID uuid = UUID.randomUUID(); 135 gatt.registerClient(new ParcelUuid(uuid), wrapper); 136 if (wrapper.scanStarted()) { 137 mLeScanClients.put(callback, wrapper); 138 } else { 139 postCallbackError(callback, 140 ScanCallback.SCAN_FAILED_APPLICATION_REGISTRATION_FAILED); 141 return; 142 } 143 } catch (RemoteException e) { 144 Log.e(TAG, "GATT service exception when starting scan", e); 145 postCallbackError(callback, ScanCallback.SCAN_FAILED_INTERNAL_ERROR); 146 } 147 } 148 } 149 150 /** 151 * Stops an ongoing Bluetooth LE scan. 152 * <p> 153 * Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN} permission. 154 * 155 * @param callback 156 */ 157 public void stopScan(ScanCallback callback) { 158 checkAdapterState(); 159 synchronized (mLeScanClients) { 160 BleScanCallbackWrapper wrapper = mLeScanClients.remove(callback); 161 if (wrapper == null) { 162 if (DBG) 163 Log.d(TAG, "could not find callback wrapper"); 164 return; 165 } 166 wrapper.stopLeScan(); 167 } 168 } 169 170 /** 171 * Flush pending batch scan results stored in Bluetooth controller. This will return Bluetooth 172 * LE scan results batched on bluetooth controller. Returns immediately, batch scan results data 173 * will be delivered through the {@code callback}. 174 * 175 * @param callback Callback of the Bluetooth LE Scan, it has to be the same instance as the one 176 * used to start scan. 177 */ 178 public void flushPendingScanResults(ScanCallback callback) { 179 checkAdapterState(); 180 if (callback == null) { 181 throw new IllegalArgumentException("callback cannot be null!"); 182 } 183 synchronized (mLeScanClients) { 184 BleScanCallbackWrapper wrapper = mLeScanClients.get(callback); 185 if (wrapper == null) { 186 return; 187 } 188 wrapper.flushPendingBatchResults(); 189 } 190 } 191 192 /** 193 * Start truncated scan. 194 * 195 * @hide 196 */ 197 @SystemApi 198 public void startTruncatedScan(List<TruncatedFilter> truncatedFilters, ScanSettings settings, 199 final ScanCallback callback) { 200 int filterSize = truncatedFilters.size(); 201 List<ScanFilter> scanFilters = new ArrayList<ScanFilter>(filterSize); 202 List<List<ResultStorageDescriptor>> scanStorages = 203 new ArrayList<List<ResultStorageDescriptor>>(filterSize); 204 for (TruncatedFilter filter : truncatedFilters) { 205 scanFilters.add(filter.getFilter()); 206 scanStorages.add(filter.getStorageDescriptors()); 207 } 208 startScan(scanFilters, settings, callback, scanStorages); 209 } 210 211 /** 212 * Bluetooth GATT interface callbacks 213 */ 214 private static class BleScanCallbackWrapper extends BluetoothGattCallbackWrapper { 215 private static final int REGISTRATION_CALLBACK_TIMEOUT_SECONDS = 5; 216 217 private final ScanCallback mScanCallback; 218 private final List<ScanFilter> mFilters; 219 private ScanSettings mSettings; 220 private IBluetoothGatt mBluetoothGatt; 221 private List<List<ResultStorageDescriptor>> mResultStorages; 222 223 // mLeHandle 0: not registered 224 // -1: scan stopped 225 // > 0: registered and scan started 226 private int mClientIf; 227 228 public BleScanCallbackWrapper(IBluetoothGatt bluetoothGatt, 229 List<ScanFilter> filters, ScanSettings settings, 230 ScanCallback scanCallback, List<List<ResultStorageDescriptor>> resultStorages) { 231 mBluetoothGatt = bluetoothGatt; 232 mFilters = filters; 233 mSettings = settings; 234 mScanCallback = scanCallback; 235 mClientIf = 0; 236 mResultStorages = resultStorages; 237 } 238 239 public boolean scanStarted() { 240 synchronized (this) { 241 if (mClientIf == -1) { 242 return false; 243 } 244 try { 245 wait(REGISTRATION_CALLBACK_TIMEOUT_SECONDS); 246 } catch (InterruptedException e) { 247 Log.e(TAG, "Callback reg wait interrupted: " + e); 248 } 249 } 250 return mClientIf > 0; 251 } 252 253 public void stopLeScan() { 254 synchronized (this) { 255 if (mClientIf <= 0) { 256 Log.e(TAG, "Error state, mLeHandle: " + mClientIf); 257 return; 258 } 259 try { 260 mBluetoothGatt.stopScan(mClientIf, false); 261 mBluetoothGatt.unregisterClient(mClientIf); 262 } catch (RemoteException e) { 263 Log.e(TAG, "Failed to stop scan and unregister", e); 264 } 265 mClientIf = -1; 266 notifyAll(); 267 } 268 } 269 270 void flushPendingBatchResults() { 271 synchronized (this) { 272 if (mClientIf <= 0) { 273 Log.e(TAG, "Error state, mLeHandle: " + mClientIf); 274 return; 275 } 276 try { 277 mBluetoothGatt.flushPendingBatchResults(mClientIf, false); 278 } catch (RemoteException e) { 279 Log.e(TAG, "Failed to get pending scan results", e); 280 } 281 } 282 } 283 284 /** 285 * Application interface registered - app is ready to go 286 */ 287 @Override 288 public void onClientRegistered(int status, int clientIf) { 289 Log.d(TAG, "onClientRegistered() - status=" + status + 290 " clientIf=" + clientIf); 291 292 synchronized (this) { 293 if (mClientIf == -1) { 294 if (DBG) 295 Log.d(TAG, "onClientRegistered LE scan canceled"); 296 } 297 298 if (status == BluetoothGatt.GATT_SUCCESS) { 299 mClientIf = clientIf; 300 try { 301 mBluetoothGatt.startScan(mClientIf, false, mSettings, mFilters, 302 mResultStorages); 303 } catch (RemoteException e) { 304 Log.e(TAG, "fail to start le scan: " + e); 305 mClientIf = -1; 306 } 307 } else { 308 // registration failed 309 mClientIf = -1; 310 } 311 notifyAll(); 312 } 313 } 314 315 /** 316 * Callback reporting an LE scan result. 317 * 318 * @hide 319 */ 320 @Override 321 public void onScanResult(final ScanResult scanResult) { 322 if (DBG) 323 Log.d(TAG, "onScanResult() - " + scanResult.toString()); 324 325 // Check null in case the scan has been stopped 326 synchronized (this) { 327 if (mClientIf <= 0) 328 return; 329 } 330 Handler handler = new Handler(Looper.getMainLooper()); 331 handler.post(new Runnable() { 332 @Override 333 public void run() { 334 mScanCallback.onScanResult(ScanSettings.CALLBACK_TYPE_ALL_MATCHES, scanResult); 335 } 336 }); 337 338 } 339 340 @Override 341 public void onBatchScanResults(final List<ScanResult> results) { 342 Handler handler = new Handler(Looper.getMainLooper()); 343 handler.post(new Runnable() { 344 @Override 345 public void run() { 346 mScanCallback.onBatchScanResults(results); 347 } 348 }); 349 } 350 351 @Override 352 public void onFoundOrLost(final boolean onFound, final ScanResult scanResult) { 353 if (DBG) { 354 Log.d(TAG, "onFoundOrLost() - onFound = " + onFound + 355 " " + scanResult.toString()); 356 } 357 358 // Check null in case the scan has been stopped 359 synchronized (this) { 360 if (mClientIf <= 0) 361 return; 362 } 363 Handler handler = new Handler(Looper.getMainLooper()); 364 handler.post(new Runnable() { 365 @Override 366 public void run() { 367 if (onFound) { 368 mScanCallback.onScanResult(ScanSettings.CALLBACK_TYPE_FIRST_MATCH, 369 scanResult); 370 } else { 371 mScanCallback.onScanResult(ScanSettings.CALLBACK_TYPE_MATCH_LOST, 372 scanResult); 373 } 374 } 375 }); 376 } 377 } 378 379 // TODO: move this api to a common util class. 380 private void checkAdapterState() { 381 if (mBluetoothAdapter.getState() != mBluetoothAdapter.STATE_ON) { 382 throw new IllegalStateException("BT Adapter is not turned ON"); 383 } 384 } 385 386 private void postCallbackError(final ScanCallback callback, final int errorCode) { 387 mHandler.post(new Runnable() { 388 @Override 389 public void run() { 390 callback.onScanFailed(errorCode); 391 } 392 }); 393 } 394 395 private boolean isSettingsConfigAllowedForScan(ScanSettings settings) { 396 if (mBluetoothAdapter.isOffloadedFilteringSupported()) { 397 return true; 398 } 399 final int callbackType = settings.getCallbackType(); 400 // Only support regular scan if no offloaded filter support. 401 if (callbackType == ScanSettings.CALLBACK_TYPE_ALL_MATCHES 402 && settings.getReportDelayMillis() == 0) { 403 return true; 404 } 405 return false; 406 } 407} 408