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.os.Handler;
26import android.os.Looper;
27import android.os.ParcelUuid;
28import android.os.RemoteException;
29import android.util.Log;
30
31import java.util.Collections;
32import java.util.HashMap;
33import java.util.Map;
34import 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 */
49public 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