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