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