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