blob: 21e9497daa62b38b00a980ab070eb2e6482635dc [file] [log] [blame]
Wei Wang6d811182014-05-22 12:10:25 -07001/*
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;
Jakub Pawlowski9e377192017-04-12 08:51:22 -070020import android.bluetooth.BluetoothDevice;
Wei Wang6d811182014-05-22 12:10:25 -070021import android.bluetooth.BluetoothGatt;
Wei Wang0e81ca22014-07-23 20:33:31 -070022import android.bluetooth.BluetoothUuid;
Wei Wang6d811182014-05-22 12:10:25 -070023import android.bluetooth.IBluetoothGatt;
Wei Wang9fb17912014-07-01 15:10:06 -070024import android.bluetooth.IBluetoothManager;
Jakub Pawlowskia480f7f2016-08-05 06:40:31 -070025import android.bluetooth.le.IAdvertiserCallback;
Wei Wang6d811182014-05-22 12:10:25 -070026import android.os.Handler;
27import android.os.Looper;
28import android.os.ParcelUuid;
29import android.os.RemoteException;
30import android.util.Log;
31
Jakub Pawlowski5f00f172017-03-15 12:34:03 -070032import java.util.Collections;
Wei Wang6d811182014-05-22 12:10:25 -070033import java.util.HashMap;
34import java.util.Map;
35import java.util.UUID;
36
37/**
Wei Wangaf74e662014-07-09 14:03:42 -070038 * This class provides a way to perform Bluetooth LE advertise operations, such as starting and
39 * stopping advertising. An advertiser can broadcast up to 31 bytes of advertisement data
40 * represented by {@link AdvertiseData}.
Wei Wang6d811182014-05-22 12:10:25 -070041 * <p>
42 * To get an instance of {@link BluetoothLeAdvertiser}, call the
43 * {@link BluetoothAdapter#getBluetoothLeAdvertiser()} method.
44 * <p>
Wei Wangaf74e662014-07-09 14:03:42 -070045 * <b>Note:</b> Most of the methods here require {@link android.Manifest.permission#BLUETOOTH_ADMIN}
Wei Wang6d811182014-05-22 12:10:25 -070046 * permission.
47 *
Wei Wangaf74e662014-07-09 14:03:42 -070048 * @see AdvertiseData
Wei Wang6d811182014-05-22 12:10:25 -070049 */
50public final class BluetoothLeAdvertiser {
51
52 private static final String TAG = "BluetoothLeAdvertiser";
53
Jakub Pawlowskif4ed33f2017-03-30 11:19:24 -070054 private static final int MAX_ADVERTISING_DATA_BYTES = 1650;
55 private static final int MAX_LEGACY_ADVERTISING_DATA_BYTES = 31;
Wei Wang0e81ca22014-07-23 20:33:31 -070056 // Each fields need one byte for field length and another byte for field type.
57 private static final int OVERHEAD_BYTES_PER_FIELD = 2;
58 // Flags field will be set by system.
59 private static final int FLAGS_FIELD_BYTES = 3;
60 private static final int MANUFACTURER_SPECIFIC_DATA_LENGTH = 2;
Wei Wang0e81ca22014-07-23 20:33:31 -070061
Wei Wang9fb17912014-07-01 15:10:06 -070062 private final IBluetoothManager mBluetoothManager;
Wei Wang6d811182014-05-22 12:10:25 -070063 private final Handler mHandler;
Prerepa Viswanadham8e5270f2014-07-08 16:33:34 -070064 private BluetoothAdapter mBluetoothAdapter;
Jakub Pawlowski5f00f172017-03-15 12:34:03 -070065 private final Map<AdvertiseCallback, AdvertisingSetCallback>
66 mLegacyAdvertisers = new HashMap<>();
Jakub Pawlowskia9d1a322017-01-10 06:15:54 -080067 private final Map<AdvertisingSetCallback, IAdvertisingSetCallback>
Jakub Pawlowski5f00f172017-03-15 12:34:03 -070068 mCallbackWrappers = Collections.synchronizedMap(new HashMap<>());
69 private final Map<Integer, AdvertisingSet>
70 mAdvertisingSets = Collections.synchronizedMap(new HashMap<>());
Wei Wang6d811182014-05-22 12:10:25 -070071
72 /**
73 * Use BluetoothAdapter.getLeAdvertiser() instead.
Wei Wang685c17582014-07-16 22:02:03 -070074 *
75 * @param bluetoothManager BluetoothManager that conducts overall Bluetooth Management
Wei Wang6d811182014-05-22 12:10:25 -070076 * @hide
77 */
Wei Wang9fb17912014-07-01 15:10:06 -070078 public BluetoothLeAdvertiser(IBluetoothManager bluetoothManager) {
79 mBluetoothManager = bluetoothManager;
Prerepa Viswanadham8e5270f2014-07-08 16:33:34 -070080 mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
Wei Wang6d811182014-05-22 12:10:25 -070081 mHandler = new Handler(Looper.getMainLooper());
82 }
83
84 /**
Wei Wang685c17582014-07-16 22:02:03 -070085 * Start Bluetooth LE Advertising. On success, the {@code advertiseData} will be broadcasted.
86 * Returns immediately, the operation status is delivered through {@code callback}.
Wei Wang6d811182014-05-22 12:10:25 -070087 * <p>
88 * Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN} permission.
89 *
90 * @param settings Settings for Bluetooth LE advertising.
91 * @param advertiseData Advertisement data to be broadcasted.
92 * @param callback Callback for advertising status.
93 */
94 public void startAdvertising(AdvertiseSettings settings,
Wei Wangaf74e662014-07-09 14:03:42 -070095 AdvertiseData advertiseData, final AdvertiseCallback callback) {
Wei Wang6d811182014-05-22 12:10:25 -070096 startAdvertising(settings, advertiseData, null, callback);
97 }
98
99 /**
Wei Wangaf74e662014-07-09 14:03:42 -0700100 * Start Bluetooth LE Advertising. The {@code advertiseData} will be broadcasted if the
Wei Wang685c17582014-07-16 22:02:03 -0700101 * operation succeeds. The {@code scanResponse} is returned when a scanning device sends an
102 * active scan request. This method returns immediately, the operation status is delivered
103 * through {@code callback}.
Wei Wang6d811182014-05-22 12:10:25 -0700104 * <p>
105 * Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN}
106 *
107 * @param settings Settings for Bluetooth LE advertising.
108 * @param advertiseData Advertisement data to be advertised in advertisement packet.
109 * @param scanResponse Scan response associated with the advertisement data.
110 * @param callback Callback for advertising status.
111 */
112 public void startAdvertising(AdvertiseSettings settings,
Wei Wangaf74e662014-07-09 14:03:42 -0700113 AdvertiseData advertiseData, AdvertiseData scanResponse,
Wei Wang6d811182014-05-22 12:10:25 -0700114 final AdvertiseCallback callback) {
Jakub Pawlowski5f00f172017-03-15 12:34:03 -0700115 synchronized (mLegacyAdvertisers) {
Wei Wang833559d2014-08-29 10:26:13 -0700116 BluetoothLeUtils.checkAdapterStateOn(mBluetoothAdapter);
Wei Wang9a974be2014-08-18 21:58:32 -0700117 if (callback == null) {
118 throw new IllegalArgumentException("callback cannot be null");
Wei Wang6d811182014-05-22 12:10:25 -0700119 }
Tom Turney29230ce2014-11-12 16:26:41 -0800120 boolean isConnectable = settings.isConnectable();
Jakub Pawlowskif4ed33f2017-03-30 11:19:24 -0700121 if (totalBytes(advertiseData, isConnectable) > MAX_LEGACY_ADVERTISING_DATA_BYTES ||
122 totalBytes(scanResponse, false) > MAX_LEGACY_ADVERTISING_DATA_BYTES) {
Wei Wang9a974be2014-08-18 21:58:32 -0700123 postStartFailure(callback, AdvertiseCallback.ADVERTISE_FAILED_DATA_TOO_LARGE);
124 return;
125 }
Jakub Pawlowski5f00f172017-03-15 12:34:03 -0700126 if (mLegacyAdvertisers.containsKey(callback)) {
Wei Wang9a974be2014-08-18 21:58:32 -0700127 postStartFailure(callback, AdvertiseCallback.ADVERTISE_FAILED_ALREADY_STARTED);
128 return;
129 }
130
Jakub Pawlowski5f00f172017-03-15 12:34:03 -0700131 AdvertisingSetParameters.Builder parameters = new AdvertisingSetParameters.Builder();
132 parameters.setLegacyMode(true);
133 parameters.setConnectable(isConnectable);
Jakub Pawlowskid12b5682017-03-20 15:57:46 -0700134 parameters.setScannable(true); // legacy advertisements we support are always scannable
Jakub Pawlowski5f00f172017-03-15 12:34:03 -0700135 if (settings.getMode() == AdvertiseSettings.ADVERTISE_MODE_LOW_POWER) {
136 parameters.setInterval(1600); // 1s
137 } else if (settings.getMode() == AdvertiseSettings.ADVERTISE_MODE_BALANCED) {
138 parameters.setInterval(400); // 250ms
139 } else if (settings.getMode() == AdvertiseSettings.ADVERTISE_MODE_LOW_LATENCY) {
140 parameters.setInterval(160); // 100ms
Wei Wang9a974be2014-08-18 21:58:32 -0700141 }
Jakub Pawlowski5f00f172017-03-15 12:34:03 -0700142
143 if (settings.getTxPowerLevel() == AdvertiseSettings.ADVERTISE_TX_POWER_ULTRA_LOW) {
144 parameters.setTxPowerLevel(-21);
145 } else if (settings.getTxPowerLevel() == AdvertiseSettings.ADVERTISE_TX_POWER_LOW) {
146 parameters.setTxPowerLevel(-15);
147 } else if (settings.getTxPowerLevel() == AdvertiseSettings.ADVERTISE_TX_POWER_MEDIUM) {
148 parameters.setTxPowerLevel(-7);
149 } else if (settings.getTxPowerLevel() == AdvertiseSettings.ADVERTISE_TX_POWER_HIGH) {
150 parameters.setTxPowerLevel(1);
151 }
152
Jakub Pawlowski5a355612017-03-30 19:10:08 -0700153 int duration = 0;
154 int timeoutMillis = settings.getTimeout();
155 if (timeoutMillis > 0) {
156 duration = (timeoutMillis < 10) ? 1 : timeoutMillis/10;
157 }
158
Jakub Pawlowski5f00f172017-03-15 12:34:03 -0700159 AdvertisingSetCallback wrapped = wrapOldCallback(callback, settings);
160 mLegacyAdvertisers.put(callback, wrapped);
161 startAdvertisingSet(parameters.build(), advertiseData, scanResponse, null, null,
Jakub Pawlowski5a355612017-03-30 19:10:08 -0700162 duration, 0, wrapped);
Wei Wang6d811182014-05-22 12:10:25 -0700163 }
164 }
165
Jakub Pawlowski5f00f172017-03-15 12:34:03 -0700166 AdvertisingSetCallback wrapOldCallback(AdvertiseCallback callback, AdvertiseSettings settings) {
167 return new AdvertisingSetCallback() {
Jakub Pawlowskibcf671b2017-03-20 17:02:20 -0700168 @Override
169 public void onAdvertisingSetStarted(AdvertisingSet advertisingSet, int txPower,
170 int status) {
Jakub Pawlowski5f00f172017-03-15 12:34:03 -0700171 if (status != AdvertisingSetCallback.ADVERTISE_SUCCESS) {
172 postStartFailure(callback, status);
173 return;
174 }
175
176 postStartSuccess(callback, settings);
177 }
178
179 /* Legacy advertiser is disabled on timeout */
Jakub Pawlowskibcf671b2017-03-20 17:02:20 -0700180 @Override
181 public void onAdvertisingEnabled(AdvertisingSet advertisingSet, boolean enabled,
182 int status) {
Jakub Pawlowski5f00f172017-03-15 12:34:03 -0700183 if (enabled == true) {
184 Log.e(TAG, "Legacy advertiser should be only disabled on timeout," +
185 " but was enabled!");
186 return;
187 }
188
189 stopAdvertising(callback);
190 }
191
192 };
193 }
194
Wei Wang6d811182014-05-22 12:10:25 -0700195 /**
196 * Stop Bluetooth LE advertising. The {@code callback} must be the same one use in
197 * {@link BluetoothLeAdvertiser#startAdvertising}.
198 * <p>
199 * Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN} permission.
200 *
Wei Wangaf74e662014-07-09 14:03:42 -0700201 * @param callback {@link AdvertiseCallback} identifies the advertising instance to stop.
Wei Wang6d811182014-05-22 12:10:25 -0700202 */
203 public void stopAdvertising(final AdvertiseCallback callback) {
Jakub Pawlowski5f00f172017-03-15 12:34:03 -0700204 synchronized (mLegacyAdvertisers) {
Wei Wang9a974be2014-08-18 21:58:32 -0700205 if (callback == null) {
206 throw new IllegalArgumentException("callback cannot be null");
Wei Wang6d811182014-05-22 12:10:25 -0700207 }
Jakub Pawlowski5f00f172017-03-15 12:34:03 -0700208 AdvertisingSetCallback wrapper = mLegacyAdvertisers.get(callback);
Wei Wang9a974be2014-08-18 21:58:32 -0700209 if (wrapper == null) return;
Jakub Pawlowski5f00f172017-03-15 12:34:03 -0700210
211 stopAdvertisingSet(wrapper);
Wei Wang6d811182014-05-22 12:10:25 -0700212 }
213 }
214
Wei Wangee809222014-08-12 22:16:32 -0700215 /**
Jakub Pawlowskif4ed33f2017-03-30 11:19:24 -0700216 * Creates a new advertising set. If operation succeed, device will start advertising. This
217 * method returns immediately, the operation status is delivered through
218 * {@code callback.onAdvertisingSetStarted()}.
219 * <p>
220 * @param parameters advertising set parameters.
221 * @param advertiseData Advertisement data to be broadcasted. Size must not exceed
222 * {@link BluetoothAdapter#getLeMaximumAdvertisingDataLength}. If the
223 * advertisement is connectable, three bytes will be added for flags.
Jakub Pawlowski5a355612017-03-30 19:10:08 -0700224 * @param scanResponse Scan response associated with the advertisement data. Size must not
225 * exceed {@link BluetoothAdapter#getLeMaximumAdvertisingDataLength}.
Jakub Pawlowskif4ed33f2017-03-30 11:19:24 -0700226 * @param periodicParameters periodic advertisng parameters. If null, periodic advertising will
227 * not be started.
228 * @param periodicData Periodic advertising data. Size must not exceed
229 * {@link BluetoothAdapter#getLeMaximumAdvertisingDataLength}.
230 * @param callback Callback for advertising set.
231 * @throws IllegalArgumentException when any of the data parameter exceed the maximum allowable
232 * size, or unsupported advertising PHY is selected, or when attempt to use
233 * Periodic Advertising feature is made when it's not supported by the
234 * controller.
235 */
Jakub Pawlowskia9d1a322017-01-10 06:15:54 -0800236 public void startAdvertisingSet(AdvertisingSetParameters parameters,
237 AdvertiseData advertiseData, AdvertiseData scanResponse,
238 PeriodicAdvertisingParameters periodicParameters,
239 AdvertiseData periodicData, AdvertisingSetCallback callback) {
Jakub Pawlowskiadbf2ee2017-03-17 11:12:15 -0700240 startAdvertisingSet(parameters, advertiseData, scanResponse, periodicParameters,
Jakub Pawlowski5a355612017-03-30 19:10:08 -0700241 periodicData, 0, 0, callback, new Handler(Looper.getMainLooper()));
Jakub Pawlowskia9d1a322017-01-10 06:15:54 -0800242 }
243
244 /**
Jakub Pawlowskif4ed33f2017-03-30 11:19:24 -0700245 * Creates a new advertising set. If operation succeed, device will start advertising. This
246 * method returns immediately, the operation status is delivered through
247 * {@code callback.onAdvertisingSetStarted()}.
248 * <p>
249 * @param parameters advertising set parameters.
250 * @param advertiseData Advertisement data to be broadcasted. Size must not exceed
251 * {@link BluetoothAdapter#getLeMaximumAdvertisingDataLength}. If the
252 * advertisement is connectable, three bytes will be added for flags.
Jakub Pawlowski5a355612017-03-30 19:10:08 -0700253 * @param scanResponse Scan response associated with the advertisement data. Size must not
254 * exceed {@link BluetoothAdapter#getLeMaximumAdvertisingDataLength}.
Jakub Pawlowskif4ed33f2017-03-30 11:19:24 -0700255 * @param periodicParameters periodic advertisng parameters. If null, periodic advertising will
256 * not be started.
257 * @param periodicData Periodic advertising data. Size must not exceed
258 * {@link BluetoothAdapter#getLeMaximumAdvertisingDataLength}.
259 * @param callback Callback for advertising set.
260 * @param handler thread upon which the callbacks will be invoked.
261 * @throws IllegalArgumentException when any of the data parameter exceed the maximum allowable
262 * size, or unsupported advertising PHY is selected, or when attempt to use
263 * Periodic Advertising feature is made when it's not supported by the
264 * controller.
265 */
Jakub Pawlowskia9d1a322017-01-10 06:15:54 -0800266 public void startAdvertisingSet(AdvertisingSetParameters parameters,
267 AdvertiseData advertiseData, AdvertiseData scanResponse,
268 PeriodicAdvertisingParameters periodicParameters,
269 AdvertiseData periodicData, AdvertisingSetCallback callback,
270 Handler handler) {
Jakub Pawlowskiadbf2ee2017-03-17 11:12:15 -0700271 startAdvertisingSet(parameters, advertiseData, scanResponse, periodicParameters,
Jakub Pawlowski5a355612017-03-30 19:10:08 -0700272 periodicData, 0, 0, callback, handler);
Jakub Pawlowskiadbf2ee2017-03-17 11:12:15 -0700273 }
274
275 /**
Jakub Pawlowskif4ed33f2017-03-30 11:19:24 -0700276 * Creates a new advertising set. If operation succeed, device will start advertising. This
277 * method returns immediately, the operation status is delivered through
278 * {@code callback.onAdvertisingSetStarted()}.
279 * <p>
280 * @param parameters advertising set parameters.
281 * @param advertiseData Advertisement data to be broadcasted. Size must not exceed
282 * {@link BluetoothAdapter#getLeMaximumAdvertisingDataLength}. If the
283 * advertisement is connectable, three bytes will be added for flags.
Jakub Pawlowski5a355612017-03-30 19:10:08 -0700284 * @param scanResponse Scan response associated with the advertisement data. Size must not
285 * exceed {@link BluetoothAdapter#getLeMaximumAdvertisingDataLength}.
Jakub Pawlowskif4ed33f2017-03-30 11:19:24 -0700286 * @param periodicParameters periodic advertisng parameters. If null, periodic advertising will
287 * not be started.
288 * @param periodicData Periodic advertising data. Size must not exceed
289 * {@link BluetoothAdapter#getLeMaximumAdvertisingDataLength}.
Jakub Pawlowski5a355612017-03-30 19:10:08 -0700290 * @param duration advertising duration, in 10ms unit. Valid range is from 1 (10ms) to
291 * 65535 (655,350 ms). 0 means advertising should continue until stopped.
292 * @param maxExtendedAdvertisingEvents maximum number of extended advertising events the
293 * controller shall attempt to send prior to terminating the extended
294 * advertising, even if the duration has not expired. Valid range is
295 * from 1 to 255. 0 means no maximum.
Jakub Pawlowskif4ed33f2017-03-30 11:19:24 -0700296 * @param callback Callback for advertising set.
297 * @throws IllegalArgumentException when any of the data parameter exceed the maximum allowable
298 * size, or unsupported advertising PHY is selected, or when attempt to use
299 * Periodic Advertising feature is made when it's not supported by the
300 * controller.
301 */
Jakub Pawlowskiadbf2ee2017-03-17 11:12:15 -0700302 public void startAdvertisingSet(AdvertisingSetParameters parameters,
303 AdvertiseData advertiseData, AdvertiseData scanResponse,
304 PeriodicAdvertisingParameters periodicParameters,
Jakub Pawlowski5a355612017-03-30 19:10:08 -0700305 AdvertiseData periodicData, int duration,
306 int maxExtendedAdvertisingEvents,
Jakub Pawlowskiadbf2ee2017-03-17 11:12:15 -0700307 AdvertisingSetCallback callback) {
308 startAdvertisingSet(parameters, advertiseData, scanResponse, periodicParameters,
Jakub Pawlowski5a355612017-03-30 19:10:08 -0700309 periodicData, duration, maxExtendedAdvertisingEvents, callback,
310 new Handler(Looper.getMainLooper()));
Jakub Pawlowskiadbf2ee2017-03-17 11:12:15 -0700311 }
312
313 /**
Jakub Pawlowskif4ed33f2017-03-30 11:19:24 -0700314 * Creates a new advertising set. If operation succeed, device will start advertising. This
315 * method returns immediately, the operation status is delivered through
316 * {@code callback.onAdvertisingSetStarted()}.
317 * <p>
Jakub Pawlowski5a355612017-03-30 19:10:08 -0700318 * @param parameters Advertising set parameters.
Jakub Pawlowskif4ed33f2017-03-30 11:19:24 -0700319 * @param advertiseData Advertisement data to be broadcasted. Size must not exceed
320 * {@link BluetoothAdapter#getLeMaximumAdvertisingDataLength}. If the
321 * advertisement is connectable, three bytes will be added for flags.
Jakub Pawlowski5a355612017-03-30 19:10:08 -0700322 * @param scanResponse Scan response associated with the advertisement data. Size must not
323 * exceed {@link BluetoothAdapter#getLeMaximumAdvertisingDataLength}
324 * @param periodicParameters Periodic advertisng parameters. If null, periodic advertising will
Jakub Pawlowskif4ed33f2017-03-30 11:19:24 -0700325 * not be started.
326 * @param periodicData Periodic advertising data. Size must not exceed
327 * {@link BluetoothAdapter#getLeMaximumAdvertisingDataLength}
Jakub Pawlowski5a355612017-03-30 19:10:08 -0700328 * @param duration advertising duration, in 10ms unit. Valid range is from 1 (10ms) to
329 * 65535 (655,350 ms). 0 means advertising should continue until stopped.
330 * @param maxExtendedAdvertisingEvents maximum number of extended advertising events the
331 * controller shall attempt to send prior to terminating the extended
332 * advertising, even if the duration has not expired. Valid range is
333 * from 1 to 255. 0 means no maximum.
Jakub Pawlowskif4ed33f2017-03-30 11:19:24 -0700334 * @param callback Callback for advertising set.
Jakub Pawlowski5a355612017-03-30 19:10:08 -0700335 * @param handler Thread upon which the callbacks will be invoked.
336 * @throws IllegalArgumentException When any of the data parameter exceed the maximum allowable
Jakub Pawlowskif4ed33f2017-03-30 11:19:24 -0700337 * size, or unsupported advertising PHY is selected, or when attempt to use
338 * Periodic Advertising feature is made when it's not supported by the
Jakub Pawlowski5a355612017-03-30 19:10:08 -0700339 * controller, or when maxExtendedAdvertisingEvents is used on a controller
340 * that doesn't support the LE Extended Advertising
Jakub Pawlowskif4ed33f2017-03-30 11:19:24 -0700341 */
Jakub Pawlowskiadbf2ee2017-03-17 11:12:15 -0700342 public void startAdvertisingSet(AdvertisingSetParameters parameters,
343 AdvertiseData advertiseData, AdvertiseData scanResponse,
344 PeriodicAdvertisingParameters periodicParameters,
Jakub Pawlowski5a355612017-03-30 19:10:08 -0700345 AdvertiseData periodicData, int duration,
346 int maxExtendedAdvertisingEvents, AdvertisingSetCallback callback,
347 Handler handler) {
Jakub Pawlowskia9d1a322017-01-10 06:15:54 -0800348 BluetoothLeUtils.checkAdapterStateOn(mBluetoothAdapter);
Jakub Pawlowskia9d1a322017-01-10 06:15:54 -0800349 if (callback == null) {
350 throw new IllegalArgumentException("callback cannot be null");
351 }
352
Jakub Pawlowskif4ed33f2017-03-30 11:19:24 -0700353 boolean isConnectable = parameters.isConnectable();
354 if (parameters.isLegacy()) {
355 if (totalBytes(advertiseData, isConnectable) > MAX_LEGACY_ADVERTISING_DATA_BYTES) {
356 throw new IllegalArgumentException("Legacy advertising data too big");
357 }
358
359 if (totalBytes(scanResponse, false) > MAX_LEGACY_ADVERTISING_DATA_BYTES) {
360 throw new IllegalArgumentException("Legacy scan response data too big");
361 }
362 } else {
363 boolean supportCodedPhy = mBluetoothAdapter.isLeCodedPhySupported();
364 boolean support2MPhy = mBluetoothAdapter.isLe2MPhySupported();
365 int pphy = parameters.getPrimaryPhy();
366 int sphy = parameters.getSecondaryPhy();
Jakub Pawlowski9e377192017-04-12 08:51:22 -0700367 if (pphy == BluetoothDevice.PHY_LE_CODED && !supportCodedPhy) {
Jakub Pawlowskif4ed33f2017-03-30 11:19:24 -0700368 throw new IllegalArgumentException("Unsupported primary PHY selected");
369 }
370
Jakub Pawlowski9e377192017-04-12 08:51:22 -0700371 if ((sphy == BluetoothDevice.PHY_LE_CODED && !supportCodedPhy)
372 || (sphy == BluetoothDevice.PHY_LE_2M && !support2MPhy)) {
Jakub Pawlowskif4ed33f2017-03-30 11:19:24 -0700373 throw new IllegalArgumentException("Unsupported secondary PHY selected");
374 }
375
376 int maxData = mBluetoothAdapter.getLeMaximumAdvertisingDataLength();
377 if (totalBytes(advertiseData, isConnectable) > maxData) {
378 throw new IllegalArgumentException("Advertising data too big");
379 }
380
381 if (totalBytes(scanResponse, false) > maxData) {
382 throw new IllegalArgumentException("Scan response data too big");
383 }
384
385 if (totalBytes(periodicData, false) > maxData) {
386 throw new IllegalArgumentException("Periodic advertising data too big");
387 }
388
389 boolean supportPeriodic = mBluetoothAdapter.isLePeriodicAdvertisingSupported();
Jakub Pawlowskie6c453d2017-04-10 13:45:16 -0700390 if (periodicParameters != null && !supportPeriodic) {
Jakub Pawlowskif4ed33f2017-03-30 11:19:24 -0700391 throw new IllegalArgumentException(
392 "Controller does not support LE Periodic Advertising");
393 }
394 }
395
Jakub Pawlowski5a355612017-03-30 19:10:08 -0700396 if (maxExtendedAdvertisingEvents < 0 || maxExtendedAdvertisingEvents > 255) {
397 throw new IllegalArgumentException(
398 "maxExtendedAdvertisingEvents out of range: " + maxExtendedAdvertisingEvents);
399 }
400
401 if (maxExtendedAdvertisingEvents != 0 &&
402 !mBluetoothAdapter.isLePeriodicAdvertisingSupported()) {
403 throw new IllegalArgumentException(
404 "Can't use maxExtendedAdvertisingEvents with controller that don't support " +
405 "LE Extended Advertising");
406 }
407
408 if (duration < 0 || duration > 65535) {
409 throw new IllegalArgumentException("duration out of range: " + duration);
410 }
411
Jakub Pawlowskia9d1a322017-01-10 06:15:54 -0800412 IBluetoothGatt gatt;
413 try {
414 gatt = mBluetoothManager.getBluetoothGatt();
415 } catch (RemoteException e) {
416 Log.e(TAG, "Failed to get Bluetooth gatt - ", e);
417 throw new IllegalStateException("Failed to get Bluetooth");
418 }
419
420 IAdvertisingSetCallback wrapped = wrap(callback, handler);
Jakub Pawlowski5f00f172017-03-15 12:34:03 -0700421 if (mCallbackWrappers.putIfAbsent(callback, wrapped) != null) {
422 throw new IllegalArgumentException(
423 "callback instance already associated with advertising");
424 }
Jakub Pawlowskia9d1a322017-01-10 06:15:54 -0800425
426 try {
427 gatt.startAdvertisingSet(parameters, advertiseData, scanResponse, periodicParameters,
Jakub Pawlowski5a355612017-03-30 19:10:08 -0700428 periodicData, duration, maxExtendedAdvertisingEvents, wrapped);
Jakub Pawlowskia9d1a322017-01-10 06:15:54 -0800429 } catch (RemoteException e) {
430 Log.e(TAG, "Failed to start advertising set - ", e);
431 throw new IllegalStateException("Failed to start advertising set");
432 }
433 }
434
435 /**
436 * Used to dispose of a {@link AdvertisingSet} object, obtained with {@link
437 * BluetoothLeAdvertiser#startAdvertisingSet}.
438 */
439 public void stopAdvertisingSet(AdvertisingSetCallback callback) {
440 if (callback == null) {
441 throw new IllegalArgumentException("callback cannot be null");
442 }
443
Jakub Pawlowski5f00f172017-03-15 12:34:03 -0700444 IAdvertisingSetCallback wrapped = mCallbackWrappers.remove(callback);
Jakub Pawlowskia9d1a322017-01-10 06:15:54 -0800445 if (wrapped == null) {
Jakub Pawlowski5f00f172017-03-15 12:34:03 -0700446 return;
Jakub Pawlowskia9d1a322017-01-10 06:15:54 -0800447 }
448
449 IBluetoothGatt gatt;
450 try {
451 gatt = mBluetoothManager.getBluetoothGatt();
452 gatt.stopAdvertisingSet(wrapped);
453 } catch (RemoteException e) {
454 Log.e(TAG, "Failed to stop advertising - ", e);
455 throw new IllegalStateException("Failed to stop advertising");
456 }
457 }
458
459 /**
Jakub Pawlowskia480f7f2016-08-05 06:40:31 -0700460 * Cleans up advertisers. Should be called when bluetooth is down.
Wei Wangee809222014-08-12 22:16:32 -0700461 *
462 * @hide
463 */
464 public void cleanup() {
Jakub Pawlowski5f00f172017-03-15 12:34:03 -0700465 mLegacyAdvertisers.clear();
466 mCallbackWrappers.clear();
467 mAdvertisingSets.clear();
Wei Wangee809222014-08-12 22:16:32 -0700468 }
469
Prerepa Viswanadhame77adab2015-01-16 10:40:11 -0800470 // Compute the size of advertisement data or scan resp
471 private int totalBytes(AdvertiseData data, boolean isFlagsIncluded) {
Wei Wang9a974be2014-08-18 21:58:32 -0700472 if (data == null) return 0;
Tom Turney29230ce2014-11-12 16:26:41 -0800473 // Flags field is omitted if the advertising is not connectable.
Prerepa Viswanadhame77adab2015-01-16 10:40:11 -0800474 int size = (isFlagsIncluded) ? FLAGS_FIELD_BYTES : 0;
Wei Wang0e81ca22014-07-23 20:33:31 -0700475 if (data.getServiceUuids() != null) {
476 int num16BitUuids = 0;
477 int num32BitUuids = 0;
478 int num128BitUuids = 0;
479 for (ParcelUuid uuid : data.getServiceUuids()) {
480 if (BluetoothUuid.is16BitUuid(uuid)) {
481 ++num16BitUuids;
482 } else if (BluetoothUuid.is32BitUuid(uuid)) {
483 ++num32BitUuids;
484 } else {
485 ++num128BitUuids;
486 }
487 }
488 // 16 bit service uuids are grouped into one field when doing advertising.
489 if (num16BitUuids != 0) {
490 size += OVERHEAD_BYTES_PER_FIELD +
491 num16BitUuids * BluetoothUuid.UUID_BYTES_16_BIT;
492 }
493 // 32 bit service uuids are grouped into one field when doing advertising.
494 if (num32BitUuids != 0) {
495 size += OVERHEAD_BYTES_PER_FIELD +
496 num32BitUuids * BluetoothUuid.UUID_BYTES_32_BIT;
497 }
498 // 128 bit service uuids are grouped into one field when doing advertising.
499 if (num128BitUuids != 0) {
500 size += OVERHEAD_BYTES_PER_FIELD +
501 num128BitUuids * BluetoothUuid.UUID_BYTES_128_BIT;
502 }
503 }
Wei Wang6bf513d2014-08-01 11:12:37 -0700504 for (ParcelUuid uuid : data.getServiceData().keySet()) {
Jakub Pawlowski72e9e9f2017-03-31 16:49:13 -0700505 int uuidLen = BluetoothUuid.uuidToBytes(uuid).length;
506 size += OVERHEAD_BYTES_PER_FIELD + uuidLen
Wei Wang6bf513d2014-08-01 11:12:37 -0700507 + byteLength(data.getServiceData().get(uuid));
Wei Wang0e81ca22014-07-23 20:33:31 -0700508 }
Wei Wang6bf513d2014-08-01 11:12:37 -0700509 for (int i = 0; i < data.getManufacturerSpecificData().size(); ++i) {
Wei Wang0e81ca22014-07-23 20:33:31 -0700510 size += OVERHEAD_BYTES_PER_FIELD + MANUFACTURER_SPECIFIC_DATA_LENGTH +
Wei Wang6bf513d2014-08-01 11:12:37 -0700511 byteLength(data.getManufacturerSpecificData().valueAt(i));
Wei Wang0e81ca22014-07-23 20:33:31 -0700512 }
513 if (data.getIncludeTxPowerLevel()) {
514 size += OVERHEAD_BYTES_PER_FIELD + 1; // tx power level value is one byte.
515 }
516 if (data.getIncludeDeviceName() && mBluetoothAdapter.getName() != null) {
517 size += OVERHEAD_BYTES_PER_FIELD + mBluetoothAdapter.getName().length();
518 }
519 return size;
520 }
521
522 private int byteLength(byte[] array) {
523 return array == null ? 0 : array.length;
524 }
525
Jakub Pawlowskia9d1a322017-01-10 06:15:54 -0800526 IAdvertisingSetCallback wrap(AdvertisingSetCallback callback, Handler handler) {
527 return new IAdvertisingSetCallback.Stub() {
Jakub Pawlowskibcf671b2017-03-20 17:02:20 -0700528 @Override
Jakub Pawlowski6a55da92017-03-17 15:33:27 -0700529 public void onAdvertisingSetStarted(int advertiserId, int txPower, int status) {
Jakub Pawlowskia9d1a322017-01-10 06:15:54 -0800530 handler.post(new Runnable() {
531 @Override
532 public void run() {
533 if (status != AdvertisingSetCallback.ADVERTISE_SUCCESS) {
Jakub Pawlowski6a55da92017-03-17 15:33:27 -0700534 callback.onAdvertisingSetStarted(null, 0, status);
Jakub Pawlowski5f00f172017-03-15 12:34:03 -0700535 mCallbackWrappers.remove(callback);
Jakub Pawlowskia9d1a322017-01-10 06:15:54 -0800536 return;
537 }
538
539 AdvertisingSet advertisingSet =
540 new AdvertisingSet(advertiserId, mBluetoothManager);
Jakub Pawlowski5f00f172017-03-15 12:34:03 -0700541 mAdvertisingSets.put(advertiserId, advertisingSet);
Jakub Pawlowski6a55da92017-03-17 15:33:27 -0700542 callback.onAdvertisingSetStarted(advertisingSet, txPower, status);
Jakub Pawlowskia9d1a322017-01-10 06:15:54 -0800543 }
544 });
545 }
546
Jakub Pawlowskibcf671b2017-03-20 17:02:20 -0700547 @Override
Jakub Pawlowski4bc4a442017-04-19 06:52:08 -0700548 public void onOwnAddressRead(int advertiserId, int addressType, String address) {
549 handler.post(new Runnable() {
550 @Override
551 public void run() {
552 AdvertisingSet advertisingSet = mAdvertisingSets.get(advertiserId);
553 callback.onOwnAddressRead(advertisingSet, addressType, address);
554 }
555 });
556 }
557
558 @Override
Jakub Pawlowskia9d1a322017-01-10 06:15:54 -0800559 public void onAdvertisingSetStopped(int advertiserId) {
560 handler.post(new Runnable() {
561 @Override
562 public void run() {
Jakub Pawlowski5f00f172017-03-15 12:34:03 -0700563 AdvertisingSet advertisingSet = mAdvertisingSets.get(advertiserId);
Jakub Pawlowskia9d1a322017-01-10 06:15:54 -0800564 callback.onAdvertisingSetStopped(advertisingSet);
Jakub Pawlowski5f00f172017-03-15 12:34:03 -0700565 mAdvertisingSets.remove(advertiserId);
566 mCallbackWrappers.remove(callback);
Jakub Pawlowskia9d1a322017-01-10 06:15:54 -0800567 }
568 });
569 }
570
Jakub Pawlowskibcf671b2017-03-20 17:02:20 -0700571 @Override
Jakub Pawlowskia9d1a322017-01-10 06:15:54 -0800572 public void onAdvertisingEnabled(int advertiserId, boolean enabled, int status) {
573 handler.post(new Runnable() {
574 @Override
575 public void run() {
Jakub Pawlowski5f00f172017-03-15 12:34:03 -0700576 AdvertisingSet advertisingSet = mAdvertisingSets.get(advertiserId);
Jakub Pawlowskia9d1a322017-01-10 06:15:54 -0800577 callback.onAdvertisingEnabled(advertisingSet, enabled, status);
578 }
579 });
580 }
581
Jakub Pawlowskibcf671b2017-03-20 17:02:20 -0700582 @Override
Jakub Pawlowskia9d1a322017-01-10 06:15:54 -0800583 public void onAdvertisingDataSet(int advertiserId, int status) {
584 handler.post(new Runnable() {
585 @Override
586 public void run() {
Jakub Pawlowski5f00f172017-03-15 12:34:03 -0700587 AdvertisingSet advertisingSet = mAdvertisingSets.get(advertiserId);
Jakub Pawlowskia9d1a322017-01-10 06:15:54 -0800588 callback.onAdvertisingDataSet(advertisingSet, status);
589 }
590 });
591 }
592
Jakub Pawlowskibcf671b2017-03-20 17:02:20 -0700593 @Override
Jakub Pawlowskia9d1a322017-01-10 06:15:54 -0800594 public void onScanResponseDataSet(int advertiserId, int status) {
595 handler.post(new Runnable() {
596 @Override
597 public void run() {
Jakub Pawlowski5f00f172017-03-15 12:34:03 -0700598 AdvertisingSet advertisingSet = mAdvertisingSets.get(advertiserId);
Jakub Pawlowskia9d1a322017-01-10 06:15:54 -0800599 callback.onScanResponseDataSet(advertisingSet, status);
600 }
601 });
602 }
603
Jakub Pawlowskibcf671b2017-03-20 17:02:20 -0700604 @Override
Jakub Pawlowski6a55da92017-03-17 15:33:27 -0700605 public void onAdvertisingParametersUpdated(int advertiserId, int txPower, int status) {
Jakub Pawlowskia9d1a322017-01-10 06:15:54 -0800606 handler.post(new Runnable() {
607 @Override
608 public void run() {
Jakub Pawlowski5f00f172017-03-15 12:34:03 -0700609 AdvertisingSet advertisingSet = mAdvertisingSets.get(advertiserId);
Jakub Pawlowski6a55da92017-03-17 15:33:27 -0700610 callback.onAdvertisingParametersUpdated(advertisingSet, txPower, status);
Jakub Pawlowskia9d1a322017-01-10 06:15:54 -0800611 }
612 });
613 }
614
Jakub Pawlowskibcf671b2017-03-20 17:02:20 -0700615 @Override
Jakub Pawlowskia9d1a322017-01-10 06:15:54 -0800616 public void onPeriodicAdvertisingParametersUpdated(int advertiserId, int status) {
617 handler.post(new Runnable() {
618 @Override
619 public void run() {
Jakub Pawlowski5f00f172017-03-15 12:34:03 -0700620 AdvertisingSet advertisingSet = mAdvertisingSets.get(advertiserId);
Jakub Pawlowskia9d1a322017-01-10 06:15:54 -0800621 callback.onPeriodicAdvertisingParametersUpdated(advertisingSet, status);
622 }
623 });
624 }
625
Jakub Pawlowskibcf671b2017-03-20 17:02:20 -0700626 @Override
Jakub Pawlowskia9d1a322017-01-10 06:15:54 -0800627 public void onPeriodicAdvertisingDataSet(int advertiserId, int status) {
628 handler.post(new Runnable() {
629 @Override
630 public void run() {
Jakub Pawlowski5f00f172017-03-15 12:34:03 -0700631 AdvertisingSet advertisingSet = mAdvertisingSets.get(advertiserId);
Jakub Pawlowskia9d1a322017-01-10 06:15:54 -0800632 callback.onPeriodicAdvertisingDataSet(advertisingSet, status);
633 }
634 });
635 }
636
Jakub Pawlowskibcf671b2017-03-20 17:02:20 -0700637 @Override
Jakub Pawlowski7998be92017-03-22 15:40:21 -0700638 public void onPeriodicAdvertisingEnabled(int advertiserId, boolean enable, int status) {
Jakub Pawlowskia9d1a322017-01-10 06:15:54 -0800639 handler.post(new Runnable() {
640 @Override
641 public void run() {
Jakub Pawlowski5f00f172017-03-15 12:34:03 -0700642 AdvertisingSet advertisingSet = mAdvertisingSets.get(advertiserId);
Jakub Pawlowski7998be92017-03-22 15:40:21 -0700643 callback.onPeriodicAdvertisingEnabled(advertisingSet, enable, status);
Jakub Pawlowskia9d1a322017-01-10 06:15:54 -0800644 }
645 });
646 }
647 };
648 }
649
Wei Wang9a974be2014-08-18 21:58:32 -0700650 private void postStartFailure(final AdvertiseCallback callback, final int error) {
Wei Wang6d811182014-05-22 12:10:25 -0700651 mHandler.post(new Runnable() {
Wei Wang9a974be2014-08-18 21:58:32 -0700652 @Override
Wei Wang6d811182014-05-22 12:10:25 -0700653 public void run() {
Wei Wangaf74e662014-07-09 14:03:42 -0700654 callback.onStartFailure(error);
Wei Wang6d811182014-05-22 12:10:25 -0700655 }
656 });
657 }
Wei Wang9a974be2014-08-18 21:58:32 -0700658
659 private void postStartSuccess(final AdvertiseCallback callback,
660 final AdvertiseSettings settings) {
661 mHandler.post(new Runnable() {
662
663 @Override
664 public void run() {
665 callback.onStartSuccess(settings);
666 }
667 });
668 }
Wei Wang6d811182014-05-22 12:10:25 -0700669}