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 static android.Manifest.permission.BLUETOOTH_ADVERTISE; 20 import static android.Manifest.permission.BLUETOOTH_PRIVILEGED; 21 22 import static java.util.Objects.requireNonNull; 23 24 import android.annotation.NonNull; 25 import android.annotation.Nullable; 26 import android.annotation.RequiresNoPermission; 27 import android.annotation.RequiresPermission; 28 import android.annotation.SuppressLint; 29 import android.annotation.SystemApi; 30 import android.bluetooth.BluetoothAdapter; 31 import android.bluetooth.BluetoothDevice; 32 import android.bluetooth.BluetoothGattServer; 33 import android.bluetooth.BluetoothUuid; 34 import android.bluetooth.IBluetoothAdvertise; 35 import android.bluetooth.annotations.RequiresBluetoothAdvertisePermission; 36 import android.bluetooth.annotations.RequiresLegacyBluetoothAdminPermission; 37 import android.content.AttributionSource; 38 import android.os.Handler; 39 import android.os.IBinder; 40 import android.os.Looper; 41 import android.os.ParcelUuid; 42 import android.os.RemoteException; 43 import android.util.Log; 44 45 import java.util.Collections; 46 import java.util.HashMap; 47 import java.util.List; 48 import java.util.Map; 49 50 /** 51 * This class provides a way to perform Bluetooth LE advertise operations, such as starting and 52 * stopping advertising. An advertiser can broadcast up to 31 bytes of advertisement data 53 * represented by {@link AdvertiseData}. 54 * 55 * <p>To get an instance of {@link BluetoothLeAdvertiser}, call the {@link 56 * BluetoothAdapter#getBluetoothLeAdvertiser()} method. 57 * 58 * @see AdvertiseData 59 */ 60 public final class BluetoothLeAdvertiser { 61 private static final String TAG = BluetoothLeAdvertiser.class.getSimpleName(); 62 63 private static final int MAX_LEGACY_ADVERTISING_DATA_BYTES = 31; 64 // Each fields need one byte for field length and another byte for field type. 65 private static final int OVERHEAD_BYTES_PER_FIELD = 2; 66 // Flags field will be set by system. 67 private static final int FLAGS_FIELD_BYTES = 3; 68 private static final int MANUFACTURER_SPECIFIC_DATA_LENGTH = 2; 69 70 private final Map<AdvertiseCallback, AdvertisingSetCallback> mLegacyAdvertisers = 71 new HashMap<>(); 72 private final Map<AdvertisingSetCallback, IAdvertisingSetCallback> mCallbackWrappers = 73 Collections.synchronizedMap(new HashMap<>()); 74 private final Map<Integer, AdvertisingSet> mAdvertisingSets = 75 Collections.synchronizedMap(new HashMap<>()); 76 77 private final BluetoothAdapter mBluetoothAdapter; 78 private final AttributionSource mAttributionSource; 79 private final Handler mHandler; 80 81 /** 82 * Use BluetoothAdapter.getLeAdvertiser() instead. 83 * 84 * @hide 85 */ BluetoothLeAdvertiser(BluetoothAdapter bluetoothAdapter)86 public BluetoothLeAdvertiser(BluetoothAdapter bluetoothAdapter) { 87 mBluetoothAdapter = requireNonNull(bluetoothAdapter); 88 mAttributionSource = mBluetoothAdapter.getAttributionSource(); 89 mHandler = new Handler(Looper.getMainLooper()); 90 } 91 92 /** 93 * Start Bluetooth LE Advertising. On success, the {@code advertiseData} will be broadcasted. 94 * Returns immediately, the operation status is delivered through {@code callback}. 95 * 96 * <p>Requires the {@link android.Manifest.permission#BLUETOOTH_PRIVILEGED} permission only when 97 * {@code settings.getOwnAddressType()} is different from {@code 98 * AdvertisingSetParameters.ADDRESS_TYPE_DEFAULT}. 99 * 100 * <p>The {@link android.Manifest.permission#BLUETOOTH_ADVERTISE} permission is always enforced. 101 * 102 * @param settings Settings for Bluetooth LE advertising. 103 * @param advertiseData Advertisement data to be broadcasted. 104 * @param callback Callback for advertising status. 105 */ 106 @RequiresLegacyBluetoothAdminPermission 107 @RequiresBluetoothAdvertisePermission 108 @RequiresPermission( 109 allOf = {BLUETOOTH_ADVERTISE, BLUETOOTH_PRIVILEGED}, 110 conditional = true) startAdvertising( AdvertiseSettings settings, AdvertiseData advertiseData, final AdvertiseCallback callback)111 public void startAdvertising( 112 AdvertiseSettings settings, 113 AdvertiseData advertiseData, 114 final AdvertiseCallback callback) { 115 startAdvertising(settings, advertiseData, null, callback); 116 } 117 118 /** 119 * Start Bluetooth LE Advertising. The {@code advertiseData} will be broadcasted if the 120 * operation succeeds. The {@code scanResponse} is returned when a scanning device sends an 121 * active scan request. This method returns immediately, the operation status is delivered 122 * through {@code callback}. 123 * 124 * <p>Requires the {@link android.Manifest.permission#BLUETOOTH_PRIVILEGED} permission only when 125 * {@code settings.getOwnAddressType()} is different from {@code 126 * AdvertisingSetParameters.ADDRESS_TYPE_DEFAULT}. 127 * 128 * <p>The {@link android.Manifest.permission#BLUETOOTH_ADVERTISE} permission is always enforced. 129 * 130 * @param settings Settings for Bluetooth LE advertising. 131 * @param advertiseData Advertisement data to be advertised in advertisement packet. 132 * @param scanResponse Scan response associated with the advertisement data. 133 * @param callback Callback for advertising status. 134 */ 135 @RequiresLegacyBluetoothAdminPermission 136 @RequiresBluetoothAdvertisePermission 137 @RequiresPermission( 138 allOf = {BLUETOOTH_ADVERTISE, BLUETOOTH_PRIVILEGED}, 139 conditional = true) startAdvertising( AdvertiseSettings settings, AdvertiseData advertiseData, AdvertiseData scanResponse, final AdvertiseCallback callback)140 public void startAdvertising( 141 AdvertiseSettings settings, 142 AdvertiseData advertiseData, 143 AdvertiseData scanResponse, 144 final AdvertiseCallback callback) { 145 synchronized (mLegacyAdvertisers) { 146 BluetoothLeUtils.checkAdapterStateOn(mBluetoothAdapter); 147 if (callback == null) { 148 throw new IllegalArgumentException("callback cannot be null"); 149 } 150 boolean isConnectable = settings.isConnectable(); 151 boolean isDiscoverable = settings.isDiscoverable(); 152 boolean hasFlags = isConnectable && isDiscoverable; 153 if (totalBytes(advertiseData, hasFlags) > MAX_LEGACY_ADVERTISING_DATA_BYTES 154 || totalBytes(scanResponse, false) > MAX_LEGACY_ADVERTISING_DATA_BYTES) { 155 postStartFailure(callback, AdvertiseCallback.ADVERTISE_FAILED_DATA_TOO_LARGE); 156 return; 157 } 158 if (mLegacyAdvertisers.containsKey(callback)) { 159 postStartFailure(callback, AdvertiseCallback.ADVERTISE_FAILED_ALREADY_STARTED); 160 return; 161 } 162 163 AdvertisingSetParameters.Builder parameters = new AdvertisingSetParameters.Builder(); 164 parameters.setLegacyMode(true); 165 parameters.setConnectable(isConnectable); 166 parameters.setDiscoverable(isDiscoverable); 167 parameters.setScannable(true); // legacy advertisements we support are always scannable 168 parameters.setOwnAddressType(settings.getOwnAddressType()); 169 if (settings.getMode() == AdvertiseSettings.ADVERTISE_MODE_LOW_POWER) { 170 parameters.setInterval(1600); // 1s 171 } else if (settings.getMode() == AdvertiseSettings.ADVERTISE_MODE_BALANCED) { 172 parameters.setInterval(400); // 250ms 173 } else if (settings.getMode() == AdvertiseSettings.ADVERTISE_MODE_LOW_LATENCY) { 174 parameters.setInterval(160); // 100ms 175 } 176 177 if (settings.getTxPowerLevel() == AdvertiseSettings.ADVERTISE_TX_POWER_ULTRA_LOW) { 178 Log.d(TAG, "TxPower == ADVERTISE_TX_POWER_ULTRA_LOW"); 179 parameters.setTxPowerLevel(-21); 180 } else if (settings.getTxPowerLevel() == AdvertiseSettings.ADVERTISE_TX_POWER_LOW) { 181 Log.d(TAG, "TxPower == ADVERTISE_TX_POWER_LOW"); 182 parameters.setTxPowerLevel(-15); 183 } else if (settings.getTxPowerLevel() == AdvertiseSettings.ADVERTISE_TX_POWER_MEDIUM) { 184 Log.d(TAG, "TxPower == ADVERTISE_TX_POWER_MEDIUM"); 185 parameters.setTxPowerLevel(-7); 186 } else if (settings.getTxPowerLevel() == AdvertiseSettings.ADVERTISE_TX_POWER_HIGH) { 187 Log.d(TAG, "TxPower == ADVERTISE_TX_POWER_HIGH"); 188 parameters.setTxPowerLevel(1); 189 } 190 191 int duration = 0; 192 int timeoutMillis = settings.getTimeout(); 193 if (timeoutMillis > 0) { 194 duration = (timeoutMillis < 10) ? 1 : timeoutMillis / 10; 195 } 196 197 AdvertisingSetCallback wrapped = wrapOldCallback(callback, settings); 198 mLegacyAdvertisers.put(callback, wrapped); 199 startAdvertisingSet( 200 parameters.build(), 201 advertiseData, 202 scanResponse, 203 null, 204 null, 205 duration, 206 0, 207 wrapped); 208 } 209 } 210 211 @SuppressLint({ 212 "AndroidFrameworkBluetoothPermission", 213 "AndroidFrameworkRequiresPermission", 214 }) wrapOldCallback(AdvertiseCallback callback, AdvertiseSettings settings)215 AdvertisingSetCallback wrapOldCallback(AdvertiseCallback callback, AdvertiseSettings settings) { 216 return new AdvertisingSetCallback() { 217 @Override 218 public void onAdvertisingSetStarted( 219 AdvertisingSet advertisingSet, int txPower, int status) { 220 if (status != AdvertisingSetCallback.ADVERTISE_SUCCESS) { 221 postStartFailure(callback, status); 222 return; 223 } 224 225 postStartSuccess(callback, settings); 226 } 227 228 /* Legacy advertiser is disabled on timeout */ 229 @Override 230 public void onAdvertisingEnabled( 231 AdvertisingSet advertisingSet, boolean enabled, int status) { 232 if (enabled) { 233 Log.e( 234 TAG, 235 "Legacy advertiser should be only disabled on timeout," 236 + " but was enabled!"); 237 return; 238 } 239 240 stopAdvertising(callback); 241 } 242 }; 243 } 244 245 /** 246 * Stop Bluetooth LE advertising. The {@code callback} must be the same one use in {@link 247 * BluetoothLeAdvertiser#startAdvertising}. 248 * 249 * @param callback {@link AdvertiseCallback} identifies the advertising instance to stop. 250 */ 251 @RequiresLegacyBluetoothAdminPermission 252 @RequiresBluetoothAdvertisePermission 253 @RequiresPermission(BLUETOOTH_ADVERTISE) 254 public void stopAdvertising(final AdvertiseCallback callback) { 255 synchronized (mLegacyAdvertisers) { 256 if (callback == null) { 257 throw new IllegalArgumentException("callback cannot be null"); 258 } 259 AdvertisingSetCallback wrapper = mLegacyAdvertisers.get(callback); 260 if (wrapper == null) return; 261 262 stopAdvertisingSet(wrapper); 263 264 mLegacyAdvertisers.remove(callback); 265 } 266 } 267 268 /** 269 * Creates a new advertising set. If operation succeed, device will start advertising. This 270 * method returns immediately, the operation status is delivered through {@code 271 * callback.onAdvertisingSetStarted()}. 272 * 273 * <p>Requires the {@link android.Manifest.permission#BLUETOOTH_PRIVILEGED} permission when 274 * {@code parameters.getOwnAddressType()} is different from {@code 275 * AdvertisingSetParameters.ADDRESS_TYPE_DEFAULT} or {@code parameters.isDirected()} is true. 276 * 277 * <p>The {@link android.Manifest.permission#BLUETOOTH_ADVERTISE} permission is always enforced. 278 * 279 * @param parameters advertising set parameters. 280 * @param advertiseData Advertisement data to be broadcasted. Size must not exceed {@link 281 * BluetoothAdapter#getLeMaximumAdvertisingDataLength}. If the advertisement is connectable, 282 * three bytes will be added for flags. 283 * @param scanResponse Scan response associated with the advertisement data. Size must not 284 * exceed {@link BluetoothAdapter#getLeMaximumAdvertisingDataLength}. 285 * @param periodicParameters periodic advertising parameters. If null, periodic advertising will 286 * not be started. 287 * @param periodicData Periodic advertising data. Size must not exceed {@link 288 * BluetoothAdapter#getLeMaximumAdvertisingDataLength}. 289 * @param callback Callback for advertising set. 290 * @throws IllegalArgumentException when any of the data parameter exceed the maximum allowable 291 * size, or unsupported advertising PHY is selected, or when attempt to use Periodic 292 * Advertising feature is made when it's not supported by the controller. 293 */ 294 @RequiresLegacyBluetoothAdminPermission 295 @RequiresBluetoothAdvertisePermission 296 @RequiresPermission( 297 allOf = {BLUETOOTH_ADVERTISE, BLUETOOTH_PRIVILEGED}, 298 conditional = true) 299 public void startAdvertisingSet( 300 AdvertisingSetParameters parameters, 301 AdvertiseData advertiseData, 302 AdvertiseData scanResponse, 303 PeriodicAdvertisingParameters periodicParameters, 304 AdvertiseData periodicData, 305 AdvertisingSetCallback callback) { 306 startAdvertisingSet( 307 parameters, 308 advertiseData, 309 scanResponse, 310 periodicParameters, 311 periodicData, 312 0, 313 0, 314 callback, 315 new Handler(Looper.getMainLooper())); 316 } 317 318 /** 319 * Creates a new advertising set. If operation succeed, device will start advertising. This 320 * method returns immediately, the operation status is delivered through {@code 321 * callback.onAdvertisingSetStarted()}. 322 * 323 * <p>Requires the {@link android.Manifest.permission#BLUETOOTH_PRIVILEGED} permission when 324 * {@code parameters.getOwnAddressType()} is different from {@code 325 * AdvertisingSetParameters.ADDRESS_TYPE_DEFAULT} or {@code parameters.isDirected()} is true. 326 * 327 * <p>The {@link android.Manifest.permission#BLUETOOTH_ADVERTISE} permission is always enforced. 328 * 329 * @param parameters advertising set parameters. 330 * @param advertiseData Advertisement data to be broadcasted. Size must not exceed {@link 331 * BluetoothAdapter#getLeMaximumAdvertisingDataLength}. If the advertisement is connectable, 332 * three bytes will be added for flags. 333 * @param scanResponse Scan response associated with the advertisement data. Size must not 334 * exceed {@link BluetoothAdapter#getLeMaximumAdvertisingDataLength}. 335 * @param periodicParameters periodic advertising parameters. If null, periodic advertising will 336 * not be started. 337 * @param periodicData Periodic advertising data. Size must not exceed {@link 338 * BluetoothAdapter#getLeMaximumAdvertisingDataLength}. 339 * @param callback Callback for advertising set. 340 * @param handler thread upon which the callbacks will be invoked. 341 * @throws IllegalArgumentException when any of the data parameter exceed the maximum allowable 342 * size, or unsupported advertising PHY is selected, or when attempt to use Periodic 343 * Advertising feature is made when it's not supported by the controller. 344 */ 345 @RequiresLegacyBluetoothAdminPermission 346 @RequiresBluetoothAdvertisePermission 347 @RequiresPermission( 348 allOf = {BLUETOOTH_ADVERTISE, BLUETOOTH_PRIVILEGED}, 349 conditional = true) 350 public void startAdvertisingSet( 351 AdvertisingSetParameters parameters, 352 AdvertiseData advertiseData, 353 AdvertiseData scanResponse, 354 PeriodicAdvertisingParameters periodicParameters, 355 AdvertiseData periodicData, 356 AdvertisingSetCallback callback, 357 Handler handler) { 358 startAdvertisingSet( 359 parameters, 360 advertiseData, 361 scanResponse, 362 periodicParameters, 363 periodicData, 364 0, 365 0, 366 callback, 367 handler); 368 } 369 370 /** 371 * Creates a new advertising set. If operation succeed, device will start advertising. This 372 * method returns immediately, the operation status is delivered through {@code 373 * callback.onAdvertisingSetStarted()}. 374 * 375 * <p>Requires the {@link android.Manifest.permission#BLUETOOTH_PRIVILEGED} permission when 376 * {@code parameters.getOwnAddressType()} is different from {@code 377 * AdvertisingSetParameters.ADDRESS_TYPE_DEFAULT} or {@code parameters.isDirected()} is true. 378 * 379 * <p>The {@link android.Manifest.permission#BLUETOOTH_ADVERTISE} permission is always enforced. 380 * 381 * @param parameters advertising set parameters. 382 * @param advertiseData Advertisement data to be broadcasted. Size must not exceed {@link 383 * BluetoothAdapter#getLeMaximumAdvertisingDataLength}. If the advertisement is connectable, 384 * three bytes will be added for flags. 385 * @param scanResponse Scan response associated with the advertisement data. Size must not 386 * exceed {@link BluetoothAdapter#getLeMaximumAdvertisingDataLength}. 387 * @param periodicParameters periodic advertising parameters. If null, periodic advertising will 388 * not be started. 389 * @param periodicData Periodic advertising data. Size must not exceed {@link 390 * BluetoothAdapter#getLeMaximumAdvertisingDataLength}. 391 * @param duration advertising duration, in 10ms unit. Valid range is from 1 (10ms) to 65535 392 * (655,350 ms). 0 means advertising should continue until stopped. 393 * @param maxExtendedAdvertisingEvents maximum number of extended advertising events the 394 * controller shall attempt to send prior to terminating the extended advertising, even if 395 * the duration has not expired. Valid range is from 1 to 255. 0 means no maximum. 396 * @param callback Callback for advertising set. 397 * @throws IllegalArgumentException when any of the data parameter exceed the maximum allowable 398 * size, or unsupported advertising PHY is selected, or when attempt to use Periodic 399 * Advertising feature is made when it's not supported by the controller. 400 */ 401 @RequiresLegacyBluetoothAdminPermission 402 @RequiresBluetoothAdvertisePermission 403 @RequiresPermission( 404 allOf = {BLUETOOTH_ADVERTISE, BLUETOOTH_PRIVILEGED}, 405 conditional = true) 406 public void startAdvertisingSet( 407 AdvertisingSetParameters parameters, 408 AdvertiseData advertiseData, 409 AdvertiseData scanResponse, 410 PeriodicAdvertisingParameters periodicParameters, 411 AdvertiseData periodicData, 412 int duration, 413 int maxExtendedAdvertisingEvents, 414 AdvertisingSetCallback callback) { 415 startAdvertisingSet( 416 parameters, 417 advertiseData, 418 scanResponse, 419 periodicParameters, 420 periodicData, 421 duration, 422 maxExtendedAdvertisingEvents, 423 callback, 424 new Handler(Looper.getMainLooper())); 425 } 426 427 /** 428 * Creates a new advertising set. If operation succeed, device will start advertising. This 429 * method returns immediately, the operation status is delivered through {@code 430 * callback.onAdvertisingSetStarted()}. 431 * 432 * <p>Requires the {@link android.Manifest.permission#BLUETOOTH_PRIVILEGED} permission when 433 * {@code parameters.getOwnAddressType()} is different from {@code 434 * AdvertisingSetParameters.ADDRESS_TYPE_DEFAULT} or {@code parameters.isDirected()} is true. 435 * 436 * <p>The {@link android.Manifest.permission#BLUETOOTH_ADVERTISE} permission is always enforced. 437 * 438 * @param parameters Advertising set parameters. 439 * @param advertiseData Advertisement data to be broadcasted. Size must not exceed {@link 440 * BluetoothAdapter#getLeMaximumAdvertisingDataLength}. If the advertisement is connectable, 441 * three bytes will be added for flags. 442 * @param scanResponse Scan response associated with the advertisement data. Size must not 443 * exceed {@link BluetoothAdapter#getLeMaximumAdvertisingDataLength} 444 * @param periodicParameters Periodic advertising parameters. If null, periodic advertising will 445 * not be started. 446 * @param periodicData Periodic advertising data. Size must not exceed {@link 447 * BluetoothAdapter#getLeMaximumAdvertisingDataLength} 448 * @param duration advertising duration, in 10ms unit. Valid range is from 1 (10ms) to 65535 449 * (655,350 ms). 0 means advertising should continue until stopped. 450 * @param maxExtendedAdvertisingEvents maximum number of extended advertising events the 451 * controller shall attempt to send prior to terminating the extended advertising, even if 452 * the duration has not expired. Valid range is from 1 to 255. 0 means no maximum. 453 * @param callback Callback for advertising set. 454 * @param handler Thread upon which the callbacks will be invoked. 455 * @throws IllegalArgumentException When any of the data parameter exceed the maximum allowable 456 * size, or unsupported advertising PHY is selected, or when attempt to use Periodic 457 * Advertising feature is made when it's not supported by the controller, or when 458 * maxExtendedAdvertisingEvents is used on a controller that doesn't support the LE Extended 459 * Advertising 460 */ 461 @RequiresLegacyBluetoothAdminPermission 462 @RequiresBluetoothAdvertisePermission 463 @RequiresPermission( 464 allOf = {BLUETOOTH_ADVERTISE, BLUETOOTH_PRIVILEGED}, 465 conditional = true) 466 public void startAdvertisingSet( 467 AdvertisingSetParameters parameters, 468 AdvertiseData advertiseData, 469 AdvertiseData scanResponse, 470 PeriodicAdvertisingParameters periodicParameters, 471 AdvertiseData periodicData, 472 int duration, 473 int maxExtendedAdvertisingEvents, 474 AdvertisingSetCallback callback, 475 Handler handler) { 476 startAdvertisingSet( 477 parameters, 478 advertiseData, 479 scanResponse, 480 periodicParameters, 481 periodicData, 482 duration, 483 maxExtendedAdvertisingEvents, 484 null, 485 callback, 486 handler); 487 } 488 489 /** 490 * Creates a new advertising set. If operation succeed, device will start advertising. This 491 * method returns immediately, the operation status is delivered through {@code 492 * callback.onAdvertisingSetStarted()}. 493 * 494 * <p>If the {@code gattServer} is provided, connections to this advertisement will only see the 495 * services/characteristics in this server, rather than the union of all GATT services (across 496 * all opened servers). 497 * 498 * <p>Requires the {@link android.Manifest.permission#BLUETOOTH_PRIVILEGED} permission when 499 * {@code parameters.getOwnAddressType()} is different from {@code 500 * AdvertisingSetParameters.ADDRESS_TYPE_DEFAULT} or {@code parameters.isDirected()} is true or 501 * when the {@code gattServer} is already registered 502 * 503 * <p>The {@link android.Manifest.permission#BLUETOOTH_ADVERTISE} permission is always enforced. 504 * 505 * @param parameters Advertising set parameters. 506 * @param advertiseData Advertisement data to be broadcasted. Size must not exceed {@link 507 * BluetoothAdapter#getLeMaximumAdvertisingDataLength}. If the advertisement is connectable, 508 * three bytes will be added for flags. 509 * @param scanResponse Scan response associated with the advertisement data. Size must not 510 * exceed {@link BluetoothAdapter#getLeMaximumAdvertisingDataLength} 511 * @param periodicParameters Periodic advertising parameters. If null, periodic advertising will 512 * not be started. 513 * @param periodicData Periodic advertising data. Size must not exceed {@link 514 * BluetoothAdapter#getLeMaximumAdvertisingDataLength} 515 * @param duration advertising duration, in 10ms unit. Valid range is from 1 (10ms) to 65535 516 * (655,350 ms). 0 means advertising should continue until stopped. 517 * @param maxExtendedAdvertisingEvents maximum number of extended advertising events the 518 * controller shall attempt to send prior to terminating the extended advertising, even if 519 * the duration has not expired. Valid range is from 1 to 255. 0 means no maximum. 520 * @param gattServer the GATT server that will "own" connections derived from this advertising 521 * set. 522 * @param callback Callback for advertising set. 523 * @param handler Thread upon which the callbacks will be invoked. 524 * @throws IllegalArgumentException When any of the data parameter exceed the maximum allowable 525 * size, or unsupported advertising PHY is selected, or when attempt to use Periodic 526 * Advertising feature is made when it's not supported by the controller, or when 527 * maxExtendedAdvertisingEvents is used on a controller that doesn't support the LE Extended 528 * Advertising 529 * @hide 530 */ 531 @SystemApi 532 @SuppressLint("ExecutorRegistration") 533 @RequiresBluetoothAdvertisePermission 534 @RequiresPermission( 535 allOf = {BLUETOOTH_ADVERTISE, BLUETOOTH_PRIVILEGED}, 536 conditional = true) 537 public void startAdvertisingSet( 538 @NonNull AdvertisingSetParameters parameters, 539 @Nullable AdvertiseData advertiseData, 540 @Nullable AdvertiseData scanResponse, 541 @Nullable PeriodicAdvertisingParameters periodicParameters, 542 @Nullable AdvertiseData periodicData, 543 int duration, 544 int maxExtendedAdvertisingEvents, 545 @Nullable BluetoothGattServer gattServer, 546 @Nullable AdvertisingSetCallback callback, 547 @SuppressLint("ListenerLast") @NonNull Handler handler) { 548 BluetoothLeUtils.checkAdapterStateOn(mBluetoothAdapter); 549 if (callback == null) { 550 throw new IllegalArgumentException("callback cannot be null"); 551 } 552 553 boolean isConnectable = parameters.isConnectable(); 554 boolean isDiscoverable = parameters.isDiscoverable(); 555 boolean hasFlags = isConnectable && isDiscoverable; 556 if (parameters.isLegacy()) { 557 if (totalBytes(advertiseData, hasFlags) > MAX_LEGACY_ADVERTISING_DATA_BYTES) { 558 throw new IllegalArgumentException("Legacy advertising data too big"); 559 } 560 561 if (totalBytes(scanResponse, false) > MAX_LEGACY_ADVERTISING_DATA_BYTES) { 562 throw new IllegalArgumentException("Legacy scan response data too big"); 563 } 564 } else { 565 boolean supportCodedPhy = mBluetoothAdapter.isLeCodedPhySupported(); 566 boolean support2MPhy = mBluetoothAdapter.isLe2MPhySupported(); 567 int pphy = parameters.getPrimaryPhy(); 568 int sphy = parameters.getSecondaryPhy(); 569 if (pphy == BluetoothDevice.PHY_LE_CODED && !supportCodedPhy) { 570 throw new IllegalArgumentException("Unsupported primary PHY selected"); 571 } 572 573 if ((sphy == BluetoothDevice.PHY_LE_CODED && !supportCodedPhy) 574 || (sphy == BluetoothDevice.PHY_LE_2M && !support2MPhy)) { 575 throw new IllegalArgumentException("Unsupported secondary PHY selected"); 576 } 577 578 int maxData = mBluetoothAdapter.getLeMaximumAdvertisingDataLength(); 579 if (totalBytes(advertiseData, hasFlags) > maxData) { 580 throw new IllegalArgumentException("Advertising data too big"); 581 } 582 583 if (totalBytes(scanResponse, false) > maxData) { 584 throw new IllegalArgumentException("Scan response data too big"); 585 } 586 587 if (totalBytes(periodicData, false) > maxData) { 588 throw new IllegalArgumentException("Periodic advertising data too big"); 589 } 590 591 boolean supportPeriodic = mBluetoothAdapter.isLePeriodicAdvertisingSupported(); 592 if (periodicParameters != null && !supportPeriodic) { 593 throw new IllegalArgumentException( 594 "Controller does not support LE Periodic Advertising"); 595 } 596 } 597 598 if (maxExtendedAdvertisingEvents < 0 || maxExtendedAdvertisingEvents > 255) { 599 throw new IllegalArgumentException( 600 "maxExtendedAdvertisingEvents out of range: " + maxExtendedAdvertisingEvents); 601 } 602 603 if (maxExtendedAdvertisingEvents != 0 604 && !mBluetoothAdapter.isLeExtendedAdvertisingSupported()) { 605 throw new IllegalArgumentException( 606 "Can't use maxExtendedAdvertisingEvents with controller that doesn't support " 607 + "LE Extended Advertising"); 608 } 609 610 if (duration < 0 || duration > 65535) { 611 throw new IllegalArgumentException("duration out of range: " + duration); 612 } 613 614 IBluetoothAdvertise advertise = mBluetoothAdapter.getBluetoothAdvertise(); 615 if (advertise == null) { 616 Log.e(TAG, "Bluetooth Advertise is null"); 617 postStartSetFailure( 618 handler, callback, AdvertiseCallback.ADVERTISE_FAILED_INTERNAL_ERROR); 619 return; 620 } 621 622 IAdvertisingSetCallback wrapped = wrap(callback, handler); 623 if (mCallbackWrappers.putIfAbsent(callback, wrapped) != null) { 624 throw new IllegalArgumentException( 625 "callback instance already associated with advertising"); 626 } 627 628 try { 629 advertise.startAdvertisingSet( 630 parameters, 631 advertiseData, 632 scanResponse, 633 periodicParameters, 634 periodicData, 635 duration, 636 maxExtendedAdvertisingEvents, 637 gattServer == null ? 0 : gattServer.getServerIf(), 638 wrapped, 639 mAttributionSource); 640 } catch (RemoteException e) { 641 Log.e(TAG, "Failed to start advertising set - ", e); 642 postStartSetFailure( 643 handler, callback, AdvertiseCallback.ADVERTISE_FAILED_INTERNAL_ERROR); 644 return; 645 } catch (SecurityException e) { 646 mCallbackWrappers.remove(callback); 647 throw e; 648 } 649 } 650 651 /** 652 * Used to dispose of a {@link AdvertisingSet} object, obtained with {@link 653 * BluetoothLeAdvertiser#startAdvertisingSet}. 654 */ 655 @RequiresLegacyBluetoothAdminPermission 656 @RequiresBluetoothAdvertisePermission 657 @RequiresPermission(BLUETOOTH_ADVERTISE) 658 public void stopAdvertisingSet(AdvertisingSetCallback callback) { 659 if (callback == null) { 660 throw new IllegalArgumentException("callback cannot be null"); 661 } 662 663 IAdvertisingSetCallback wrapped = mCallbackWrappers.remove(callback); 664 if (wrapped == null) { 665 return; 666 } 667 668 IBluetoothAdvertise advertise = mBluetoothAdapter.getBluetoothAdvertise(); 669 if (advertise == null) { 670 Log.e(TAG, "Bluetooth Advertise is null"); 671 return; 672 } 673 try { 674 advertise.stopAdvertisingSet(wrapped, mAttributionSource); 675 } catch (RemoteException e) { 676 Log.e(TAG, "Failed to stop advertising - ", e); 677 } 678 } 679 680 /** 681 * Cleans up advertisers. Should be called when bluetooth is down. 682 * 683 * @hide 684 */ 685 @RequiresNoPermission 686 public void cleanup() { 687 mLegacyAdvertisers.clear(); 688 mCallbackWrappers.clear(); 689 mAdvertisingSets.clear(); 690 } 691 692 // Compute the size of advertisement data or scan resp 693 @RequiresBluetoothAdvertisePermission 694 @RequiresPermission(BLUETOOTH_ADVERTISE) 695 private int totalBytes(AdvertiseData data, boolean isFlagsIncluded) { 696 if (data == null) return 0; 697 // Flags field is omitted if the advertising is not connectable. 698 int size = (isFlagsIncluded) ? FLAGS_FIELD_BYTES : 0; 699 size += calculateUuidsSize(data.getServiceUuids()); 700 size += calculateUuidsSize(data.getServiceSolicitationUuids()); 701 702 for (TransportDiscoveryData transportDiscoveryData : data.getTransportDiscoveryData()) { 703 size += OVERHEAD_BYTES_PER_FIELD + transportDiscoveryData.totalBytes(); 704 } 705 for (Map.Entry<ParcelUuid, byte[]> entry : data.getServiceData().entrySet()) { 706 final ParcelUuid uuid = entry.getKey(); 707 final byte[] serviceData = entry.getValue(); 708 final int uuidLen = BluetoothUuid.uuidToBytes(uuid).length; 709 size += OVERHEAD_BYTES_PER_FIELD + uuidLen + byteLength(serviceData); 710 } 711 for (int i = 0; i < data.getManufacturerSpecificData().size(); ++i) { 712 size += 713 OVERHEAD_BYTES_PER_FIELD 714 + MANUFACTURER_SPECIFIC_DATA_LENGTH 715 + byteLength(data.getManufacturerSpecificData().valueAt(i)); 716 } 717 if (data.getIncludeTxPowerLevel()) { 718 size += OVERHEAD_BYTES_PER_FIELD + 1; // tx power level value is one byte. 719 } 720 if (data.getIncludeDeviceName()) { 721 final int length = mBluetoothAdapter.getNameLengthForAdvertise(); 722 if (length >= 0) { 723 size += OVERHEAD_BYTES_PER_FIELD + length; 724 } 725 } 726 return size; 727 } 728 729 private static int calculateUuidsSize(List<ParcelUuid> uuids) { 730 if (uuids == null) return 0; 731 int num16BitUuids = 0; 732 int num32BitUuids = 0; 733 int num128BitUuids = 0; 734 for (ParcelUuid uuid : uuids) { 735 if (BluetoothUuid.is16BitUuid(uuid)) { 736 ++num16BitUuids; 737 } else if (BluetoothUuid.is32BitUuid(uuid)) { 738 ++num32BitUuids; 739 } else { 740 ++num128BitUuids; 741 } 742 } 743 int size = 0; 744 // 16 bit service uuids are grouped into one field when doing advertising. 745 if (num16BitUuids != 0) { 746 size += OVERHEAD_BYTES_PER_FIELD + num16BitUuids * BluetoothUuid.UUID_BYTES_16_BIT; 747 } 748 // 32 bit service uuids are grouped into one field when doing advertising. 749 if (num32BitUuids != 0) { 750 size += OVERHEAD_BYTES_PER_FIELD + num32BitUuids * BluetoothUuid.UUID_BYTES_32_BIT; 751 } 752 // 128 bit service uuids are grouped into one field when doing advertising. 753 if (num128BitUuids != 0) { 754 size += OVERHEAD_BYTES_PER_FIELD + num128BitUuids * BluetoothUuid.UUID_BYTES_128_BIT; 755 } 756 return size; 757 } 758 759 private static int byteLength(byte[] array) { 760 return array == null ? 0 : array.length; 761 } 762 763 @SuppressLint("AndroidFrameworkBluetoothPermission") 764 IAdvertisingSetCallback wrap(AdvertisingSetCallback callback, Handler handler) { 765 return new IAdvertisingSetCallback.Stub() { 766 @Override 767 public void onAdvertisingSetStarted( 768 IBinder advertiseBinder, int advertiserId, int txPower, int status) { 769 handler.post( 770 () -> { 771 if (status != AdvertisingSetCallback.ADVERTISE_SUCCESS) { 772 callback.onAdvertisingSetStarted(null, 0, status); 773 mCallbackWrappers.remove(callback); 774 return; 775 } 776 777 AdvertisingSet advertisingSet = 778 new AdvertisingSet( 779 IBluetoothAdvertise.Stub.asInterface(advertiseBinder), 780 advertiserId, 781 mBluetoothAdapter, 782 mAttributionSource); 783 mAdvertisingSets.put(advertiserId, advertisingSet); 784 callback.onAdvertisingSetStarted(advertisingSet, txPower, status); 785 }); 786 } 787 788 @Override 789 public void onOwnAddressRead(int advertiserId, int addressType, String address) { 790 handler.post( 791 () -> { 792 AdvertisingSet advertisingSet = mAdvertisingSets.get(advertiserId); 793 callback.onOwnAddressRead(advertisingSet, addressType, address); 794 }); 795 } 796 797 @Override 798 public void onAdvertisingSetStopped(int advertiserId) { 799 handler.post( 800 () -> { 801 AdvertisingSet advertisingSet = mAdvertisingSets.get(advertiserId); 802 callback.onAdvertisingSetStopped(advertisingSet); 803 mAdvertisingSets.remove(advertiserId); 804 mCallbackWrappers.remove(callback); 805 }); 806 } 807 808 @Override 809 public void onAdvertisingEnabled(int advertiserId, boolean enabled, int status) { 810 handler.post( 811 () -> { 812 AdvertisingSet advertisingSet = mAdvertisingSets.get(advertiserId); 813 callback.onAdvertisingEnabled(advertisingSet, enabled, status); 814 }); 815 } 816 817 @Override 818 public void onAdvertisingDataSet(int advertiserId, int status) { 819 handler.post( 820 () -> { 821 AdvertisingSet advertisingSet = mAdvertisingSets.get(advertiserId); 822 callback.onAdvertisingDataSet(advertisingSet, status); 823 }); 824 } 825 826 @Override 827 public void onScanResponseDataSet(int advertiserId, int status) { 828 handler.post( 829 () -> { 830 AdvertisingSet advertisingSet = mAdvertisingSets.get(advertiserId); 831 callback.onScanResponseDataSet(advertisingSet, status); 832 }); 833 } 834 835 @Override 836 public void onAdvertisingParametersUpdated(int advertiserId, int txPower, int status) { 837 handler.post( 838 () -> { 839 AdvertisingSet advertisingSet = mAdvertisingSets.get(advertiserId); 840 callback.onAdvertisingParametersUpdated( 841 advertisingSet, txPower, status); 842 }); 843 } 844 845 @Override 846 public void onPeriodicAdvertisingParametersUpdated(int advertiserId, int status) { 847 handler.post( 848 () -> { 849 AdvertisingSet advertisingSet = mAdvertisingSets.get(advertiserId); 850 callback.onPeriodicAdvertisingParametersUpdated(advertisingSet, status); 851 }); 852 } 853 854 @Override 855 public void onPeriodicAdvertisingDataSet(int advertiserId, int status) { 856 handler.post( 857 () -> { 858 AdvertisingSet advertisingSet = mAdvertisingSets.get(advertiserId); 859 callback.onPeriodicAdvertisingDataSet(advertisingSet, status); 860 }); 861 } 862 863 @Override 864 public void onPeriodicAdvertisingEnabled(int advertiserId, boolean enable, int status) { 865 handler.post( 866 () -> { 867 AdvertisingSet advertisingSet = mAdvertisingSets.get(advertiserId); 868 callback.onPeriodicAdvertisingEnabled(advertisingSet, enable, status); 869 }); 870 } 871 }; 872 } 873 874 @SuppressLint("AndroidFrameworkBluetoothPermission") 875 private static void postStartSetFailure( 876 Handler handler, final AdvertisingSetCallback callback, final int error) { 877 handler.post(() -> callback.onAdvertisingSetStarted(null, 0, error)); 878 } 879 880 @SuppressLint("AndroidFrameworkBluetoothPermission") 881 private void postStartFailure(final AdvertiseCallback callback, final int error) { 882 mHandler.post(() -> callback.onStartFailure(error)); 883 } 884 885 @SuppressLint("AndroidFrameworkBluetoothPermission") 886 private void postStartSuccess( 887 final AdvertiseCallback callback, final AdvertiseSettings settings) { 888 mHandler.post(() -> callback.onStartSuccess(settings)); 889 } 890 } 891