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