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