| /* |
| * Copyright (C) 2014 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| package android.bluetooth.le; |
| |
| import android.annotation.NonNull; |
| import android.annotation.Nullable; |
| import android.annotation.RequiresNoPermission; |
| import android.annotation.RequiresPermission; |
| import android.annotation.SuppressLint; |
| import android.annotation.SystemApi; |
| import android.bluetooth.BluetoothAdapter; |
| import android.bluetooth.BluetoothDevice; |
| import android.bluetooth.BluetoothGattServer; |
| import android.bluetooth.BluetoothUuid; |
| import android.bluetooth.IBluetoothGatt; |
| import android.bluetooth.annotations.RequiresBluetoothAdvertisePermission; |
| import android.bluetooth.annotations.RequiresLegacyBluetoothAdminPermission; |
| import android.content.AttributionSource; |
| import android.os.Handler; |
| import android.os.IBinder; |
| import android.os.Looper; |
| import android.os.ParcelUuid; |
| import android.os.RemoteException; |
| import android.util.Log; |
| |
| import java.util.Collections; |
| import java.util.HashMap; |
| import java.util.Map; |
| import java.util.Objects; |
| |
| /** |
| * This class provides a way to perform Bluetooth LE advertise operations, such as starting and |
| * stopping advertising. An advertiser can broadcast up to 31 bytes of advertisement data |
| * represented by {@link AdvertiseData}. |
| * |
| * <p>To get an instance of {@link BluetoothLeAdvertiser}, call the {@link |
| * BluetoothAdapter#getBluetoothLeAdvertiser()} method. |
| * |
| * @see AdvertiseData |
| */ |
| public final class BluetoothLeAdvertiser { |
| |
| private static final String TAG = "BluetoothLeAdvertiser"; |
| |
| private static final int MAX_LEGACY_ADVERTISING_DATA_BYTES = 31; |
| // Each fields need one byte for field length and another byte for field type. |
| private static final int OVERHEAD_BYTES_PER_FIELD = 2; |
| // Flags field will be set by system. |
| private static final int FLAGS_FIELD_BYTES = 3; |
| private static final int MANUFACTURER_SPECIFIC_DATA_LENGTH = 2; |
| |
| private final BluetoothAdapter mBluetoothAdapter; |
| private final AttributionSource mAttributionSource; |
| |
| private final Handler mHandler; |
| private final Map<AdvertiseCallback, AdvertisingSetCallback> mLegacyAdvertisers = |
| new HashMap<>(); |
| private final Map<AdvertisingSetCallback, IAdvertisingSetCallback> mCallbackWrappers = |
| Collections.synchronizedMap(new HashMap<>()); |
| private final Map<Integer, AdvertisingSet> mAdvertisingSets = |
| Collections.synchronizedMap(new HashMap<>()); |
| |
| /** |
| * Use BluetoothAdapter.getLeAdvertiser() instead. |
| * |
| * @hide |
| */ |
| public BluetoothLeAdvertiser(BluetoothAdapter bluetoothAdapter) { |
| mBluetoothAdapter = Objects.requireNonNull(bluetoothAdapter); |
| mAttributionSource = mBluetoothAdapter.getAttributionSource(); |
| mHandler = new Handler(Looper.getMainLooper()); |
| } |
| |
| /** |
| * Start Bluetooth LE Advertising. On success, the {@code advertiseData} will be broadcasted. |
| * Returns immediately, the operation status is delivered through {@code callback}. |
| * |
| * @param settings Settings for Bluetooth LE advertising. |
| * @param advertiseData Advertisement data to be broadcasted. |
| * @param callback Callback for advertising status. |
| */ |
| @RequiresLegacyBluetoothAdminPermission |
| @RequiresBluetoothAdvertisePermission |
| @RequiresPermission(android.Manifest.permission.BLUETOOTH_ADVERTISE) |
| public void startAdvertising( |
| AdvertiseSettings settings, |
| AdvertiseData advertiseData, |
| final AdvertiseCallback callback) { |
| startAdvertising(settings, advertiseData, null, callback); |
| } |
| |
| /** |
| * Start Bluetooth LE Advertising. The {@code advertiseData} will be broadcasted if the |
| * operation succeeds. The {@code scanResponse} is returned when a scanning device sends an |
| * active scan request. This method returns immediately, the operation status is delivered |
| * through {@code callback}. |
| * |
| * @param settings Settings for Bluetooth LE advertising. |
| * @param advertiseData Advertisement data to be advertised in advertisement packet. |
| * @param scanResponse Scan response associated with the advertisement data. |
| * @param callback Callback for advertising status. |
| */ |
| @RequiresLegacyBluetoothAdminPermission |
| @RequiresBluetoothAdvertisePermission |
| @RequiresPermission(android.Manifest.permission.BLUETOOTH_ADVERTISE) |
| public void startAdvertising( |
| AdvertiseSettings settings, |
| AdvertiseData advertiseData, |
| AdvertiseData scanResponse, |
| final AdvertiseCallback callback) { |
| synchronized (mLegacyAdvertisers) { |
| BluetoothLeUtils.checkAdapterStateOn(mBluetoothAdapter); |
| if (callback == null) { |
| throw new IllegalArgumentException("callback cannot be null"); |
| } |
| boolean isConnectable = settings.isConnectable(); |
| boolean isDiscoverable = settings.isDiscoverable(); |
| boolean hasFlags = isConnectable && isDiscoverable; |
| if (totalBytes(advertiseData, hasFlags) > MAX_LEGACY_ADVERTISING_DATA_BYTES |
| || totalBytes(scanResponse, false) > MAX_LEGACY_ADVERTISING_DATA_BYTES) { |
| postStartFailure(callback, AdvertiseCallback.ADVERTISE_FAILED_DATA_TOO_LARGE); |
| return; |
| } |
| if (mLegacyAdvertisers.containsKey(callback)) { |
| postStartFailure(callback, AdvertiseCallback.ADVERTISE_FAILED_ALREADY_STARTED); |
| return; |
| } |
| |
| AdvertisingSetParameters.Builder parameters = new AdvertisingSetParameters.Builder(); |
| parameters.setLegacyMode(true); |
| parameters.setConnectable(isConnectable); |
| parameters.setDiscoverable(isDiscoverable); |
| parameters.setScannable(true); // legacy advertisements we support are always scannable |
| parameters.setOwnAddressType(settings.getOwnAddressType()); |
| if (settings.getMode() == AdvertiseSettings.ADVERTISE_MODE_LOW_POWER) { |
| parameters.setInterval(1600); // 1s |
| } else if (settings.getMode() == AdvertiseSettings.ADVERTISE_MODE_BALANCED) { |
| parameters.setInterval(400); // 250ms |
| } else if (settings.getMode() == AdvertiseSettings.ADVERTISE_MODE_LOW_LATENCY) { |
| parameters.setInterval(160); // 100ms |
| } |
| |
| if (settings.getTxPowerLevel() == AdvertiseSettings.ADVERTISE_TX_POWER_ULTRA_LOW) { |
| Log.d(TAG, "TxPower == ADVERTISE_TX_POWER_ULTRA_LOW"); |
| parameters.setTxPowerLevel(-21); |
| } else if (settings.getTxPowerLevel() == AdvertiseSettings.ADVERTISE_TX_POWER_LOW) { |
| Log.d(TAG, "TxPower == ADVERTISE_TX_POWER_LOW"); |
| parameters.setTxPowerLevel(-15); |
| } else if (settings.getTxPowerLevel() == AdvertiseSettings.ADVERTISE_TX_POWER_MEDIUM) { |
| Log.d(TAG, "TxPower == ADVERTISE_TX_POWER_MEDIUM"); |
| parameters.setTxPowerLevel(-7); |
| } else if (settings.getTxPowerLevel() == AdvertiseSettings.ADVERTISE_TX_POWER_HIGH) { |
| Log.d(TAG, "TxPower == ADVERTISE_TX_POWER_HIGH"); |
| parameters.setTxPowerLevel(1); |
| } |
| |
| int duration = 0; |
| int timeoutMillis = settings.getTimeout(); |
| if (timeoutMillis > 0) { |
| duration = (timeoutMillis < 10) ? 1 : timeoutMillis / 10; |
| } |
| |
| AdvertisingSetCallback wrapped = wrapOldCallback(callback, settings); |
| mLegacyAdvertisers.put(callback, wrapped); |
| startAdvertisingSet( |
| parameters.build(), |
| advertiseData, |
| scanResponse, |
| null, |
| null, |
| duration, |
| 0, |
| wrapped); |
| } |
| } |
| |
| @SuppressLint({ |
| "AndroidFrameworkBluetoothPermission", |
| "AndroidFrameworkRequiresPermission", |
| }) |
| AdvertisingSetCallback wrapOldCallback(AdvertiseCallback callback, AdvertiseSettings settings) { |
| return new AdvertisingSetCallback() { |
| @Override |
| public void onAdvertisingSetStarted( |
| AdvertisingSet advertisingSet, int txPower, int status) { |
| if (status != AdvertisingSetCallback.ADVERTISE_SUCCESS) { |
| postStartFailure(callback, status); |
| return; |
| } |
| |
| postStartSuccess(callback, settings); |
| } |
| |
| /* Legacy advertiser is disabled on timeout */ |
| @Override |
| public void onAdvertisingEnabled( |
| AdvertisingSet advertisingSet, boolean enabled, int status) { |
| if (enabled) { |
| Log.e( |
| TAG, |
| "Legacy advertiser should be only disabled on timeout," |
| + " but was enabled!"); |
| return; |
| } |
| |
| stopAdvertising(callback); |
| } |
| }; |
| } |
| |
| /** |
| * Stop Bluetooth LE advertising. The {@code callback} must be the same one use in {@link |
| * BluetoothLeAdvertiser#startAdvertising}. |
| * |
| * @param callback {@link AdvertiseCallback} identifies the advertising instance to stop. |
| */ |
| @RequiresLegacyBluetoothAdminPermission |
| @RequiresBluetoothAdvertisePermission |
| @RequiresPermission(android.Manifest.permission.BLUETOOTH_ADVERTISE) |
| public void stopAdvertising(final AdvertiseCallback callback) { |
| synchronized (mLegacyAdvertisers) { |
| if (callback == null) { |
| throw new IllegalArgumentException("callback cannot be null"); |
| } |
| AdvertisingSetCallback wrapper = mLegacyAdvertisers.get(callback); |
| if (wrapper == null) return; |
| |
| stopAdvertisingSet(wrapper); |
| |
| mLegacyAdvertisers.remove(callback); |
| } |
| } |
| |
| /** |
| * Creates a new advertising set. If operation succeed, device will start advertising. This |
| * method returns immediately, the operation status is delivered through {@code |
| * callback.onAdvertisingSetStarted()}. |
| * |
| * <p> |
| * |
| * @param parameters advertising set parameters. |
| * @param advertiseData Advertisement data to be broadcasted. Size must not exceed {@link |
| * BluetoothAdapter#getLeMaximumAdvertisingDataLength}. If the advertisement is connectable, |
| * three bytes will be added for flags. |
| * @param scanResponse Scan response associated with the advertisement data. Size must not |
| * exceed {@link BluetoothAdapter#getLeMaximumAdvertisingDataLength}. |
| * @param periodicParameters periodic advertisng parameters. If null, periodic advertising will |
| * not be started. |
| * @param periodicData Periodic advertising data. Size must not exceed {@link |
| * BluetoothAdapter#getLeMaximumAdvertisingDataLength}. |
| * @param callback Callback for advertising set. |
| * @throws IllegalArgumentException when any of the data parameter exceed the maximum allowable |
| * size, or unsupported advertising PHY is selected, or when attempt to use Periodic |
| * Advertising feature is made when it's not supported by the controller. |
| */ |
| @RequiresLegacyBluetoothAdminPermission |
| @RequiresBluetoothAdvertisePermission |
| @RequiresPermission(android.Manifest.permission.BLUETOOTH_ADVERTISE) |
| public void startAdvertisingSet( |
| AdvertisingSetParameters parameters, |
| AdvertiseData advertiseData, |
| AdvertiseData scanResponse, |
| PeriodicAdvertisingParameters periodicParameters, |
| AdvertiseData periodicData, |
| AdvertisingSetCallback callback) { |
| startAdvertisingSet( |
| parameters, |
| advertiseData, |
| scanResponse, |
| periodicParameters, |
| periodicData, |
| 0, |
| 0, |
| callback, |
| new Handler(Looper.getMainLooper())); |
| } |
| |
| /** |
| * Creates a new advertising set. If operation succeed, device will start advertising. This |
| * method returns immediately, the operation status is delivered through {@code |
| * callback.onAdvertisingSetStarted()}. |
| * |
| * <p> |
| * |
| * @param parameters advertising set parameters. |
| * @param advertiseData Advertisement data to be broadcasted. Size must not exceed {@link |
| * BluetoothAdapter#getLeMaximumAdvertisingDataLength}. If the advertisement is connectable, |
| * three bytes will be added for flags. |
| * @param scanResponse Scan response associated with the advertisement data. Size must not |
| * exceed {@link BluetoothAdapter#getLeMaximumAdvertisingDataLength}. |
| * @param periodicParameters periodic advertisng parameters. If null, periodic advertising will |
| * not be started. |
| * @param periodicData Periodic advertising data. Size must not exceed {@link |
| * BluetoothAdapter#getLeMaximumAdvertisingDataLength}. |
| * @param callback Callback for advertising set. |
| * @param handler thread upon which the callbacks will be invoked. |
| * @throws IllegalArgumentException when any of the data parameter exceed the maximum allowable |
| * size, or unsupported advertising PHY is selected, or when attempt to use Periodic |
| * Advertising feature is made when it's not supported by the controller. |
| */ |
| @RequiresLegacyBluetoothAdminPermission |
| @RequiresBluetoothAdvertisePermission |
| @RequiresPermission(android.Manifest.permission.BLUETOOTH_ADVERTISE) |
| public void startAdvertisingSet( |
| AdvertisingSetParameters parameters, |
| AdvertiseData advertiseData, |
| AdvertiseData scanResponse, |
| PeriodicAdvertisingParameters periodicParameters, |
| AdvertiseData periodicData, |
| AdvertisingSetCallback callback, |
| Handler handler) { |
| startAdvertisingSet( |
| parameters, |
| advertiseData, |
| scanResponse, |
| periodicParameters, |
| periodicData, |
| 0, |
| 0, |
| callback, |
| handler); |
| } |
| |
| /** |
| * Creates a new advertising set. If operation succeed, device will start advertising. This |
| * method returns immediately, the operation status is delivered through {@code |
| * callback.onAdvertisingSetStarted()}. |
| * |
| * <p> |
| * |
| * @param parameters advertising set parameters. |
| * @param advertiseData Advertisement data to be broadcasted. Size must not exceed {@link |
| * BluetoothAdapter#getLeMaximumAdvertisingDataLength}. If the advertisement is connectable, |
| * three bytes will be added for flags. |
| * @param scanResponse Scan response associated with the advertisement data. Size must not |
| * exceed {@link BluetoothAdapter#getLeMaximumAdvertisingDataLength}. |
| * @param periodicParameters periodic advertisng parameters. If null, periodic advertising will |
| * not be started. |
| * @param periodicData Periodic advertising data. Size must not exceed {@link |
| * BluetoothAdapter#getLeMaximumAdvertisingDataLength}. |
| * @param duration advertising duration, in 10ms unit. Valid range is from 1 (10ms) to 65535 |
| * (655,350 ms). 0 means advertising should continue until stopped. |
| * @param maxExtendedAdvertisingEvents maximum number of extended advertising events the |
| * controller shall attempt to send prior to terminating the extended advertising, even if |
| * the duration has not expired. Valid range is from 1 to 255. 0 means no maximum. |
| * @param callback Callback for advertising set. |
| * @throws IllegalArgumentException when any of the data parameter exceed the maximum allowable |
| * size, or unsupported advertising PHY is selected, or when attempt to use Periodic |
| * Advertising feature is made when it's not supported by the controller. |
| */ |
| @RequiresLegacyBluetoothAdminPermission |
| @RequiresBluetoothAdvertisePermission |
| @RequiresPermission(android.Manifest.permission.BLUETOOTH_ADVERTISE) |
| public void startAdvertisingSet( |
| AdvertisingSetParameters parameters, |
| AdvertiseData advertiseData, |
| AdvertiseData scanResponse, |
| PeriodicAdvertisingParameters periodicParameters, |
| AdvertiseData periodicData, |
| int duration, |
| int maxExtendedAdvertisingEvents, |
| AdvertisingSetCallback callback) { |
| startAdvertisingSet( |
| parameters, |
| advertiseData, |
| scanResponse, |
| periodicParameters, |
| periodicData, |
| duration, |
| maxExtendedAdvertisingEvents, |
| callback, |
| new Handler(Looper.getMainLooper())); |
| } |
| |
| /** |
| * Creates a new advertising set. If operation succeed, device will start advertising. This |
| * method returns immediately, the operation status is delivered through {@code |
| * callback.onAdvertisingSetStarted()}. |
| * |
| * <p> |
| * |
| * @param parameters Advertising set parameters. |
| * @param advertiseData Advertisement data to be broadcasted. Size must not exceed {@link |
| * BluetoothAdapter#getLeMaximumAdvertisingDataLength}. If the advertisement is connectable, |
| * three bytes will be added for flags. |
| * @param scanResponse Scan response associated with the advertisement data. Size must not |
| * exceed {@link BluetoothAdapter#getLeMaximumAdvertisingDataLength} |
| * @param periodicParameters Periodic advertisng parameters. If null, periodic advertising will |
| * not be started. |
| * @param periodicData Periodic advertising data. Size must not exceed {@link |
| * BluetoothAdapter#getLeMaximumAdvertisingDataLength} |
| * @param duration advertising duration, in 10ms unit. Valid range is from 1 (10ms) to 65535 |
| * (655,350 ms). 0 means advertising should continue until stopped. |
| * @param maxExtendedAdvertisingEvents maximum number of extended advertising events the |
| * controller shall attempt to send prior to terminating the extended advertising, even if |
| * the duration has not expired. Valid range is from 1 to 255. 0 means no maximum. |
| * @param callback Callback for advertising set. |
| * @param handler Thread upon which the callbacks will be invoked. |
| * @throws IllegalArgumentException When any of the data parameter exceed the maximum allowable |
| * size, or unsupported advertising PHY is selected, or when attempt to use Periodic |
| * Advertising feature is made when it's not supported by the controller, or when |
| * maxExtendedAdvertisingEvents is used on a controller that doesn't support the LE Extended |
| * Advertising |
| */ |
| @RequiresLegacyBluetoothAdminPermission |
| @RequiresBluetoothAdvertisePermission |
| @RequiresPermission(android.Manifest.permission.BLUETOOTH_ADVERTISE) |
| public void startAdvertisingSet( |
| AdvertisingSetParameters parameters, |
| AdvertiseData advertiseData, |
| AdvertiseData scanResponse, |
| PeriodicAdvertisingParameters periodicParameters, |
| AdvertiseData periodicData, |
| int duration, |
| int maxExtendedAdvertisingEvents, |
| AdvertisingSetCallback callback, |
| Handler handler) { |
| startAdvertisingSet( |
| parameters, |
| advertiseData, |
| scanResponse, |
| periodicParameters, |
| periodicData, |
| duration, |
| maxExtendedAdvertisingEvents, |
| null, |
| callback, |
| handler); |
| } |
| |
| /** |
| * Creates a new advertising set. If operation succeed, device will start advertising. This |
| * method returns immediately, the operation status is delivered through {@code |
| * callback.onAdvertisingSetStarted()}. |
| * |
| * <p>If the {@code gattServer} is provided, connections to this advertisement will only see the |
| * services/characteristics in this server, rather than the union of all GATT services (across |
| * all opened servers). |
| * |
| * @param parameters Advertising set parameters. |
| * @param advertiseData Advertisement data to be broadcasted. Size must not exceed {@link |
| * BluetoothAdapter#getLeMaximumAdvertisingDataLength}. If the advertisement is connectable, |
| * three bytes will be added for flags. |
| * @param scanResponse Scan response associated with the advertisement data. Size must not |
| * exceed {@link BluetoothAdapter#getLeMaximumAdvertisingDataLength} |
| * @param periodicParameters Periodic advertisng parameters. If null, periodic advertising will |
| * not be started. |
| * @param periodicData Periodic advertising data. Size must not exceed {@link |
| * BluetoothAdapter#getLeMaximumAdvertisingDataLength} |
| * @param duration advertising duration, in 10ms unit. Valid range is from 1 (10ms) to 65535 |
| * (655,350 ms). 0 means advertising should continue until stopped. |
| * @param maxExtendedAdvertisingEvents maximum number of extended advertising events the |
| * controller shall attempt to send prior to terminating the extended advertising, even if |
| * the duration has not expired. Valid range is from 1 to 255. 0 means no maximum. |
| * @param gattServer the GATT server that will "own" connections derived from this advertising |
| * set. |
| * @param callback Callback for advertising set. |
| * @param handler Thread upon which the callbacks will be invoked. |
| * @throws IllegalArgumentException When any of the data parameter exceed the maximum allowable |
| * size, or unsupported advertising PHY is selected, or when attempt to use Periodic |
| * Advertising feature is made when it's not supported by the controller, or when |
| * maxExtendedAdvertisingEvents is used on a controller that doesn't support the LE Extended |
| * Advertising |
| * @hide |
| */ |
| @SystemApi |
| @SuppressLint("ExecutorRegistration") |
| @RequiresBluetoothAdvertisePermission |
| @RequiresPermission( |
| allOf = { |
| android.Manifest.permission.BLUETOOTH_PRIVILEGED, |
| android.Manifest.permission.BLUETOOTH_ADVERTISE, |
| android.Manifest.permission.BLUETOOTH_CONNECT, |
| }) |
| public void startAdvertisingSet( |
| @NonNull AdvertisingSetParameters parameters, |
| @Nullable AdvertiseData advertiseData, |
| @Nullable AdvertiseData scanResponse, |
| @Nullable PeriodicAdvertisingParameters periodicParameters, |
| @Nullable AdvertiseData periodicData, |
| int duration, |
| int maxExtendedAdvertisingEvents, |
| @Nullable BluetoothGattServer gattServer, |
| @Nullable AdvertisingSetCallback callback, |
| @SuppressLint("ListenerLast") @NonNull Handler handler) { |
| BluetoothLeUtils.checkAdapterStateOn(mBluetoothAdapter); |
| if (callback == null) { |
| throw new IllegalArgumentException("callback cannot be null"); |
| } |
| |
| boolean isConnectable = parameters.isConnectable(); |
| boolean isDiscoverable = parameters.isDiscoverable(); |
| boolean hasFlags = isConnectable && isDiscoverable; |
| if (parameters.isLegacy()) { |
| if (totalBytes(advertiseData, hasFlags) > MAX_LEGACY_ADVERTISING_DATA_BYTES) { |
| throw new IllegalArgumentException("Legacy advertising data too big"); |
| } |
| |
| if (totalBytes(scanResponse, false) > MAX_LEGACY_ADVERTISING_DATA_BYTES) { |
| throw new IllegalArgumentException("Legacy scan response data too big"); |
| } |
| } else { |
| boolean supportCodedPhy = mBluetoothAdapter.isLeCodedPhySupported(); |
| boolean support2MPhy = mBluetoothAdapter.isLe2MPhySupported(); |
| int pphy = parameters.getPrimaryPhy(); |
| int sphy = parameters.getSecondaryPhy(); |
| if (pphy == BluetoothDevice.PHY_LE_CODED && !supportCodedPhy) { |
| throw new IllegalArgumentException("Unsupported primary PHY selected"); |
| } |
| |
| if ((sphy == BluetoothDevice.PHY_LE_CODED && !supportCodedPhy) |
| || (sphy == BluetoothDevice.PHY_LE_2M && !support2MPhy)) { |
| throw new IllegalArgumentException("Unsupported secondary PHY selected"); |
| } |
| |
| int maxData = mBluetoothAdapter.getLeMaximumAdvertisingDataLength(); |
| if (totalBytes(advertiseData, hasFlags) > maxData) { |
| throw new IllegalArgumentException("Advertising data too big"); |
| } |
| |
| if (totalBytes(scanResponse, false) > maxData) { |
| throw new IllegalArgumentException("Scan response data too big"); |
| } |
| |
| if (totalBytes(periodicData, false) > maxData) { |
| throw new IllegalArgumentException("Periodic advertising data too big"); |
| } |
| |
| boolean supportPeriodic = mBluetoothAdapter.isLePeriodicAdvertisingSupported(); |
| if (periodicParameters != null && !supportPeriodic) { |
| throw new IllegalArgumentException( |
| "Controller does not support LE Periodic Advertising"); |
| } |
| } |
| |
| if (maxExtendedAdvertisingEvents < 0 || maxExtendedAdvertisingEvents > 255) { |
| throw new IllegalArgumentException( |
| "maxExtendedAdvertisingEvents out of range: " + maxExtendedAdvertisingEvents); |
| } |
| |
| if (maxExtendedAdvertisingEvents != 0 |
| && !mBluetoothAdapter.isLePeriodicAdvertisingSupported()) { |
| throw new IllegalArgumentException( |
| "Can't use maxExtendedAdvertisingEvents with controller that don't support " |
| + "LE Extended Advertising"); |
| } |
| |
| if (duration < 0 || duration > 65535) { |
| throw new IllegalArgumentException("duration out of range: " + duration); |
| } |
| |
| IBluetoothGatt gatt = mBluetoothAdapter.getBluetoothGatt(); |
| |
| if (gatt == null) { |
| Log.e(TAG, "Bluetooth GATT is null"); |
| postStartSetFailure( |
| handler, callback, AdvertiseCallback.ADVERTISE_FAILED_INTERNAL_ERROR); |
| return; |
| } |
| |
| IAdvertisingSetCallback wrapped = wrap(callback, handler); |
| if (mCallbackWrappers.putIfAbsent(callback, wrapped) != null) { |
| throw new IllegalArgumentException( |
| "callback instance already associated with advertising"); |
| } |
| |
| try { |
| gatt.startAdvertisingSet( |
| parameters, |
| advertiseData, |
| scanResponse, |
| periodicParameters, |
| periodicData, |
| duration, |
| maxExtendedAdvertisingEvents, |
| gattServer == null ? 0 : gattServer.getServerIf(), |
| wrapped, |
| mAttributionSource); |
| } catch (RemoteException e) { |
| Log.e(TAG, "Failed to start advertising set - ", e); |
| postStartSetFailure( |
| handler, callback, AdvertiseCallback.ADVERTISE_FAILED_INTERNAL_ERROR); |
| return; |
| } catch (SecurityException e) { |
| mCallbackWrappers.remove(callback); |
| throw e; |
| } |
| } |
| |
| /** |
| * Used to dispose of a {@link AdvertisingSet} object, obtained with {@link |
| * BluetoothLeAdvertiser#startAdvertisingSet}. |
| */ |
| @RequiresLegacyBluetoothAdminPermission |
| @RequiresBluetoothAdvertisePermission |
| @RequiresPermission(android.Manifest.permission.BLUETOOTH_ADVERTISE) |
| public void stopAdvertisingSet(AdvertisingSetCallback callback) { |
| if (callback == null) { |
| throw new IllegalArgumentException("callback cannot be null"); |
| } |
| |
| IAdvertisingSetCallback wrapped = mCallbackWrappers.remove(callback); |
| if (wrapped == null) { |
| return; |
| } |
| |
| IBluetoothGatt gatt = mBluetoothAdapter.getBluetoothGatt(); |
| if (gatt == null) { |
| Log.e(TAG, "Bluetooth GATT is null"); |
| return; |
| } |
| try { |
| gatt.stopAdvertisingSet(wrapped, mAttributionSource); |
| } catch (RemoteException e) { |
| Log.e(TAG, "Failed to stop advertising - ", e); |
| } |
| } |
| |
| /** |
| * Cleans up advertisers. Should be called when bluetooth is down. |
| * |
| * @hide |
| */ |
| @RequiresNoPermission |
| public void cleanup() { |
| mLegacyAdvertisers.clear(); |
| mCallbackWrappers.clear(); |
| mAdvertisingSets.clear(); |
| } |
| |
| // Compute the size of advertisement data or scan resp |
| @RequiresBluetoothAdvertisePermission |
| @RequiresPermission(android.Manifest.permission.BLUETOOTH_ADVERTISE) |
| private int totalBytes(AdvertiseData data, boolean isFlagsIncluded) { |
| if (data == null) return 0; |
| // Flags field is omitted if the advertising is not connectable. |
| int size = (isFlagsIncluded) ? FLAGS_FIELD_BYTES : 0; |
| if (data.getServiceUuids() != null) { |
| int num16BitUuids = 0; |
| int num32BitUuids = 0; |
| int num128BitUuids = 0; |
| for (ParcelUuid uuid : data.getServiceUuids()) { |
| if (BluetoothUuid.is16BitUuid(uuid)) { |
| ++num16BitUuids; |
| } else if (BluetoothUuid.is32BitUuid(uuid)) { |
| ++num32BitUuids; |
| } else { |
| ++num128BitUuids; |
| } |
| } |
| // 16 bit service uuids are grouped into one field when doing advertising. |
| if (num16BitUuids != 0) { |
| size += OVERHEAD_BYTES_PER_FIELD + num16BitUuids * BluetoothUuid.UUID_BYTES_16_BIT; |
| } |
| // 32 bit service uuids are grouped into one field when doing advertising. |
| if (num32BitUuids != 0) { |
| size += OVERHEAD_BYTES_PER_FIELD + num32BitUuids * BluetoothUuid.UUID_BYTES_32_BIT; |
| } |
| // 128 bit service uuids are grouped into one field when doing advertising. |
| if (num128BitUuids != 0) { |
| size += |
| OVERHEAD_BYTES_PER_FIELD |
| + num128BitUuids * BluetoothUuid.UUID_BYTES_128_BIT; |
| } |
| } |
| if (data.getServiceSolicitationUuids() != null) { |
| int num16BitUuids = 0; |
| int num32BitUuids = 0; |
| int num128BitUuids = 0; |
| for (ParcelUuid uuid : data.getServiceSolicitationUuids()) { |
| if (BluetoothUuid.is16BitUuid(uuid)) { |
| ++num16BitUuids; |
| } else if (BluetoothUuid.is32BitUuid(uuid)) { |
| ++num32BitUuids; |
| } else { |
| ++num128BitUuids; |
| } |
| } |
| // 16 bit service uuids are grouped into one field when doing advertising. |
| if (num16BitUuids != 0) { |
| size += OVERHEAD_BYTES_PER_FIELD + num16BitUuids * BluetoothUuid.UUID_BYTES_16_BIT; |
| } |
| // 32 bit service uuids are grouped into one field when doing advertising. |
| if (num32BitUuids != 0) { |
| size += OVERHEAD_BYTES_PER_FIELD + num32BitUuids * BluetoothUuid.UUID_BYTES_32_BIT; |
| } |
| // 128 bit service uuids are grouped into one field when doing advertising. |
| if (num128BitUuids != 0) { |
| size += |
| OVERHEAD_BYTES_PER_FIELD |
| + num128BitUuids * BluetoothUuid.UUID_BYTES_128_BIT; |
| } |
| } |
| for (TransportDiscoveryData transportDiscoveryData : data.getTransportDiscoveryData()) { |
| size += OVERHEAD_BYTES_PER_FIELD + transportDiscoveryData.totalBytes(); |
| } |
| for (ParcelUuid uuid : data.getServiceData().keySet()) { |
| int uuidLen = BluetoothUuid.uuidToBytes(uuid).length; |
| size += |
| OVERHEAD_BYTES_PER_FIELD |
| + uuidLen |
| + byteLength(data.getServiceData().get(uuid)); |
| } |
| for (int i = 0; i < data.getManufacturerSpecificData().size(); ++i) { |
| size += |
| OVERHEAD_BYTES_PER_FIELD |
| + MANUFACTURER_SPECIFIC_DATA_LENGTH |
| + byteLength(data.getManufacturerSpecificData().valueAt(i)); |
| } |
| if (data.getIncludeTxPowerLevel()) { |
| size += OVERHEAD_BYTES_PER_FIELD + 1; // tx power level value is one byte. |
| } |
| if (data.getIncludeDeviceName()) { |
| final int length = mBluetoothAdapter.getNameLengthForAdvertise(); |
| if (length >= 0) { |
| size += OVERHEAD_BYTES_PER_FIELD + length; |
| } |
| } |
| return size; |
| } |
| |
| private int byteLength(byte[] array) { |
| return array == null ? 0 : array.length; |
| } |
| |
| @SuppressLint("AndroidFrameworkBluetoothPermission") |
| IAdvertisingSetCallback wrap(AdvertisingSetCallback callback, Handler handler) { |
| return new IAdvertisingSetCallback.Stub() { |
| @Override |
| public void onAdvertisingSetStarted( |
| IBinder gattBinder, int advertiserId, int txPower, int status) { |
| handler.post( |
| () -> { |
| if (status != AdvertisingSetCallback.ADVERTISE_SUCCESS) { |
| callback.onAdvertisingSetStarted(null, 0, status); |
| mCallbackWrappers.remove(callback); |
| return; |
| } |
| |
| AdvertisingSet advertisingSet = |
| new AdvertisingSet( |
| IBluetoothGatt.Stub.asInterface(gattBinder), |
| advertiserId, |
| mBluetoothAdapter, |
| mAttributionSource); |
| mAdvertisingSets.put(advertiserId, advertisingSet); |
| callback.onAdvertisingSetStarted(advertisingSet, txPower, status); |
| }); |
| } |
| |
| @Override |
| public void onOwnAddressRead(int advertiserId, int addressType, String address) { |
| handler.post( |
| () -> { |
| AdvertisingSet advertisingSet = mAdvertisingSets.get(advertiserId); |
| callback.onOwnAddressRead(advertisingSet, addressType, address); |
| }); |
| } |
| |
| @Override |
| public void onAdvertisingSetStopped(int advertiserId) { |
| handler.post( |
| () -> { |
| AdvertisingSet advertisingSet = mAdvertisingSets.get(advertiserId); |
| callback.onAdvertisingSetStopped(advertisingSet); |
| mAdvertisingSets.remove(advertiserId); |
| mCallbackWrappers.remove(callback); |
| }); |
| } |
| |
| @Override |
| public void onAdvertisingEnabled(int advertiserId, boolean enabled, int status) { |
| handler.post( |
| () -> { |
| AdvertisingSet advertisingSet = mAdvertisingSets.get(advertiserId); |
| callback.onAdvertisingEnabled(advertisingSet, enabled, status); |
| }); |
| } |
| |
| @Override |
| public void onAdvertisingDataSet(int advertiserId, int status) { |
| handler.post( |
| () -> { |
| AdvertisingSet advertisingSet = mAdvertisingSets.get(advertiserId); |
| callback.onAdvertisingDataSet(advertisingSet, status); |
| }); |
| } |
| |
| @Override |
| public void onScanResponseDataSet(int advertiserId, int status) { |
| handler.post( |
| () -> { |
| AdvertisingSet advertisingSet = mAdvertisingSets.get(advertiserId); |
| callback.onScanResponseDataSet(advertisingSet, status); |
| }); |
| } |
| |
| @Override |
| public void onAdvertisingParametersUpdated(int advertiserId, int txPower, int status) { |
| handler.post( |
| () -> { |
| AdvertisingSet advertisingSet = mAdvertisingSets.get(advertiserId); |
| callback.onAdvertisingParametersUpdated( |
| advertisingSet, txPower, status); |
| }); |
| } |
| |
| @Override |
| public void onPeriodicAdvertisingParametersUpdated(int advertiserId, int status) { |
| handler.post( |
| () -> { |
| AdvertisingSet advertisingSet = mAdvertisingSets.get(advertiserId); |
| callback.onPeriodicAdvertisingParametersUpdated(advertisingSet, status); |
| }); |
| } |
| |
| @Override |
| public void onPeriodicAdvertisingDataSet(int advertiserId, int status) { |
| handler.post( |
| () -> { |
| AdvertisingSet advertisingSet = mAdvertisingSets.get(advertiserId); |
| callback.onPeriodicAdvertisingDataSet(advertisingSet, status); |
| }); |
| } |
| |
| @Override |
| public void onPeriodicAdvertisingEnabled(int advertiserId, boolean enable, int status) { |
| handler.post( |
| () -> { |
| AdvertisingSet advertisingSet = mAdvertisingSets.get(advertiserId); |
| callback.onPeriodicAdvertisingEnabled(advertisingSet, enable, status); |
| }); |
| } |
| }; |
| } |
| |
| @SuppressLint("AndroidFrameworkBluetoothPermission") |
| private void postStartSetFailure( |
| Handler handler, final AdvertisingSetCallback callback, final int error) { |
| handler.post( |
| new Runnable() { |
| @Override |
| public void run() { |
| callback.onAdvertisingSetStarted(null, 0, error); |
| } |
| }); |
| } |
| |
| @SuppressLint("AndroidFrameworkBluetoothPermission") |
| private void postStartFailure(final AdvertiseCallback callback, final int error) { |
| mHandler.post( |
| new Runnable() { |
| @Override |
| public void run() { |
| callback.onStartFailure(error); |
| } |
| }); |
| } |
| |
| @SuppressLint("AndroidFrameworkBluetoothPermission") |
| private void postStartSuccess( |
| final AdvertiseCallback callback, final AdvertiseSettings settings) { |
| mHandler.post( |
| new Runnable() { |
| |
| @Override |
| public void run() { |
| callback.onStartSuccess(settings); |
| } |
| }); |
| } |
| } |