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