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