• 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 android.bluetooth.BluetoothAdapter;
20 import android.bluetooth.BluetoothGatt;
21 import android.bluetooth.BluetoothGattCallbackWrapper;
22 import android.bluetooth.BluetoothUuid;
23 import android.bluetooth.IBluetoothGatt;
24 import android.bluetooth.IBluetoothManager;
25 import android.os.Handler;
26 import android.os.Looper;
27 import android.os.ParcelUuid;
28 import android.os.RemoteException;
29 import android.util.Log;
30 
31 import java.util.HashMap;
32 import java.util.Map;
33 import java.util.UUID;
34 
35 /**
36  * This class provides a way to perform Bluetooth LE advertise operations, such as starting and
37  * stopping advertising. An advertiser can broadcast up to 31 bytes of advertisement data
38  * represented by {@link AdvertiseData}.
39  * <p>
40  * To get an instance of {@link BluetoothLeAdvertiser}, call the
41  * {@link BluetoothAdapter#getBluetoothLeAdvertiser()} method.
42  * <p>
43  * <b>Note:</b> Most of the methods here require {@link android.Manifest.permission#BLUETOOTH_ADMIN}
44  * permission.
45  *
46  * @see AdvertiseData
47  */
48 public final class BluetoothLeAdvertiser {
49 
50     private static final String TAG = "BluetoothLeAdvertiser";
51 
52     private static final int MAX_ADVERTISING_DATA_BYTES = 31;
53     // Each fields need one byte for field length and another byte for field type.
54     private static final int OVERHEAD_BYTES_PER_FIELD = 2;
55     // Flags field will be set by system.
56     private static final int FLAGS_FIELD_BYTES = 3;
57     private static final int MANUFACTURER_SPECIFIC_DATA_LENGTH = 2;
58     private static final int SERVICE_DATA_UUID_LENGTH = 2;
59 
60     private final IBluetoothManager mBluetoothManager;
61     private final Handler mHandler;
62     private BluetoothAdapter mBluetoothAdapter;
63     private final Map<AdvertiseCallback, AdvertiseCallbackWrapper>
64             mLeAdvertisers = new HashMap<AdvertiseCallback, AdvertiseCallbackWrapper>();
65 
66     /**
67      * Use BluetoothAdapter.getLeAdvertiser() instead.
68      *
69      * @param bluetoothManager BluetoothManager that conducts overall Bluetooth Management
70      * @hide
71      */
BluetoothLeAdvertiser(IBluetoothManager bluetoothManager)72     public BluetoothLeAdvertiser(IBluetoothManager bluetoothManager) {
73         mBluetoothManager = bluetoothManager;
74         mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
75         mHandler = new Handler(Looper.getMainLooper());
76     }
77 
78     /**
79      * Start Bluetooth LE Advertising. On success, the {@code advertiseData} will be broadcasted.
80      * Returns immediately, the operation status is delivered through {@code callback}.
81      * <p>
82      * Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN} permission.
83      *
84      * @param settings Settings for Bluetooth LE advertising.
85      * @param advertiseData Advertisement data to be broadcasted.
86      * @param callback Callback for advertising status.
87      */
startAdvertising(AdvertiseSettings settings, AdvertiseData advertiseData, final AdvertiseCallback callback)88     public void startAdvertising(AdvertiseSettings settings,
89             AdvertiseData advertiseData, final AdvertiseCallback callback) {
90         startAdvertising(settings, advertiseData, null, callback);
91     }
92 
93     /**
94      * Start Bluetooth LE Advertising. The {@code advertiseData} will be broadcasted if the
95      * operation succeeds. The {@code scanResponse} is returned when a scanning device sends an
96      * active scan request. This method returns immediately, the operation status is delivered
97      * through {@code callback}.
98      * <p>
99      * Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN}
100      *
101      * @param settings Settings for Bluetooth LE advertising.
102      * @param advertiseData Advertisement data to be advertised in advertisement packet.
103      * @param scanResponse Scan response associated with the advertisement data.
104      * @param callback Callback for advertising status.
105      */
startAdvertising(AdvertiseSettings settings, AdvertiseData advertiseData, AdvertiseData scanResponse, final AdvertiseCallback callback)106     public void startAdvertising(AdvertiseSettings settings,
107             AdvertiseData advertiseData, AdvertiseData scanResponse,
108             final AdvertiseCallback callback) {
109         synchronized (mLeAdvertisers) {
110             BluetoothLeUtils.checkAdapterStateOn(mBluetoothAdapter);
111             if (callback == null) {
112                 throw new IllegalArgumentException("callback cannot be null");
113             }
114             if (!mBluetoothAdapter.isMultipleAdvertisementSupported() &&
115                     !mBluetoothAdapter.isPeripheralModeSupported()) {
116                 postStartFailure(callback,
117                         AdvertiseCallback.ADVERTISE_FAILED_FEATURE_UNSUPPORTED);
118                 return;
119             }
120             boolean isConnectable = settings.isConnectable();
121             if (totalBytes(advertiseData, isConnectable) > MAX_ADVERTISING_DATA_BYTES ||
122                     totalBytes(scanResponse, false) > MAX_ADVERTISING_DATA_BYTES) {
123                 postStartFailure(callback, AdvertiseCallback.ADVERTISE_FAILED_DATA_TOO_LARGE);
124                 return;
125             }
126             if (mLeAdvertisers.containsKey(callback)) {
127                 postStartFailure(callback, AdvertiseCallback.ADVERTISE_FAILED_ALREADY_STARTED);
128                 return;
129             }
130 
131             IBluetoothGatt gatt;
132             try {
133                 gatt = mBluetoothManager.getBluetoothGatt();
134             } catch (RemoteException e) {
135                 Log.e(TAG, "Failed to get Bluetooth gatt - ", e);
136                 postStartFailure(callback, AdvertiseCallback.ADVERTISE_FAILED_INTERNAL_ERROR);
137                 return;
138             }
139             AdvertiseCallbackWrapper wrapper = new AdvertiseCallbackWrapper(callback, advertiseData,
140                     scanResponse, settings, gatt);
141             wrapper.startRegisteration();
142         }
143     }
144 
145     /**
146      * Stop Bluetooth LE advertising. The {@code callback} must be the same one use in
147      * {@link BluetoothLeAdvertiser#startAdvertising}.
148      * <p>
149      * Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN} permission.
150      *
151      * @param callback {@link AdvertiseCallback} identifies the advertising instance to stop.
152      */
stopAdvertising(final AdvertiseCallback callback)153     public void stopAdvertising(final AdvertiseCallback callback) {
154         synchronized (mLeAdvertisers) {
155             if (callback == null) {
156                 throw new IllegalArgumentException("callback cannot be null");
157             }
158             AdvertiseCallbackWrapper wrapper = mLeAdvertisers.get(callback);
159             if (wrapper == null) return;
160             wrapper.stopAdvertising();
161         }
162     }
163 
164     /**
165      * Cleans up advertise clients. Should be called when bluetooth is down.
166      *
167      * @hide
168      */
cleanup()169     public void cleanup() {
170         mLeAdvertisers.clear();
171     }
172 
173     // Compute the size of advertisement data or scan resp
totalBytes(AdvertiseData data, boolean isFlagsIncluded)174     private int totalBytes(AdvertiseData data, boolean isFlagsIncluded) {
175         if (data == null) return 0;
176         // Flags field is omitted if the advertising is not connectable.
177         int size = (isFlagsIncluded) ? FLAGS_FIELD_BYTES : 0;
178         if (data.getServiceUuids() != null) {
179             int num16BitUuids = 0;
180             int num32BitUuids = 0;
181             int num128BitUuids = 0;
182             for (ParcelUuid uuid : data.getServiceUuids()) {
183                 if (BluetoothUuid.is16BitUuid(uuid)) {
184                     ++num16BitUuids;
185                 } else if (BluetoothUuid.is32BitUuid(uuid)) {
186                     ++num32BitUuids;
187                 } else {
188                     ++num128BitUuids;
189                 }
190             }
191             // 16 bit service uuids are grouped into one field when doing advertising.
192             if (num16BitUuids != 0) {
193                 size += OVERHEAD_BYTES_PER_FIELD +
194                         num16BitUuids * BluetoothUuid.UUID_BYTES_16_BIT;
195             }
196             // 32 bit service uuids are grouped into one field when doing advertising.
197             if (num32BitUuids != 0) {
198                 size += OVERHEAD_BYTES_PER_FIELD +
199                         num32BitUuids * BluetoothUuid.UUID_BYTES_32_BIT;
200             }
201             // 128 bit service uuids are grouped into one field when doing advertising.
202             if (num128BitUuids != 0) {
203                 size += OVERHEAD_BYTES_PER_FIELD +
204                         num128BitUuids * BluetoothUuid.UUID_BYTES_128_BIT;
205             }
206         }
207         for (ParcelUuid uuid : data.getServiceData().keySet()) {
208             size += OVERHEAD_BYTES_PER_FIELD + SERVICE_DATA_UUID_LENGTH
209                     + byteLength(data.getServiceData().get(uuid));
210         }
211         for (int i = 0; i < data.getManufacturerSpecificData().size(); ++i) {
212             size += OVERHEAD_BYTES_PER_FIELD + MANUFACTURER_SPECIFIC_DATA_LENGTH +
213                     byteLength(data.getManufacturerSpecificData().valueAt(i));
214         }
215         if (data.getIncludeTxPowerLevel()) {
216             size += OVERHEAD_BYTES_PER_FIELD + 1; // tx power level value is one byte.
217         }
218         if (data.getIncludeDeviceName() && mBluetoothAdapter.getName() != null) {
219             size += OVERHEAD_BYTES_PER_FIELD + mBluetoothAdapter.getName().length();
220         }
221         return size;
222     }
223 
byteLength(byte[] array)224     private int byteLength(byte[] array) {
225         return array == null ? 0 : array.length;
226     }
227 
228     /**
229      * Bluetooth GATT interface callbacks for advertising.
230      */
231     private class AdvertiseCallbackWrapper extends BluetoothGattCallbackWrapper {
232         private static final int LE_CALLBACK_TIMEOUT_MILLIS = 2000;
233         private final AdvertiseCallback mAdvertiseCallback;
234         private final AdvertiseData mAdvertisement;
235         private final AdvertiseData mScanResponse;
236         private final AdvertiseSettings mSettings;
237         private final IBluetoothGatt mBluetoothGatt;
238 
239         // mClientIf 0: not registered
240         // -1: advertise stopped or registration timeout
241         // >0: registered and advertising started
242         private int mClientIf;
243         private boolean mIsAdvertising = false;
244 
AdvertiseCallbackWrapper(AdvertiseCallback advertiseCallback, AdvertiseData advertiseData, AdvertiseData scanResponse, AdvertiseSettings settings, IBluetoothGatt bluetoothGatt)245         public AdvertiseCallbackWrapper(AdvertiseCallback advertiseCallback,
246                 AdvertiseData advertiseData, AdvertiseData scanResponse,
247                 AdvertiseSettings settings,
248                 IBluetoothGatt bluetoothGatt) {
249             mAdvertiseCallback = advertiseCallback;
250             mAdvertisement = advertiseData;
251             mScanResponse = scanResponse;
252             mSettings = settings;
253             mBluetoothGatt = bluetoothGatt;
254             mClientIf = 0;
255         }
256 
startRegisteration()257         public void startRegisteration() {
258             synchronized (this) {
259                 if (mClientIf == -1) return;
260 
261                 try {
262                     UUID uuid = UUID.randomUUID();
263                     mBluetoothGatt.registerClient(new ParcelUuid(uuid), this);
264                     wait(LE_CALLBACK_TIMEOUT_MILLIS);
265                 } catch (InterruptedException | RemoteException e) {
266                     Log.e(TAG, "Failed to start registeration", e);
267                 }
268                 if (mClientIf > 0 && mIsAdvertising) {
269                     mLeAdvertisers.put(mAdvertiseCallback, this);
270                 } else if (mClientIf <= 0) {
271 
272                     // Registration timeout, reset mClientIf to -1 so no subsequent operations can
273                     // proceed.
274                     if (mClientIf == 0) mClientIf = -1;
275                     // Post internal error if registration failed.
276                     postStartFailure(mAdvertiseCallback,
277                             AdvertiseCallback.ADVERTISE_FAILED_INTERNAL_ERROR);
278                 } else {
279                     // Unregister application if it's already registered but advertise failed.
280                     try {
281                         mBluetoothGatt.unregisterClient(mClientIf);
282                         mClientIf = -1;
283                     } catch (RemoteException e) {
284                         Log.e(TAG, "remote exception when unregistering", e);
285                     }
286                 }
287             }
288         }
289 
stopAdvertising()290         public void stopAdvertising() {
291             synchronized (this) {
292                 try {
293                     mBluetoothGatt.stopMultiAdvertising(mClientIf);
294                     wait(LE_CALLBACK_TIMEOUT_MILLIS);
295                 } catch (InterruptedException | RemoteException e) {
296                     Log.e(TAG, "Failed to stop advertising", e);
297                 }
298                 // Advertise callback should have been removed from LeAdvertisers when
299                 // onMultiAdvertiseCallback was called. In case onMultiAdvertiseCallback is never
300                 // invoked and wait timeout expires, remove callback here.
301                 if (mLeAdvertisers.containsKey(mAdvertiseCallback)) {
302                     mLeAdvertisers.remove(mAdvertiseCallback);
303                 }
304             }
305         }
306 
307         /**
308          * Application interface registered - app is ready to go
309          */
310         @Override
onClientRegistered(int status, int clientIf)311         public void onClientRegistered(int status, int clientIf) {
312             Log.d(TAG, "onClientRegistered() - status=" + status + " clientIf=" + clientIf);
313             synchronized (this) {
314                 if (status == BluetoothGatt.GATT_SUCCESS) {
315                     try {
316                         if (mClientIf == -1) {
317                             // Registration succeeds after timeout, unregister client.
318                             mBluetoothGatt.unregisterClient(clientIf);
319                         } else {
320                             mClientIf = clientIf;
321                             mBluetoothGatt.startMultiAdvertising(mClientIf, mAdvertisement,
322                                     mScanResponse, mSettings);
323                         }
324                         return;
325                     } catch (RemoteException e) {
326                         Log.e(TAG, "failed to start advertising", e);
327                     }
328                 }
329                 // Registration failed.
330                 mClientIf = -1;
331                 notifyAll();
332             }
333         }
334 
335         @Override
onMultiAdvertiseCallback(int status, boolean isStart, AdvertiseSettings settings)336         public void onMultiAdvertiseCallback(int status, boolean isStart,
337                 AdvertiseSettings settings) {
338             synchronized (this) {
339                 if (isStart) {
340                     if (status == AdvertiseCallback.ADVERTISE_SUCCESS) {
341                         // Start success
342                         mIsAdvertising = true;
343                         postStartSuccess(mAdvertiseCallback, settings);
344                     } else {
345                         // Start failure.
346                         postStartFailure(mAdvertiseCallback, status);
347                     }
348                 } else {
349                     // unregister client for stop.
350                     try {
351                         mBluetoothGatt.unregisterClient(mClientIf);
352                         mClientIf = -1;
353                         mIsAdvertising = false;
354                         mLeAdvertisers.remove(mAdvertiseCallback);
355                     } catch (RemoteException e) {
356                         Log.e(TAG, "remote exception when unregistering", e);
357                     }
358                 }
359                 notifyAll();
360             }
361 
362         }
363     }
364 
postStartFailure(final AdvertiseCallback callback, final int error)365     private void postStartFailure(final AdvertiseCallback callback, final int error) {
366         mHandler.post(new Runnable() {
367             @Override
368             public void run() {
369                 callback.onStartFailure(error);
370             }
371         });
372     }
373 
postStartSuccess(final AdvertiseCallback callback, final AdvertiseSettings settings)374     private void postStartSuccess(final AdvertiseCallback callback,
375             final AdvertiseSettings settings) {
376         mHandler.post(new Runnable() {
377 
378             @Override
379             public void run() {
380                 callback.onStartSuccess(settings);
381             }
382         });
383     }
384 }
385