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