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