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