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 17 package android.bluetooth.le; 18 19 import android.bluetooth.BluetoothAdapter; 20 import android.bluetooth.BluetoothDevice; 21 import android.bluetooth.BluetoothGatt; 22 import android.bluetooth.BluetoothUuid; 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.Collections; 32 import java.util.HashMap; 33 import java.util.Map; 34 import 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 */ 49 public final class BluetoothLeAdvertiser { 50 51 private static final String TAG = "BluetoothLeAdvertiser"; 52 53 private static final int MAX_ADVERTISING_DATA_BYTES = 1650; 54 private static final int MAX_LEGACY_ADVERTISING_DATA_BYTES = 31; 55 // Each fields need one byte for field length and another byte for field type. 56 private static final int OVERHEAD_BYTES_PER_FIELD = 2; 57 // Flags field will be set by system. 58 private static final int FLAGS_FIELD_BYTES = 3; 59 private static final int MANUFACTURER_SPECIFIC_DATA_LENGTH = 2; 60 61 private final IBluetoothManager mBluetoothManager; 62 private final Handler mHandler; 63 private BluetoothAdapter mBluetoothAdapter; 64 private final Map<AdvertiseCallback, AdvertisingSetCallback> 65 mLegacyAdvertisers = new HashMap<>(); 66 private final Map<AdvertisingSetCallback, IAdvertisingSetCallback> 67 mCallbackWrappers = Collections.synchronizedMap(new HashMap<>()); 68 private final Map<Integer, AdvertisingSet> 69 mAdvertisingSets = Collections.synchronizedMap(new HashMap<>()); 70 71 /** 72 * Use BluetoothAdapter.getLeAdvertiser() instead. 73 * 74 * @param bluetoothManager BluetoothManager that conducts overall Bluetooth Management 75 * @hide 76 */ BluetoothLeAdvertiser(IBluetoothManager bluetoothManager)77 public BluetoothLeAdvertiser(IBluetoothManager bluetoothManager) { 78 mBluetoothManager = bluetoothManager; 79 mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter(); 80 mHandler = new Handler(Looper.getMainLooper()); 81 } 82 83 /** 84 * Start Bluetooth LE Advertising. On success, the {@code advertiseData} will be broadcasted. 85 * Returns immediately, the operation status is delivered through {@code callback}. 86 * <p> 87 * Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN} permission. 88 * 89 * @param settings Settings for Bluetooth LE advertising. 90 * @param advertiseData Advertisement data to be broadcasted. 91 * @param callback Callback for advertising status. 92 */ startAdvertising(AdvertiseSettings settings, AdvertiseData advertiseData, final AdvertiseCallback callback)93 public void startAdvertising(AdvertiseSettings settings, 94 AdvertiseData advertiseData, final AdvertiseCallback callback) { 95 startAdvertising(settings, advertiseData, null, callback); 96 } 97 98 /** 99 * Start Bluetooth LE Advertising. The {@code advertiseData} will be broadcasted if the 100 * operation succeeds. The {@code scanResponse} is returned when a scanning device sends an 101 * active scan request. This method returns immediately, the operation status is delivered 102 * through {@code callback}. 103 * <p> 104 * Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN} 105 * 106 * @param settings Settings for Bluetooth LE advertising. 107 * @param advertiseData Advertisement data to be advertised in advertisement packet. 108 * @param scanResponse Scan response associated with the advertisement data. 109 * @param callback Callback for advertising status. 110 */ startAdvertising(AdvertiseSettings settings, AdvertiseData advertiseData, AdvertiseData scanResponse, final AdvertiseCallback callback)111 public void startAdvertising(AdvertiseSettings settings, 112 AdvertiseData advertiseData, AdvertiseData scanResponse, 113 final AdvertiseCallback callback) { 114 synchronized (mLegacyAdvertisers) { 115 BluetoothLeUtils.checkAdapterStateOn(mBluetoothAdapter); 116 if (callback == null) { 117 throw new IllegalArgumentException("callback cannot be null"); 118 } 119 boolean isConnectable = settings.isConnectable(); 120 if (totalBytes(advertiseData, isConnectable) > MAX_LEGACY_ADVERTISING_DATA_BYTES || 121 totalBytes(scanResponse, false) > MAX_LEGACY_ADVERTISING_DATA_BYTES) { 122 postStartFailure(callback, AdvertiseCallback.ADVERTISE_FAILED_DATA_TOO_LARGE); 123 return; 124 } 125 if (mLegacyAdvertisers.containsKey(callback)) { 126 postStartFailure(callback, AdvertiseCallback.ADVERTISE_FAILED_ALREADY_STARTED); 127 return; 128 } 129 130 AdvertisingSetParameters.Builder parameters = new AdvertisingSetParameters.Builder(); 131 parameters.setLegacyMode(true); 132 parameters.setConnectable(isConnectable); 133 parameters.setScannable(true); // legacy advertisements we support are always scannable 134 if (settings.getMode() == AdvertiseSettings.ADVERTISE_MODE_LOW_POWER) { 135 parameters.setInterval(1600); // 1s 136 } else if (settings.getMode() == AdvertiseSettings.ADVERTISE_MODE_BALANCED) { 137 parameters.setInterval(400); // 250ms 138 } else if (settings.getMode() == AdvertiseSettings.ADVERTISE_MODE_LOW_LATENCY) { 139 parameters.setInterval(160); // 100ms 140 } 141 142 if (settings.getTxPowerLevel() == AdvertiseSettings.ADVERTISE_TX_POWER_ULTRA_LOW) { 143 parameters.setTxPowerLevel(-21); 144 } else if (settings.getTxPowerLevel() == AdvertiseSettings.ADVERTISE_TX_POWER_LOW) { 145 parameters.setTxPowerLevel(-15); 146 } else if (settings.getTxPowerLevel() == AdvertiseSettings.ADVERTISE_TX_POWER_MEDIUM) { 147 parameters.setTxPowerLevel(-7); 148 } else if (settings.getTxPowerLevel() == AdvertiseSettings.ADVERTISE_TX_POWER_HIGH) { 149 parameters.setTxPowerLevel(1); 150 } 151 152 int duration = 0; 153 int timeoutMillis = settings.getTimeout(); 154 if (timeoutMillis > 0) { 155 duration = (timeoutMillis < 10) ? 1 : timeoutMillis/10; 156 } 157 158 AdvertisingSetCallback wrapped = wrapOldCallback(callback, settings); 159 mLegacyAdvertisers.put(callback, wrapped); 160 startAdvertisingSet(parameters.build(), advertiseData, scanResponse, null, null, 161 duration, 0, wrapped); 162 } 163 } 164 wrapOldCallback(AdvertiseCallback callback, AdvertiseSettings settings)165 AdvertisingSetCallback wrapOldCallback(AdvertiseCallback callback, AdvertiseSettings settings) { 166 return new AdvertisingSetCallback() { 167 @Override 168 public void onAdvertisingSetStarted(AdvertisingSet advertisingSet, int txPower, 169 int status) { 170 if (status != AdvertisingSetCallback.ADVERTISE_SUCCESS) { 171 postStartFailure(callback, status); 172 return; 173 } 174 175 postStartSuccess(callback, settings); 176 } 177 178 /* Legacy advertiser is disabled on timeout */ 179 @Override 180 public void onAdvertisingEnabled(AdvertisingSet advertisingSet, boolean enabled, 181 int status) { 182 if (enabled == true) { 183 Log.e(TAG, "Legacy advertiser should be only disabled on timeout," + 184 " but was enabled!"); 185 return; 186 } 187 188 stopAdvertising(callback); 189 } 190 191 }; 192 } 193 194 /** 195 * Stop Bluetooth LE advertising. The {@code callback} must be the same one use in 196 * {@link BluetoothLeAdvertiser#startAdvertising}. 197 * <p> 198 * Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN} permission. 199 * 200 * @param callback {@link AdvertiseCallback} identifies the advertising instance to stop. 201 */ 202 public void stopAdvertising(final AdvertiseCallback callback) { 203 synchronized (mLegacyAdvertisers) { 204 if (callback == null) { 205 throw new IllegalArgumentException("callback cannot be null"); 206 } 207 AdvertisingSetCallback wrapper = mLegacyAdvertisers.get(callback); 208 if (wrapper == null) return; 209 210 stopAdvertisingSet(wrapper); 211 212 mLegacyAdvertisers.remove(callback); 213 } 214 } 215 216 /** 217 * Creates a new advertising set. If operation succeed, device will start advertising. This 218 * method returns immediately, the operation status is delivered through 219 * {@code callback.onAdvertisingSetStarted()}. 220 * <p> 221 * @param parameters advertising set parameters. 222 * @param advertiseData Advertisement data to be broadcasted. Size must not exceed 223 * {@link BluetoothAdapter#getLeMaximumAdvertisingDataLength}. If the 224 * advertisement is connectable, three bytes will be added for flags. 225 * @param scanResponse Scan response associated with the advertisement data. Size must not 226 * exceed {@link BluetoothAdapter#getLeMaximumAdvertisingDataLength}. 227 * @param periodicParameters periodic advertisng parameters. If null, periodic advertising will 228 * not be started. 229 * @param periodicData Periodic advertising data. Size must not exceed 230 * {@link BluetoothAdapter#getLeMaximumAdvertisingDataLength}. 231 * @param callback Callback for advertising set. 232 * @throws IllegalArgumentException when any of the data parameter exceed the maximum allowable 233 * size, or unsupported advertising PHY is selected, or when attempt to use 234 * Periodic Advertising feature is made when it's not supported by the 235 * controller. 236 */ 237 public void startAdvertisingSet(AdvertisingSetParameters parameters, 238 AdvertiseData advertiseData, AdvertiseData scanResponse, 239 PeriodicAdvertisingParameters periodicParameters, 240 AdvertiseData periodicData, AdvertisingSetCallback callback) { 241 startAdvertisingSet(parameters, advertiseData, scanResponse, periodicParameters, 242 periodicData, 0, 0, callback, new Handler(Looper.getMainLooper())); 243 } 244 245 /** 246 * Creates a new advertising set. If operation succeed, device will start advertising. This 247 * method returns immediately, the operation status is delivered through 248 * {@code callback.onAdvertisingSetStarted()}. 249 * <p> 250 * @param parameters advertising set parameters. 251 * @param advertiseData Advertisement data to be broadcasted. Size must not exceed 252 * {@link BluetoothAdapter#getLeMaximumAdvertisingDataLength}. If the 253 * advertisement is connectable, three bytes will be added for flags. 254 * @param scanResponse Scan response associated with the advertisement data. Size must not 255 * exceed {@link BluetoothAdapter#getLeMaximumAdvertisingDataLength}. 256 * @param periodicParameters periodic advertisng parameters. If null, periodic advertising will 257 * not be started. 258 * @param periodicData Periodic advertising data. Size must not exceed 259 * {@link BluetoothAdapter#getLeMaximumAdvertisingDataLength}. 260 * @param callback Callback for advertising set. 261 * @param handler thread upon which the callbacks will be invoked. 262 * @throws IllegalArgumentException when any of the data parameter exceed the maximum allowable 263 * size, or unsupported advertising PHY is selected, or when attempt to use 264 * Periodic Advertising feature is made when it's not supported by the 265 * controller. 266 */ 267 public void startAdvertisingSet(AdvertisingSetParameters parameters, 268 AdvertiseData advertiseData, AdvertiseData scanResponse, 269 PeriodicAdvertisingParameters periodicParameters, 270 AdvertiseData periodicData, AdvertisingSetCallback callback, 271 Handler handler) { 272 startAdvertisingSet(parameters, advertiseData, scanResponse, periodicParameters, 273 periodicData, 0, 0, callback, handler); 274 } 275 276 /** 277 * Creates a new advertising set. If operation succeed, device will start advertising. This 278 * method returns immediately, the operation status is delivered through 279 * {@code callback.onAdvertisingSetStarted()}. 280 * <p> 281 * @param parameters advertising set parameters. 282 * @param advertiseData Advertisement data to be broadcasted. Size must not exceed 283 * {@link BluetoothAdapter#getLeMaximumAdvertisingDataLength}. If the 284 * advertisement is connectable, three bytes will be added for flags. 285 * @param scanResponse Scan response associated with the advertisement data. Size must not 286 * exceed {@link BluetoothAdapter#getLeMaximumAdvertisingDataLength}. 287 * @param periodicParameters periodic advertisng parameters. If null, periodic advertising will 288 * not be started. 289 * @param periodicData Periodic advertising data. Size must not exceed 290 * {@link BluetoothAdapter#getLeMaximumAdvertisingDataLength}. 291 * @param duration advertising duration, in 10ms unit. Valid range is from 1 (10ms) to 292 * 65535 (655,350 ms). 0 means advertising should continue until stopped. 293 * @param maxExtendedAdvertisingEvents maximum number of extended advertising events the 294 * controller shall attempt to send prior to terminating the extended 295 * advertising, even if the duration has not expired. Valid range is 296 * from 1 to 255. 0 means no maximum. 297 * @param callback Callback for advertising set. 298 * @throws IllegalArgumentException when any of the data parameter exceed the maximum allowable 299 * size, or unsupported advertising PHY is selected, or when attempt to use 300 * Periodic Advertising feature is made when it's not supported by the 301 * controller. 302 */ 303 public void startAdvertisingSet(AdvertisingSetParameters parameters, 304 AdvertiseData advertiseData, AdvertiseData scanResponse, 305 PeriodicAdvertisingParameters periodicParameters, 306 AdvertiseData periodicData, int duration, 307 int maxExtendedAdvertisingEvents, 308 AdvertisingSetCallback callback) { 309 startAdvertisingSet(parameters, advertiseData, scanResponse, periodicParameters, 310 periodicData, duration, maxExtendedAdvertisingEvents, callback, 311 new Handler(Looper.getMainLooper())); 312 } 313 314 /** 315 * Creates a new advertising set. If operation succeed, device will start advertising. This 316 * method returns immediately, the operation status is delivered through 317 * {@code callback.onAdvertisingSetStarted()}. 318 * <p> 319 * @param parameters Advertising set parameters. 320 * @param advertiseData Advertisement data to be broadcasted. Size must not exceed 321 * {@link BluetoothAdapter#getLeMaximumAdvertisingDataLength}. If the 322 * advertisement is connectable, three bytes will be added for flags. 323 * @param scanResponse Scan response associated with the advertisement data. Size must not 324 * exceed {@link BluetoothAdapter#getLeMaximumAdvertisingDataLength} 325 * @param periodicParameters Periodic advertisng parameters. If null, periodic advertising will 326 * not be started. 327 * @param periodicData Periodic advertising data. Size must not exceed 328 * {@link BluetoothAdapter#getLeMaximumAdvertisingDataLength} 329 * @param duration advertising duration, in 10ms unit. Valid range is from 1 (10ms) to 330 * 65535 (655,350 ms). 0 means advertising should continue until stopped. 331 * @param maxExtendedAdvertisingEvents maximum number of extended advertising events the 332 * controller shall attempt to send prior to terminating the extended 333 * advertising, even if the duration has not expired. Valid range is 334 * from 1 to 255. 0 means no maximum. 335 * @param callback Callback for advertising set. 336 * @param handler Thread upon which the callbacks will be invoked. 337 * @throws IllegalArgumentException When any of the data parameter exceed the maximum allowable 338 * size, or unsupported advertising PHY is selected, or when attempt to use 339 * Periodic Advertising feature is made when it's not supported by the 340 * controller, or when maxExtendedAdvertisingEvents is used on a controller 341 * that doesn't support the LE Extended Advertising 342 */ 343 public void startAdvertisingSet(AdvertisingSetParameters parameters, 344 AdvertiseData advertiseData, AdvertiseData scanResponse, 345 PeriodicAdvertisingParameters periodicParameters, 346 AdvertiseData periodicData, int duration, 347 int maxExtendedAdvertisingEvents, AdvertisingSetCallback callback, 348 Handler handler) { 349 BluetoothLeUtils.checkAdapterStateOn(mBluetoothAdapter); 350 if (callback == null) { 351 throw new IllegalArgumentException("callback cannot be null"); 352 } 353 354 boolean isConnectable = parameters.isConnectable(); 355 if (parameters.isLegacy()) { 356 if (totalBytes(advertiseData, isConnectable) > MAX_LEGACY_ADVERTISING_DATA_BYTES) { 357 throw new IllegalArgumentException("Legacy advertising data too big"); 358 } 359 360 if (totalBytes(scanResponse, false) > MAX_LEGACY_ADVERTISING_DATA_BYTES) { 361 throw new IllegalArgumentException("Legacy scan response data too big"); 362 } 363 } else { 364 boolean supportCodedPhy = mBluetoothAdapter.isLeCodedPhySupported(); 365 boolean support2MPhy = mBluetoothAdapter.isLe2MPhySupported(); 366 int pphy = parameters.getPrimaryPhy(); 367 int sphy = parameters.getSecondaryPhy(); 368 if (pphy == BluetoothDevice.PHY_LE_CODED && !supportCodedPhy) { 369 throw new IllegalArgumentException("Unsupported primary PHY selected"); 370 } 371 372 if ((sphy == BluetoothDevice.PHY_LE_CODED && !supportCodedPhy) 373 || (sphy == BluetoothDevice.PHY_LE_2M && !support2MPhy)) { 374 throw new IllegalArgumentException("Unsupported secondary PHY selected"); 375 } 376 377 int maxData = mBluetoothAdapter.getLeMaximumAdvertisingDataLength(); 378 if (totalBytes(advertiseData, isConnectable) > maxData) { 379 throw new IllegalArgumentException("Advertising data too big"); 380 } 381 382 if (totalBytes(scanResponse, false) > maxData) { 383 throw new IllegalArgumentException("Scan response data too big"); 384 } 385 386 if (totalBytes(periodicData, false) > maxData) { 387 throw new IllegalArgumentException("Periodic advertising data too big"); 388 } 389 390 boolean supportPeriodic = mBluetoothAdapter.isLePeriodicAdvertisingSupported(); 391 if (periodicParameters != null && !supportPeriodic) { 392 throw new IllegalArgumentException( 393 "Controller does not support LE Periodic Advertising"); 394 } 395 } 396 397 if (maxExtendedAdvertisingEvents < 0 || maxExtendedAdvertisingEvents > 255) { 398 throw new IllegalArgumentException( 399 "maxExtendedAdvertisingEvents out of range: " + maxExtendedAdvertisingEvents); 400 } 401 402 if (maxExtendedAdvertisingEvents != 0 && 403 !mBluetoothAdapter.isLePeriodicAdvertisingSupported()) { 404 throw new IllegalArgumentException( 405 "Can't use maxExtendedAdvertisingEvents with controller that don't support " + 406 "LE Extended Advertising"); 407 } 408 409 if (duration < 0 || duration > 65535) { 410 throw new IllegalArgumentException("duration out of range: " + duration); 411 } 412 413 IBluetoothGatt gatt; 414 try { 415 gatt = mBluetoothManager.getBluetoothGatt(); 416 } catch (RemoteException e) { 417 Log.e(TAG, "Failed to get Bluetooth gatt - ", e); 418 throw new IllegalStateException("Failed to get Bluetooth"); 419 } 420 421 IAdvertisingSetCallback wrapped = wrap(callback, handler); 422 if (mCallbackWrappers.putIfAbsent(callback, wrapped) != null) { 423 throw new IllegalArgumentException( 424 "callback instance already associated with advertising"); 425 } 426 427 try { 428 gatt.startAdvertisingSet(parameters, advertiseData, scanResponse, periodicParameters, 429 periodicData, duration, maxExtendedAdvertisingEvents, wrapped); 430 } catch (RemoteException e) { 431 Log.e(TAG, "Failed to start advertising set - ", e); 432 throw new IllegalStateException("Failed to start advertising set"); 433 } 434 } 435 436 /** 437 * Used to dispose of a {@link AdvertisingSet} object, obtained with {@link 438 * BluetoothLeAdvertiser#startAdvertisingSet}. 439 */ 440 public void stopAdvertisingSet(AdvertisingSetCallback callback) { 441 if (callback == null) { 442 throw new IllegalArgumentException("callback cannot be null"); 443 } 444 445 IAdvertisingSetCallback wrapped = mCallbackWrappers.remove(callback); 446 if (wrapped == null) { 447 return; 448 } 449 450 IBluetoothGatt gatt; 451 try { 452 gatt = mBluetoothManager.getBluetoothGatt(); 453 gatt.stopAdvertisingSet(wrapped); 454 } catch (RemoteException e) { 455 Log.e(TAG, "Failed to stop advertising - ", e); 456 throw new IllegalStateException("Failed to stop advertising"); 457 } 458 } 459 460 /** 461 * Cleans up advertisers. Should be called when bluetooth is down. 462 * 463 * @hide 464 */ 465 public void cleanup() { 466 mLegacyAdvertisers.clear(); 467 mCallbackWrappers.clear(); 468 mAdvertisingSets.clear(); 469 } 470 471 // Compute the size of advertisement data or scan resp 472 private int totalBytes(AdvertiseData data, boolean isFlagsIncluded) { 473 if (data == null) return 0; 474 // Flags field is omitted if the advertising is not connectable. 475 int size = (isFlagsIncluded) ? FLAGS_FIELD_BYTES : 0; 476 if (data.getServiceUuids() != null) { 477 int num16BitUuids = 0; 478 int num32BitUuids = 0; 479 int num128BitUuids = 0; 480 for (ParcelUuid uuid : data.getServiceUuids()) { 481 if (BluetoothUuid.is16BitUuid(uuid)) { 482 ++num16BitUuids; 483 } else if (BluetoothUuid.is32BitUuid(uuid)) { 484 ++num32BitUuids; 485 } else { 486 ++num128BitUuids; 487 } 488 } 489 // 16 bit service uuids are grouped into one field when doing advertising. 490 if (num16BitUuids != 0) { 491 size += OVERHEAD_BYTES_PER_FIELD + 492 num16BitUuids * BluetoothUuid.UUID_BYTES_16_BIT; 493 } 494 // 32 bit service uuids are grouped into one field when doing advertising. 495 if (num32BitUuids != 0) { 496 size += OVERHEAD_BYTES_PER_FIELD + 497 num32BitUuids * BluetoothUuid.UUID_BYTES_32_BIT; 498 } 499 // 128 bit service uuids are grouped into one field when doing advertising. 500 if (num128BitUuids != 0) { 501 size += OVERHEAD_BYTES_PER_FIELD + 502 num128BitUuids * BluetoothUuid.UUID_BYTES_128_BIT; 503 } 504 } 505 for (ParcelUuid uuid : data.getServiceData().keySet()) { 506 int uuidLen = BluetoothUuid.uuidToBytes(uuid).length; 507 size += OVERHEAD_BYTES_PER_FIELD + uuidLen 508 + byteLength(data.getServiceData().get(uuid)); 509 } 510 for (int i = 0; i < data.getManufacturerSpecificData().size(); ++i) { 511 size += OVERHEAD_BYTES_PER_FIELD + MANUFACTURER_SPECIFIC_DATA_LENGTH + 512 byteLength(data.getManufacturerSpecificData().valueAt(i)); 513 } 514 if (data.getIncludeTxPowerLevel()) { 515 size += OVERHEAD_BYTES_PER_FIELD + 1; // tx power level value is one byte. 516 } 517 if (data.getIncludeDeviceName() && mBluetoothAdapter.getName() != null) { 518 size += OVERHEAD_BYTES_PER_FIELD + mBluetoothAdapter.getName().length(); 519 } 520 return size; 521 } 522 523 private int byteLength(byte[] array) { 524 return array == null ? 0 : array.length; 525 } 526 527 IAdvertisingSetCallback wrap(AdvertisingSetCallback callback, Handler handler) { 528 return new IAdvertisingSetCallback.Stub() { 529 @Override 530 public void onAdvertisingSetStarted(int advertiserId, int txPower, int status) { 531 handler.post(new Runnable() { 532 @Override 533 public void run() { 534 if (status != AdvertisingSetCallback.ADVERTISE_SUCCESS) { 535 callback.onAdvertisingSetStarted(null, 0, status); 536 mCallbackWrappers.remove(callback); 537 return; 538 } 539 540 AdvertisingSet advertisingSet = 541 new AdvertisingSet(advertiserId, mBluetoothManager); 542 mAdvertisingSets.put(advertiserId, advertisingSet); 543 callback.onAdvertisingSetStarted(advertisingSet, txPower, status); 544 } 545 }); 546 } 547 548 @Override 549 public void onOwnAddressRead(int advertiserId, int addressType, String address) { 550 handler.post(new Runnable() { 551 @Override 552 public void run() { 553 AdvertisingSet advertisingSet = mAdvertisingSets.get(advertiserId); 554 callback.onOwnAddressRead(advertisingSet, addressType, address); 555 } 556 }); 557 } 558 559 @Override 560 public void onAdvertisingSetStopped(int advertiserId) { 561 handler.post(new Runnable() { 562 @Override 563 public void run() { 564 AdvertisingSet advertisingSet = mAdvertisingSets.get(advertiserId); 565 callback.onAdvertisingSetStopped(advertisingSet); 566 mAdvertisingSets.remove(advertiserId); 567 mCallbackWrappers.remove(callback); 568 } 569 }); 570 } 571 572 @Override 573 public void onAdvertisingEnabled(int advertiserId, boolean enabled, int status) { 574 handler.post(new Runnable() { 575 @Override 576 public void run() { 577 AdvertisingSet advertisingSet = mAdvertisingSets.get(advertiserId); 578 callback.onAdvertisingEnabled(advertisingSet, enabled, status); 579 } 580 }); 581 } 582 583 @Override 584 public void onAdvertisingDataSet(int advertiserId, int status) { 585 handler.post(new Runnable() { 586 @Override 587 public void run() { 588 AdvertisingSet advertisingSet = mAdvertisingSets.get(advertiserId); 589 callback.onAdvertisingDataSet(advertisingSet, status); 590 } 591 }); 592 } 593 594 @Override 595 public void onScanResponseDataSet(int advertiserId, int status) { 596 handler.post(new Runnable() { 597 @Override 598 public void run() { 599 AdvertisingSet advertisingSet = mAdvertisingSets.get(advertiserId); 600 callback.onScanResponseDataSet(advertisingSet, status); 601 } 602 }); 603 } 604 605 @Override 606 public void onAdvertisingParametersUpdated(int advertiserId, int txPower, int status) { 607 handler.post(new Runnable() { 608 @Override 609 public void run() { 610 AdvertisingSet advertisingSet = mAdvertisingSets.get(advertiserId); 611 callback.onAdvertisingParametersUpdated(advertisingSet, txPower, status); 612 } 613 }); 614 } 615 616 @Override 617 public void onPeriodicAdvertisingParametersUpdated(int advertiserId, int status) { 618 handler.post(new Runnable() { 619 @Override 620 public void run() { 621 AdvertisingSet advertisingSet = mAdvertisingSets.get(advertiserId); 622 callback.onPeriodicAdvertisingParametersUpdated(advertisingSet, status); 623 } 624 }); 625 } 626 627 @Override 628 public void onPeriodicAdvertisingDataSet(int advertiserId, int status) { 629 handler.post(new Runnable() { 630 @Override 631 public void run() { 632 AdvertisingSet advertisingSet = mAdvertisingSets.get(advertiserId); 633 callback.onPeriodicAdvertisingDataSet(advertisingSet, status); 634 } 635 }); 636 } 637 638 @Override 639 public void onPeriodicAdvertisingEnabled(int advertiserId, boolean enable, int status) { 640 handler.post(new Runnable() { 641 @Override 642 public void run() { 643 AdvertisingSet advertisingSet = mAdvertisingSets.get(advertiserId); 644 callback.onPeriodicAdvertisingEnabled(advertisingSet, enable, status); 645 } 646 }); 647 } 648 }; 649 } 650 651 private void postStartFailure(final AdvertiseCallback callback, final int error) { 652 mHandler.post(new Runnable() { 653 @Override 654 public void run() { 655 callback.onStartFailure(error); 656 } 657 }); 658 } 659 660 private void postStartSuccess(final AdvertiseCallback callback, 661 final AdvertiseSettings settings) { 662 mHandler.post(new Runnable() { 663 664 @Override 665 public void run() { 666 callback.onStartSuccess(settings); 667 } 668 }); 669 } 670 } 671