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