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      */
BluetoothLeAdvertiser(IBluetoothManager bluetoothManager)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      */
startAdvertising(AdvertiseSettings settings, AdvertiseData advertiseData, final AdvertiseCallback callback)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      */
startAdvertising(AdvertiseSettings settings, AdvertiseData advertiseData, AdvertiseData scanResponse, final AdvertiseCallback callback)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 
wrapOldCallback(AdvertiseCallback callback, AdvertiseSettings settings)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           throw new IllegalStateException("Failed to get Bluetooth");
419         }
420 
421         IAdvertisingSetCallback wrapped = wrap(callback, handler);
422         if (mCallbackWrappers.putIfAbsent(callback, wrapped) != null) {
423             throw new IllegalArgumentException(
424                 "callback instance already associated with advertising");
425         }
426 
427         try {
428             gatt.startAdvertisingSet(parameters, advertiseData, scanResponse, periodicParameters,
429                                      periodicData, duration, maxExtendedAdvertisingEvents, wrapped);
430         } catch (RemoteException e) {
431           Log.e(TAG, "Failed to start advertising set - ", e);
432           throw new IllegalStateException("Failed to start advertising set");
433         }
434     }
435 
436     /**
437      * Used to dispose of a {@link AdvertisingSet} object, obtained with {@link
438      * BluetoothLeAdvertiser#startAdvertisingSet}.
439      */
440     public void stopAdvertisingSet(AdvertisingSetCallback callback) {
441         if (callback == null) {
442           throw new IllegalArgumentException("callback cannot be null");
443         }
444 
445         IAdvertisingSetCallback wrapped = mCallbackWrappers.remove(callback);
446         if (wrapped == null) {
447             return;
448         }
449 
450         IBluetoothGatt gatt;
451         try {
452             gatt = mBluetoothManager.getBluetoothGatt();
453             gatt.stopAdvertisingSet(wrapped);
454        } catch (RemoteException e) {
455             Log.e(TAG, "Failed to stop advertising - ", e);
456             throw new IllegalStateException("Failed to stop advertising");
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 +
492                         num16BitUuids * BluetoothUuid.UUID_BYTES_16_BIT;
493             }
494             // 32 bit service uuids are grouped into one field when doing advertising.
495             if (num32BitUuids != 0) {
496                 size += OVERHEAD_BYTES_PER_FIELD +
497                         num32BitUuids * BluetoothUuid.UUID_BYTES_32_BIT;
498             }
499             // 128 bit service uuids are grouped into one field when doing advertising.
500             if (num128BitUuids != 0) {
501                 size += OVERHEAD_BYTES_PER_FIELD +
502                         num128BitUuids * BluetoothUuid.UUID_BYTES_128_BIT;
503             }
504         }
505         for (ParcelUuid uuid : data.getServiceData().keySet()) {
506             int uuidLen = BluetoothUuid.uuidToBytes(uuid).length;
507             size += OVERHEAD_BYTES_PER_FIELD + uuidLen
508                     + byteLength(data.getServiceData().get(uuid));
509         }
510         for (int i = 0; i < data.getManufacturerSpecificData().size(); ++i) {
511             size += OVERHEAD_BYTES_PER_FIELD + MANUFACTURER_SPECIFIC_DATA_LENGTH +
512                     byteLength(data.getManufacturerSpecificData().valueAt(i));
513         }
514         if (data.getIncludeTxPowerLevel()) {
515             size += OVERHEAD_BYTES_PER_FIELD + 1; // tx power level value is one byte.
516         }
517         if (data.getIncludeDeviceName() && mBluetoothAdapter.getName() != null) {
518             size += OVERHEAD_BYTES_PER_FIELD + mBluetoothAdapter.getName().length();
519         }
520         return size;
521     }
522 
523     private int byteLength(byte[] array) {
524         return array == null ? 0 : array.length;
525     }
526 
527     IAdvertisingSetCallback wrap(AdvertisingSetCallback callback, Handler handler) {
528         return new IAdvertisingSetCallback.Stub() {
529             @Override
530             public void onAdvertisingSetStarted(int advertiserId, int txPower, int status) {
531                 handler.post(new Runnable() {
532                     @Override
533                     public void run() {
534                         if (status != AdvertisingSetCallback.ADVERTISE_SUCCESS) {
535                             callback.onAdvertisingSetStarted(null, 0, status);
536                             mCallbackWrappers.remove(callback);
537                             return;
538                         }
539 
540                         AdvertisingSet advertisingSet =
541                             new AdvertisingSet(advertiserId, mBluetoothManager);
542                         mAdvertisingSets.put(advertiserId, advertisingSet);
543                         callback.onAdvertisingSetStarted(advertisingSet, txPower, status);
544                     }
545                 });
546             }
547 
548             @Override
549             public void onOwnAddressRead(int advertiserId, int addressType, String address) {
550                 handler.post(new Runnable() {
551                     @Override
552                     public void run() {
553                         AdvertisingSet advertisingSet = mAdvertisingSets.get(advertiserId);
554                         callback.onOwnAddressRead(advertisingSet, addressType, address);
555                     }
556                 });
557             }
558 
559             @Override
560             public void onAdvertisingSetStopped(int advertiserId) {
561                 handler.post(new Runnable() {
562                     @Override
563                     public void run() {
564                         AdvertisingSet advertisingSet = mAdvertisingSets.get(advertiserId);
565                         callback.onAdvertisingSetStopped(advertisingSet);
566                         mAdvertisingSets.remove(advertiserId);
567                         mCallbackWrappers.remove(callback);
568                     }
569                 });
570             }
571 
572             @Override
573             public void onAdvertisingEnabled(int advertiserId, boolean enabled, int status) {
574                 handler.post(new Runnable() {
575                     @Override
576                     public void run() {
577                         AdvertisingSet advertisingSet = mAdvertisingSets.get(advertiserId);
578                         callback.onAdvertisingEnabled(advertisingSet, enabled, status);
579                     }
580                 });
581             }
582 
583             @Override
584             public void onAdvertisingDataSet(int advertiserId, int status) {
585                 handler.post(new Runnable() {
586                     @Override
587                     public void run() {
588                         AdvertisingSet advertisingSet = mAdvertisingSets.get(advertiserId);
589                         callback.onAdvertisingDataSet(advertisingSet, status);
590                     }
591                 });
592             }
593 
594             @Override
595             public void onScanResponseDataSet(int advertiserId, int status) {
596                 handler.post(new Runnable() {
597                     @Override
598                     public void run() {
599                         AdvertisingSet advertisingSet = mAdvertisingSets.get(advertiserId);
600                         callback.onScanResponseDataSet(advertisingSet, status);
601                     }
602                 });
603             }
604 
605             @Override
606             public void onAdvertisingParametersUpdated(int advertiserId, int txPower, int status) {
607                 handler.post(new Runnable() {
608                     @Override
609                     public void run() {
610                         AdvertisingSet advertisingSet = mAdvertisingSets.get(advertiserId);
611                         callback.onAdvertisingParametersUpdated(advertisingSet, txPower, status);
612                     }
613                 });
614             }
615 
616             @Override
617             public void onPeriodicAdvertisingParametersUpdated(int advertiserId, int status) {
618                 handler.post(new Runnable() {
619                     @Override
620                     public void run() {
621                         AdvertisingSet advertisingSet = mAdvertisingSets.get(advertiserId);
622                         callback.onPeriodicAdvertisingParametersUpdated(advertisingSet, status);
623                     }
624                 });
625             }
626 
627             @Override
628             public void onPeriodicAdvertisingDataSet(int advertiserId, int status) {
629                 handler.post(new Runnable() {
630                     @Override
631                     public void run() {
632                         AdvertisingSet advertisingSet = mAdvertisingSets.get(advertiserId);
633                         callback.onPeriodicAdvertisingDataSet(advertisingSet, status);
634                     }
635                 });
636             }
637 
638             @Override
639             public void onPeriodicAdvertisingEnabled(int advertiserId, boolean enable, int status) {
640                 handler.post(new Runnable() {
641                     @Override
642                     public void run() {
643                         AdvertisingSet advertisingSet = mAdvertisingSets.get(advertiserId);
644                         callback.onPeriodicAdvertisingEnabled(advertisingSet, enable, status);
645                     }
646                 });
647             }
648         };
649     }
650 
651     private void postStartFailure(final AdvertiseCallback callback, final int error) {
652         mHandler.post(new Runnable() {
653             @Override
654             public void run() {
655                 callback.onStartFailure(error);
656             }
657         });
658     }
659 
660     private void postStartSuccess(final AdvertiseCallback callback,
661             final AdvertiseSettings settings) {
662         mHandler.post(new Runnable() {
663 
664             @Override
665             public void run() {
666                 callback.onStartSuccess(settings);
667             }
668         });
669     }
670 }
671