BluetoothLeAdvertiser.java revision f6b3d8ca364f3008730741258ceb07c7039a5528
1/* 2 * Copyright (C) 2014 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of 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, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17package android.bluetooth.le; 18 19import android.bluetooth.BluetoothAdapter; 20import android.bluetooth.BluetoothGatt; 21import android.bluetooth.BluetoothUuid; 22import android.bluetooth.IBluetoothGatt; 23import android.bluetooth.IBluetoothGattCallback; 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.HashMap; 32import java.util.List; 33import java.util.Map; 34import java.util.UUID; 35 36/** 37 * This class provides a way to perform Bluetooth LE advertise operations, such as starting and 38 * stopping advertising. An advertiser can broadcast up to 31 bytes of advertisement data 39 * represented by {@link AdvertiseData}. 40 * <p> 41 * To get an instance of {@link BluetoothLeAdvertiser}, call the 42 * {@link BluetoothAdapter#getBluetoothLeAdvertiser()} method. 43 * <p> 44 * <b>Note:</b> Most of the methods here require {@link android.Manifest.permission#BLUETOOTH_ADMIN} 45 * permission. 46 * 47 * @see AdvertiseData 48 */ 49public final class BluetoothLeAdvertiser { 50 51 private static final String TAG = "BluetoothLeAdvertiser"; 52 53 private static final int MAX_ADVERTISING_DATA_BYTES = 31; 54 // Each fields need one byte for field length and another byte for field type. 55 private static final int OVERHEAD_BYTES_PER_FIELD = 2; 56 // Flags field will be set by system. 57 private static final int FLAGS_FIELD_BYTES = 3; 58 private static final int MANUFACTURER_SPECIFIC_DATA_LENGTH = 2; 59 private static final int SERVICE_DATA_UUID_LENGTH = 2; 60 61 private final IBluetoothManager mBluetoothManager; 62 private final Handler mHandler; 63 private BluetoothAdapter mBluetoothAdapter; 64 private final Map<AdvertiseCallback, AdvertiseCallbackWrapper> 65 mLeAdvertisers = new HashMap<AdvertiseCallback, AdvertiseCallbackWrapper>(); 66 67 /** 68 * Use BluetoothAdapter.getLeAdvertiser() instead. 69 * 70 * @param bluetoothManager BluetoothManager that conducts overall Bluetooth Management 71 * @hide 72 */ 73 public BluetoothLeAdvertiser(IBluetoothManager bluetoothManager) { 74 mBluetoothManager = bluetoothManager; 75 mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter(); 76 mHandler = new Handler(Looper.getMainLooper()); 77 } 78 79 /** 80 * Start Bluetooth LE Advertising. On success, the {@code advertiseData} will be broadcasted. 81 * Returns immediately, the operation status is delivered through {@code callback}. 82 * <p> 83 * Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN} permission. 84 * 85 * @param settings Settings for Bluetooth LE advertising. 86 * @param advertiseData Advertisement data to be broadcasted. 87 * @param callback Callback for advertising status. 88 */ 89 public void startAdvertising(AdvertiseSettings settings, 90 AdvertiseData advertiseData, final AdvertiseCallback callback) { 91 startAdvertising(settings, advertiseData, null, callback); 92 } 93 94 /** 95 * Start Bluetooth LE Advertising. The {@code advertiseData} will be broadcasted if the 96 * operation succeeds. The {@code scanResponse} is returned when a scanning device sends an 97 * active scan request. This method returns immediately, the operation status is delivered 98 * through {@code callback}. 99 * <p> 100 * Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN} 101 * 102 * @param settings Settings for Bluetooth LE advertising. 103 * @param advertiseData Advertisement data to be advertised in advertisement packet. 104 * @param scanResponse Scan response associated with the advertisement data. 105 * @param callback Callback for advertising status. 106 */ 107 public void startAdvertising(AdvertiseSettings settings, 108 AdvertiseData advertiseData, AdvertiseData scanResponse, 109 final AdvertiseCallback callback) { 110 checkAdapterState(); 111 if (callback == null) { 112 throw new IllegalArgumentException("callback cannot be null"); 113 } 114 if (totalBytes(advertiseData) > MAX_ADVERTISING_DATA_BYTES || 115 totalBytes(scanResponse) > MAX_ADVERTISING_DATA_BYTES) { 116 postCallbackFailure(callback, AdvertiseCallback.ADVERTISE_FAILED_DATA_TOO_LARGE); 117 return; 118 } 119 if (mLeAdvertisers.containsKey(callback)) { 120 postCallbackFailure(callback, AdvertiseCallback.ADVERTISE_FAILED_ALREADY_STARTED); 121 return; 122 } 123 IBluetoothGatt gatt; 124 try { 125 gatt = mBluetoothManager.getBluetoothGatt(); 126 } catch (RemoteException e) { 127 Log.e(TAG, "Failed to get Bluetooth gatt - ", e); 128 postCallbackFailure(callback, AdvertiseCallback.ADVERTISE_FAILED_INTERNAL_ERROR); 129 return; 130 } 131 if (!mBluetoothAdapter.isMultipleAdvertisementSupported()) { 132 postCallbackFailure(callback, AdvertiseCallback.ADVERTISE_FAILED_FEATURE_UNSUPPORTED); 133 return; 134 } 135 AdvertiseCallbackWrapper wrapper = new AdvertiseCallbackWrapper(callback, advertiseData, 136 scanResponse, settings, gatt); 137 UUID uuid = UUID.randomUUID(); 138 try { 139 gatt.registerClient(new ParcelUuid(uuid), wrapper); 140 if (wrapper.advertiseStarted()) { 141 mLeAdvertisers.put(callback, wrapper); 142 } 143 } catch (RemoteException e) { 144 Log.e(TAG, "Failed to stop advertising", e); 145 } 146 } 147 148 /** 149 * Stop Bluetooth LE advertising. The {@code callback} must be the same one use in 150 * {@link BluetoothLeAdvertiser#startAdvertising}. 151 * <p> 152 * Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN} permission. 153 * 154 * @param callback {@link AdvertiseCallback} identifies the advertising instance to stop. 155 */ 156 public void stopAdvertising(final AdvertiseCallback callback) { 157 checkAdapterState(); 158 if (callback == null) { 159 throw new IllegalArgumentException("callback cannot be null"); 160 } 161 AdvertiseCallbackWrapper wrapper = mLeAdvertisers.get(callback); 162 if (wrapper == null) 163 return; 164 try { 165 IBluetoothGatt gatt = mBluetoothManager.getBluetoothGatt(); 166 if (gatt != null) 167 gatt.stopMultiAdvertising(wrapper.mClientIf); 168 169 if (wrapper.advertiseStopped()) { 170 mLeAdvertisers.remove(callback); 171 } 172 } catch (RemoteException e) { 173 Log.e(TAG, "Failed to stop advertising", e); 174 } 175 } 176 177 // Compute the size of the advertise data. 178 private int totalBytes(AdvertiseData data) { 179 if (data == null) { 180 return 0; 181 } 182 int size = FLAGS_FIELD_BYTES; // flags field is always set. 183 if (data.getServiceUuids() != null) { 184 int num16BitUuids = 0; 185 int num32BitUuids = 0; 186 int num128BitUuids = 0; 187 for (ParcelUuid uuid : data.getServiceUuids()) { 188 if (BluetoothUuid.is16BitUuid(uuid)) { 189 ++num16BitUuids; 190 } else if (BluetoothUuid.is32BitUuid(uuid)) { 191 ++num32BitUuids; 192 } else { 193 ++num128BitUuids; 194 } 195 } 196 // 16 bit service uuids are grouped into one field when doing advertising. 197 if (num16BitUuids != 0) { 198 size += OVERHEAD_BYTES_PER_FIELD + 199 num16BitUuids * BluetoothUuid.UUID_BYTES_16_BIT; 200 } 201 // 32 bit service uuids are grouped into one field when doing advertising. 202 if (num32BitUuids != 0) { 203 size += OVERHEAD_BYTES_PER_FIELD + 204 num32BitUuids * BluetoothUuid.UUID_BYTES_32_BIT; 205 } 206 // 128 bit service uuids are grouped into one field when doing advertising. 207 if (num128BitUuids != 0) { 208 size += OVERHEAD_BYTES_PER_FIELD + 209 num128BitUuids * BluetoothUuid.UUID_BYTES_128_BIT; 210 } 211 } 212 if (data.getServiceDataUuid() != null) { 213 size += OVERHEAD_BYTES_PER_FIELD + SERVICE_DATA_UUID_LENGTH 214 + byteLength(data.getServiceData()); 215 } 216 if (data.getManufacturerId() > 0) { 217 size += OVERHEAD_BYTES_PER_FIELD + MANUFACTURER_SPECIFIC_DATA_LENGTH + 218 byteLength(data.getManufacturerSpecificData()); 219 } 220 if (data.getIncludeTxPowerLevel()) { 221 size += OVERHEAD_BYTES_PER_FIELD + 1; // tx power level value is one byte. 222 } 223 if (data.getIncludeDeviceName() && mBluetoothAdapter.getName() != null) { 224 size += OVERHEAD_BYTES_PER_FIELD + mBluetoothAdapter.getName().length(); 225 } 226 return size; 227 } 228 229 private int byteLength(byte[] array) { 230 return array == null ? 0 : array.length; 231 } 232 233 /** 234 * Bluetooth GATT interface callbacks for advertising. 235 */ 236 private static class AdvertiseCallbackWrapper extends IBluetoothGattCallback.Stub { 237 private static final int LE_CALLBACK_TIMEOUT_MILLIS = 2000; 238 private final AdvertiseCallback mAdvertiseCallback; 239 private final AdvertiseData mAdvertisement; 240 private final AdvertiseData mScanResponse; 241 private final AdvertiseSettings mSettings; 242 private final IBluetoothGatt mBluetoothGatt; 243 244 // mClientIf 0: not registered 245 // -1: scan stopped 246 // >0: registered and scan started 247 private int mClientIf; 248 private boolean isAdvertising = false; 249 250 public AdvertiseCallbackWrapper(AdvertiseCallback advertiseCallback, 251 AdvertiseData advertiseData, AdvertiseData scanResponse, 252 AdvertiseSettings settings, 253 IBluetoothGatt bluetoothGatt) { 254 mAdvertiseCallback = advertiseCallback; 255 mAdvertisement = advertiseData; 256 mScanResponse = scanResponse; 257 mSettings = settings; 258 mBluetoothGatt = bluetoothGatt; 259 mClientIf = 0; 260 } 261 262 public boolean advertiseStarted() { 263 boolean started = false; 264 synchronized (this) { 265 if (mClientIf == -1) { 266 return false; 267 } 268 try { 269 wait(LE_CALLBACK_TIMEOUT_MILLIS); 270 } catch (InterruptedException e) { 271 Log.e(TAG, "Callback reg wait interrupted: ", e); 272 } 273 started = (mClientIf > 0 && isAdvertising); 274 } 275 return started; 276 } 277 278 public boolean advertiseStopped() { 279 synchronized (this) { 280 try { 281 wait(LE_CALLBACK_TIMEOUT_MILLIS); 282 } catch (InterruptedException e) { 283 Log.e(TAG, "Callback reg wait interrupted: " + e); 284 } 285 return !isAdvertising; 286 } 287 } 288 289 /** 290 * Application interface registered - app is ready to go 291 */ 292 @Override 293 public void onClientRegistered(int status, int clientIf) { 294 Log.d(TAG, "onClientRegistered() - status=" + status + " clientIf=" + clientIf); 295 synchronized (this) { 296 if (status == BluetoothGatt.GATT_SUCCESS) { 297 mClientIf = clientIf; 298 try { 299 mBluetoothGatt.startMultiAdvertising(mClientIf, mAdvertisement, 300 mScanResponse, mSettings); 301 } catch (RemoteException e) { 302 Log.e(TAG, "fail to start le advertise: " + e); 303 mClientIf = -1; 304 notifyAll(); 305 } 306 } else { 307 // registration failed 308 mClientIf = -1; 309 notifyAll(); 310 } 311 } 312 } 313 314 @Override 315 public void onClientConnectionState(int status, int clientIf, 316 boolean connected, String address) { 317 // no op 318 } 319 320 @Override 321 public void onScanResult(String address, int rssi, byte[] advData) { 322 // no op 323 } 324 325 @Override 326 public void onGetService(String address, int srvcType, 327 int srvcInstId, ParcelUuid srvcUuid) { 328 // no op 329 } 330 331 @Override 332 public void onGetIncludedService(String address, int srvcType, 333 int srvcInstId, ParcelUuid srvcUuid, 334 int inclSrvcType, int inclSrvcInstId, 335 ParcelUuid inclSrvcUuid) { 336 // no op 337 } 338 339 @Override 340 public void onGetCharacteristic(String address, int srvcType, 341 int srvcInstId, ParcelUuid srvcUuid, 342 int charInstId, ParcelUuid charUuid, 343 int charProps) { 344 // no op 345 } 346 347 @Override 348 public void onGetDescriptor(String address, int srvcType, 349 int srvcInstId, ParcelUuid srvcUuid, 350 int charInstId, ParcelUuid charUuid, 351 int descInstId, ParcelUuid descUuid) { 352 // no op 353 } 354 355 @Override 356 public void onSearchComplete(String address, int status) { 357 // no op 358 } 359 360 @Override 361 public void onCharacteristicRead(String address, int status, int srvcType, 362 int srvcInstId, ParcelUuid srvcUuid, 363 int charInstId, ParcelUuid charUuid, byte[] value) { 364 // no op 365 } 366 367 @Override 368 public void onCharacteristicWrite(String address, int status, int srvcType, 369 int srvcInstId, ParcelUuid srvcUuid, 370 int charInstId, ParcelUuid charUuid) { 371 // no op 372 } 373 374 @Override 375 public void onNotify(String address, int srvcType, 376 int srvcInstId, ParcelUuid srvcUuid, 377 int charInstId, ParcelUuid charUuid, 378 byte[] value) { 379 // no op 380 } 381 382 @Override 383 public void onDescriptorRead(String address, int status, int srvcType, 384 int srvcInstId, ParcelUuid srvcUuid, 385 int charInstId, ParcelUuid charUuid, 386 int descInstId, ParcelUuid descrUuid, byte[] value) { 387 // no op 388 } 389 390 @Override 391 public void onDescriptorWrite(String address, int status, int srvcType, 392 int srvcInstId, ParcelUuid srvcUuid, 393 int charInstId, ParcelUuid charUuid, 394 int descInstId, ParcelUuid descrUuid) { 395 // no op 396 } 397 398 @Override 399 public void onExecuteWrite(String address, int status) { 400 // no op 401 } 402 403 @Override 404 public void onReadRemoteRssi(String address, int rssi, int status) { 405 // no op 406 } 407 408 @Override 409 public void onMultiAdvertiseCallback(int status) { 410 // TODO: This logic needs to be re-visited to account 411 // for whether the scan has actually been started 412 // or not. Toggling the isAdvertising does not seem 413 // correct. 414 synchronized (this) { 415 if (status == AdvertiseCallback.ADVERTISE_SUCCESS) { 416 isAdvertising = !isAdvertising; 417 if (!isAdvertising) { 418 try { 419 mBluetoothGatt.unregisterClient(mClientIf); 420 mClientIf = -1; 421 } catch (RemoteException e) { 422 Log.e(TAG, "remote exception when unregistering", e); 423 } 424 } else { 425 mAdvertiseCallback.onStartSuccess(null); 426 } 427 } else { 428 if (!isAdvertising) 429 mAdvertiseCallback.onStartFailure(status); 430 } 431 notifyAll(); 432 } 433 434 } 435 436 /** 437 * Callback reporting LE ATT MTU. 438 * 439 * @hide 440 */ 441 @Override 442 public void onConfigureMTU(String address, int mtu, int status) { 443 // no op 444 } 445 446 @Override 447 public void onConnectionCongested(String address, boolean congested) { 448 // no op 449 } 450 451 @Override 452 public void onBatchScanResults(List<ScanResult> results) { 453 // no op 454 } 455 456 @Override 457 public void onFoundOrLost(boolean onFound, String address, int rssi, 458 byte[] advData) { 459 // no op 460 } 461 } 462 463 //TODO: move this api to a common util class. 464 private void checkAdapterState() { 465 if (mBluetoothAdapter.getState() != mBluetoothAdapter.STATE_ON) { 466 throw new IllegalStateException("BT Adapter is not turned ON"); 467 } 468 } 469 470 private void postCallbackFailure(final AdvertiseCallback callback, final int error) { 471 mHandler.post(new Runnable() { 472 @Override 473 public void run() { 474 callback.onStartFailure(error); 475 } 476 }); 477 } 478} 479