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