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.os.Handler; 26import android.os.Looper; 27import android.os.ParcelUuid; 28import android.os.RemoteException; 29import android.util.Log; 30 31import java.util.Collections; 32import java.util.HashMap; 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 = 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 */ 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 */ 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 */ 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 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 postStartSetFailure(handler, callback, AdvertiseCallback.ADVERTISE_FAILED_INTERNAL_ERROR); 419 return; 420 } 421 422 IAdvertisingSetCallback wrapped = wrap(callback, handler); 423 if (mCallbackWrappers.putIfAbsent(callback, wrapped) != null) { 424 throw new IllegalArgumentException( 425 "callback instance already associated with advertising"); 426 } 427 428 try { 429 gatt.startAdvertisingSet(parameters, advertiseData, scanResponse, periodicParameters, 430 periodicData, duration, maxExtendedAdvertisingEvents, wrapped); 431 } catch (RemoteException e) { 432 Log.e(TAG, "Failed to start advertising set - ", e); 433 postStartSetFailure(handler, callback, AdvertiseCallback.ADVERTISE_FAILED_INTERNAL_ERROR); 434 return; 435 } 436 } 437 438 /** 439 * Used to dispose of a {@link AdvertisingSet} object, obtained with {@link 440 * BluetoothLeAdvertiser#startAdvertisingSet}. 441 */ 442 public void stopAdvertisingSet(AdvertisingSetCallback callback) { 443 if (callback == null) { 444 throw new IllegalArgumentException("callback cannot be null"); 445 } 446 447 IAdvertisingSetCallback wrapped = mCallbackWrappers.remove(callback); 448 if (wrapped == null) { 449 return; 450 } 451 452 IBluetoothGatt gatt; 453 try { 454 gatt = mBluetoothManager.getBluetoothGatt(); 455 gatt.stopAdvertisingSet(wrapped); 456 } catch (RemoteException e) { 457 Log.e(TAG, "Failed to stop advertising - ", e); 458 } 459 } 460 461 /** 462 * Cleans up advertisers. Should be called when bluetooth is down. 463 * 464 * @hide 465 */ 466 public void cleanup() { 467 mLegacyAdvertisers.clear(); 468 mCallbackWrappers.clear(); 469 mAdvertisingSets.clear(); 470 } 471 472 // Compute the size of advertisement data or scan resp 473 private int totalBytes(AdvertiseData data, boolean isFlagsIncluded) { 474 if (data == null) return 0; 475 // Flags field is omitted if the advertising is not connectable. 476 int size = (isFlagsIncluded) ? FLAGS_FIELD_BYTES : 0; 477 if (data.getServiceUuids() != null) { 478 int num16BitUuids = 0; 479 int num32BitUuids = 0; 480 int num128BitUuids = 0; 481 for (ParcelUuid uuid : data.getServiceUuids()) { 482 if (BluetoothUuid.is16BitUuid(uuid)) { 483 ++num16BitUuids; 484 } else if (BluetoothUuid.is32BitUuid(uuid)) { 485 ++num32BitUuids; 486 } else { 487 ++num128BitUuids; 488 } 489 } 490 // 16 bit service uuids are grouped into one field when doing advertising. 491 if (num16BitUuids != 0) { 492 size += OVERHEAD_BYTES_PER_FIELD + 493 num16BitUuids * BluetoothUuid.UUID_BYTES_16_BIT; 494 } 495 // 32 bit service uuids are grouped into one field when doing advertising. 496 if (num32BitUuids != 0) { 497 size += OVERHEAD_BYTES_PER_FIELD + 498 num32BitUuids * BluetoothUuid.UUID_BYTES_32_BIT; 499 } 500 // 128 bit service uuids are grouped into one field when doing advertising. 501 if (num128BitUuids != 0) { 502 size += OVERHEAD_BYTES_PER_FIELD + 503 num128BitUuids * BluetoothUuid.UUID_BYTES_128_BIT; 504 } 505 } 506 for (ParcelUuid uuid : data.getServiceData().keySet()) { 507 int uuidLen = BluetoothUuid.uuidToBytes(uuid).length; 508 size += OVERHEAD_BYTES_PER_FIELD + uuidLen 509 + byteLength(data.getServiceData().get(uuid)); 510 } 511 for (int i = 0; i < data.getManufacturerSpecificData().size(); ++i) { 512 size += OVERHEAD_BYTES_PER_FIELD + MANUFACTURER_SPECIFIC_DATA_LENGTH + 513 byteLength(data.getManufacturerSpecificData().valueAt(i)); 514 } 515 if (data.getIncludeTxPowerLevel()) { 516 size += OVERHEAD_BYTES_PER_FIELD + 1; // tx power level value is one byte. 517 } 518 if (data.getIncludeDeviceName() && mBluetoothAdapter.getName() != null) { 519 size += OVERHEAD_BYTES_PER_FIELD + mBluetoothAdapter.getName().length(); 520 } 521 return size; 522 } 523 524 private int byteLength(byte[] array) { 525 return array == null ? 0 : array.length; 526 } 527 528 IAdvertisingSetCallback wrap(AdvertisingSetCallback callback, Handler handler) { 529 return new IAdvertisingSetCallback.Stub() { 530 @Override 531 public void onAdvertisingSetStarted(int advertiserId, int txPower, int status) { 532 handler.post(new Runnable() { 533 @Override 534 public void run() { 535 if (status != AdvertisingSetCallback.ADVERTISE_SUCCESS) { 536 callback.onAdvertisingSetStarted(null, 0, status); 537 mCallbackWrappers.remove(callback); 538 return; 539 } 540 541 AdvertisingSet advertisingSet = 542 new AdvertisingSet(advertiserId, mBluetoothManager); 543 mAdvertisingSets.put(advertiserId, advertisingSet); 544 callback.onAdvertisingSetStarted(advertisingSet, txPower, status); 545 } 546 }); 547 } 548 549 @Override 550 public void onOwnAddressRead(int advertiserId, int addressType, String address) { 551 handler.post(new Runnable() { 552 @Override 553 public void run() { 554 AdvertisingSet advertisingSet = mAdvertisingSets.get(advertiserId); 555 callback.onOwnAddressRead(advertisingSet, addressType, address); 556 } 557 }); 558 } 559 560 @Override 561 public void onAdvertisingSetStopped(int advertiserId) { 562 handler.post(new Runnable() { 563 @Override 564 public void run() { 565 AdvertisingSet advertisingSet = mAdvertisingSets.get(advertiserId); 566 callback.onAdvertisingSetStopped(advertisingSet); 567 mAdvertisingSets.remove(advertiserId); 568 mCallbackWrappers.remove(callback); 569 } 570 }); 571 } 572 573 @Override 574 public void onAdvertisingEnabled(int advertiserId, boolean enabled, int status) { 575 handler.post(new Runnable() { 576 @Override 577 public void run() { 578 AdvertisingSet advertisingSet = mAdvertisingSets.get(advertiserId); 579 callback.onAdvertisingEnabled(advertisingSet, enabled, status); 580 } 581 }); 582 } 583 584 @Override 585 public void onAdvertisingDataSet(int advertiserId, int status) { 586 handler.post(new Runnable() { 587 @Override 588 public void run() { 589 AdvertisingSet advertisingSet = mAdvertisingSets.get(advertiserId); 590 callback.onAdvertisingDataSet(advertisingSet, status); 591 } 592 }); 593 } 594 595 @Override 596 public void onScanResponseDataSet(int advertiserId, int status) { 597 handler.post(new Runnable() { 598 @Override 599 public void run() { 600 AdvertisingSet advertisingSet = mAdvertisingSets.get(advertiserId); 601 callback.onScanResponseDataSet(advertisingSet, status); 602 } 603 }); 604 } 605 606 @Override 607 public void onAdvertisingParametersUpdated(int advertiserId, int txPower, int status) { 608 handler.post(new Runnable() { 609 @Override 610 public void run() { 611 AdvertisingSet advertisingSet = mAdvertisingSets.get(advertiserId); 612 callback.onAdvertisingParametersUpdated(advertisingSet, txPower, status); 613 } 614 }); 615 } 616 617 @Override 618 public void onPeriodicAdvertisingParametersUpdated(int advertiserId, int status) { 619 handler.post(new Runnable() { 620 @Override 621 public void run() { 622 AdvertisingSet advertisingSet = mAdvertisingSets.get(advertiserId); 623 callback.onPeriodicAdvertisingParametersUpdated(advertisingSet, status); 624 } 625 }); 626 } 627 628 @Override 629 public void onPeriodicAdvertisingDataSet(int advertiserId, int status) { 630 handler.post(new Runnable() { 631 @Override 632 public void run() { 633 AdvertisingSet advertisingSet = mAdvertisingSets.get(advertiserId); 634 callback.onPeriodicAdvertisingDataSet(advertisingSet, status); 635 } 636 }); 637 } 638 639 @Override 640 public void onPeriodicAdvertisingEnabled(int advertiserId, boolean enable, int status) { 641 handler.post(new Runnable() { 642 @Override 643 public void run() { 644 AdvertisingSet advertisingSet = mAdvertisingSets.get(advertiserId); 645 callback.onPeriodicAdvertisingEnabled(advertisingSet, enable, status); 646 } 647 }); 648 } 649 }; 650 } 651 652 private void postStartSetFailure(Handler handler, final AdvertisingSetCallback callback, 653 final int error) { 654 handler.post(new Runnable() { 655 @Override 656 public void run() { 657 callback.onAdvertisingSetStarted(null, 0, error); 658 } 659 }); 660 } 661 662 private void postStartFailure(final AdvertiseCallback callback, final int error) { 663 mHandler.post(new Runnable() { 664 @Override 665 public void run() { 666 callback.onStartFailure(error); 667 } 668 }); 669 } 670 671 private void postStartSuccess(final AdvertiseCallback callback, 672 final AdvertiseSettings settings) { 673 mHandler.post(new Runnable() { 674 675 @Override 676 public void run() { 677 callback.onStartSuccess(settings); 678 } 679 }); 680 } 681} 682