BluetoothLeAdvertiser.java revision f6b3d8ca364f3008730741258ceb07c7039a5528
1/*
2 * Copyright (C) 2014 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package android.bluetooth.le;
18
19import android.bluetooth.BluetoothAdapter;
20import android.bluetooth.BluetoothGatt;
21import android.bluetooth.BluetoothUuid;
22import android.bluetooth.IBluetoothGatt;
23import android.bluetooth.IBluetoothGattCallback;
24import android.bluetooth.IBluetoothManager;
25import android.os.Handler;
26import android.os.Looper;
27import android.os.ParcelUuid;
28import android.os.RemoteException;
29import android.util.Log;
30
31import java.util.HashMap;
32import java.util.List;
33import java.util.Map;
34import java.util.UUID;
35
36/**
37 * This class provides a way to perform Bluetooth LE advertise operations, such as starting and
38 * stopping advertising. An advertiser can broadcast up to 31 bytes of advertisement data
39 * represented by {@link AdvertiseData}.
40 * <p>
41 * To get an instance of {@link BluetoothLeAdvertiser}, call the
42 * {@link BluetoothAdapter#getBluetoothLeAdvertiser()} method.
43 * <p>
44 * <b>Note:</b> Most of the methods here require {@link android.Manifest.permission#BLUETOOTH_ADMIN}
45 * permission.
46 *
47 * @see AdvertiseData
48 */
49public final class BluetoothLeAdvertiser {
50
51    private static final String TAG = "BluetoothLeAdvertiser";
52
53    private static final int MAX_ADVERTISING_DATA_BYTES = 31;
54    // Each fields need one byte for field length and another byte for field type.
55    private static final int OVERHEAD_BYTES_PER_FIELD = 2;
56    // Flags field will be set by system.
57    private static final int FLAGS_FIELD_BYTES = 3;
58    private static final int MANUFACTURER_SPECIFIC_DATA_LENGTH = 2;
59    private static final int SERVICE_DATA_UUID_LENGTH = 2;
60
61    private final IBluetoothManager mBluetoothManager;
62    private final Handler mHandler;
63    private BluetoothAdapter mBluetoothAdapter;
64    private final Map<AdvertiseCallback, AdvertiseCallbackWrapper>
65            mLeAdvertisers = new HashMap<AdvertiseCallback, AdvertiseCallbackWrapper>();
66
67    /**
68     * Use BluetoothAdapter.getLeAdvertiser() instead.
69     *
70     * @param bluetoothManager BluetoothManager that conducts overall Bluetooth Management
71     * @hide
72     */
73    public BluetoothLeAdvertiser(IBluetoothManager bluetoothManager) {
74        mBluetoothManager = bluetoothManager;
75        mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
76        mHandler = new Handler(Looper.getMainLooper());
77    }
78
79    /**
80     * Start Bluetooth LE Advertising. On success, the {@code advertiseData} will be broadcasted.
81     * Returns immediately, the operation status is delivered through {@code callback}.
82     * <p>
83     * Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN} permission.
84     *
85     * @param settings Settings for Bluetooth LE advertising.
86     * @param advertiseData Advertisement data to be broadcasted.
87     * @param callback Callback for advertising status.
88     */
89    public void startAdvertising(AdvertiseSettings settings,
90            AdvertiseData advertiseData, final AdvertiseCallback callback) {
91        startAdvertising(settings, advertiseData, null, callback);
92    }
93
94    /**
95     * Start Bluetooth LE Advertising. The {@code advertiseData} will be broadcasted if the
96     * operation succeeds. The {@code scanResponse} is returned when a scanning device sends an
97     * active scan request. This method returns immediately, the operation status is delivered
98     * through {@code callback}.
99     * <p>
100     * Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN}
101     *
102     * @param settings Settings for Bluetooth LE advertising.
103     * @param advertiseData Advertisement data to be advertised in advertisement packet.
104     * @param scanResponse Scan response associated with the advertisement data.
105     * @param callback Callback for advertising status.
106     */
107    public void startAdvertising(AdvertiseSettings settings,
108            AdvertiseData advertiseData, AdvertiseData scanResponse,
109            final AdvertiseCallback callback) {
110        checkAdapterState();
111        if (callback == null) {
112            throw new IllegalArgumentException("callback cannot be null");
113        }
114        if (totalBytes(advertiseData) > MAX_ADVERTISING_DATA_BYTES ||
115                totalBytes(scanResponse) > MAX_ADVERTISING_DATA_BYTES) {
116            postCallbackFailure(callback, AdvertiseCallback.ADVERTISE_FAILED_DATA_TOO_LARGE);
117            return;
118        }
119        if (mLeAdvertisers.containsKey(callback)) {
120            postCallbackFailure(callback, AdvertiseCallback.ADVERTISE_FAILED_ALREADY_STARTED);
121            return;
122        }
123        IBluetoothGatt gatt;
124        try {
125            gatt = mBluetoothManager.getBluetoothGatt();
126        } catch (RemoteException e) {
127            Log.e(TAG, "Failed to get Bluetooth gatt - ", e);
128            postCallbackFailure(callback, AdvertiseCallback.ADVERTISE_FAILED_INTERNAL_ERROR);
129            return;
130        }
131        if (!mBluetoothAdapter.isMultipleAdvertisementSupported()) {
132            postCallbackFailure(callback, AdvertiseCallback.ADVERTISE_FAILED_FEATURE_UNSUPPORTED);
133            return;
134        }
135        AdvertiseCallbackWrapper wrapper = new AdvertiseCallbackWrapper(callback, advertiseData,
136                scanResponse, settings, gatt);
137        UUID uuid = UUID.randomUUID();
138        try {
139            gatt.registerClient(new ParcelUuid(uuid), wrapper);
140            if (wrapper.advertiseStarted()) {
141                mLeAdvertisers.put(callback, wrapper);
142            }
143        } catch (RemoteException e) {
144            Log.e(TAG, "Failed to stop advertising", e);
145        }
146    }
147
148    /**
149     * Stop Bluetooth LE advertising. The {@code callback} must be the same one use in
150     * {@link BluetoothLeAdvertiser#startAdvertising}.
151     * <p>
152     * Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN} permission.
153     *
154     * @param callback {@link AdvertiseCallback} identifies the advertising instance to stop.
155     */
156    public void stopAdvertising(final AdvertiseCallback callback) {
157        checkAdapterState();
158        if (callback == null) {
159            throw new IllegalArgumentException("callback cannot be null");
160        }
161        AdvertiseCallbackWrapper wrapper = mLeAdvertisers.get(callback);
162        if (wrapper == null)
163            return;
164        try {
165            IBluetoothGatt gatt = mBluetoothManager.getBluetoothGatt();
166            if (gatt != null)
167                gatt.stopMultiAdvertising(wrapper.mClientIf);
168
169            if (wrapper.advertiseStopped()) {
170                mLeAdvertisers.remove(callback);
171            }
172        } catch (RemoteException e) {
173            Log.e(TAG, "Failed to stop advertising", e);
174        }
175    }
176
177    // Compute the size of the advertise data.
178    private int totalBytes(AdvertiseData data) {
179        if (data == null) {
180            return 0;
181        }
182        int size = FLAGS_FIELD_BYTES; // flags field is always set.
183        if (data.getServiceUuids() != null) {
184            int num16BitUuids = 0;
185            int num32BitUuids = 0;
186            int num128BitUuids = 0;
187            for (ParcelUuid uuid : data.getServiceUuids()) {
188                if (BluetoothUuid.is16BitUuid(uuid)) {
189                    ++num16BitUuids;
190                } else if (BluetoothUuid.is32BitUuid(uuid)) {
191                    ++num32BitUuids;
192                } else {
193                    ++num128BitUuids;
194                }
195            }
196            // 16 bit service uuids are grouped into one field when doing advertising.
197            if (num16BitUuids != 0) {
198                size += OVERHEAD_BYTES_PER_FIELD +
199                        num16BitUuids * BluetoothUuid.UUID_BYTES_16_BIT;
200            }
201            // 32 bit service uuids are grouped into one field when doing advertising.
202            if (num32BitUuids != 0) {
203                size += OVERHEAD_BYTES_PER_FIELD +
204                        num32BitUuids * BluetoothUuid.UUID_BYTES_32_BIT;
205            }
206            // 128 bit service uuids are grouped into one field when doing advertising.
207            if (num128BitUuids != 0) {
208                size += OVERHEAD_BYTES_PER_FIELD +
209                        num128BitUuids * BluetoothUuid.UUID_BYTES_128_BIT;
210            }
211        }
212        if (data.getServiceDataUuid() != null) {
213            size += OVERHEAD_BYTES_PER_FIELD + SERVICE_DATA_UUID_LENGTH
214                    + byteLength(data.getServiceData());
215        }
216        if (data.getManufacturerId() > 0) {
217            size += OVERHEAD_BYTES_PER_FIELD + MANUFACTURER_SPECIFIC_DATA_LENGTH +
218                    byteLength(data.getManufacturerSpecificData());
219        }
220        if (data.getIncludeTxPowerLevel()) {
221            size += OVERHEAD_BYTES_PER_FIELD + 1; // tx power level value is one byte.
222        }
223        if (data.getIncludeDeviceName() && mBluetoothAdapter.getName() != null) {
224            size += OVERHEAD_BYTES_PER_FIELD + mBluetoothAdapter.getName().length();
225        }
226        return size;
227    }
228
229    private int byteLength(byte[] array) {
230        return array == null ? 0 : array.length;
231    }
232
233    /**
234     * Bluetooth GATT interface callbacks for advertising.
235     */
236    private static class AdvertiseCallbackWrapper extends IBluetoothGattCallback.Stub {
237        private static final int LE_CALLBACK_TIMEOUT_MILLIS = 2000;
238        private final AdvertiseCallback mAdvertiseCallback;
239        private final AdvertiseData mAdvertisement;
240        private final AdvertiseData mScanResponse;
241        private final AdvertiseSettings mSettings;
242        private final IBluetoothGatt mBluetoothGatt;
243
244        // mClientIf 0: not registered
245        // -1: scan stopped
246        // >0: registered and scan started
247        private int mClientIf;
248        private boolean isAdvertising = false;
249
250        public AdvertiseCallbackWrapper(AdvertiseCallback advertiseCallback,
251                AdvertiseData advertiseData, AdvertiseData scanResponse,
252                AdvertiseSettings settings,
253                IBluetoothGatt bluetoothGatt) {
254            mAdvertiseCallback = advertiseCallback;
255            mAdvertisement = advertiseData;
256            mScanResponse = scanResponse;
257            mSettings = settings;
258            mBluetoothGatt = bluetoothGatt;
259            mClientIf = 0;
260        }
261
262        public boolean advertiseStarted() {
263            boolean started = false;
264            synchronized (this) {
265                if (mClientIf == -1) {
266                    return false;
267                }
268                try {
269                    wait(LE_CALLBACK_TIMEOUT_MILLIS);
270                } catch (InterruptedException e) {
271                    Log.e(TAG, "Callback reg wait interrupted: ", e);
272                }
273                started = (mClientIf > 0 && isAdvertising);
274            }
275            return started;
276        }
277
278        public boolean advertiseStopped() {
279            synchronized (this) {
280                try {
281                    wait(LE_CALLBACK_TIMEOUT_MILLIS);
282                } catch (InterruptedException e) {
283                    Log.e(TAG, "Callback reg wait interrupted: " + e);
284                }
285                return !isAdvertising;
286            }
287        }
288
289        /**
290         * Application interface registered - app is ready to go
291         */
292        @Override
293        public void onClientRegistered(int status, int clientIf) {
294            Log.d(TAG, "onClientRegistered() - status=" + status + " clientIf=" + clientIf);
295            synchronized (this) {
296                if (status == BluetoothGatt.GATT_SUCCESS) {
297                    mClientIf = clientIf;
298                    try {
299                        mBluetoothGatt.startMultiAdvertising(mClientIf, mAdvertisement,
300                                mScanResponse, mSettings);
301                    } catch (RemoteException e) {
302                        Log.e(TAG, "fail to start le advertise: " + e);
303                        mClientIf = -1;
304                        notifyAll();
305                    }
306                } else {
307                    // registration failed
308                    mClientIf = -1;
309                    notifyAll();
310                }
311            }
312        }
313
314        @Override
315        public void onClientConnectionState(int status, int clientIf,
316                boolean connected, String address) {
317            // no op
318        }
319
320        @Override
321        public void onScanResult(String address, int rssi, byte[] advData) {
322            // no op
323        }
324
325        @Override
326        public void onGetService(String address, int srvcType,
327                int srvcInstId, ParcelUuid srvcUuid) {
328            // no op
329        }
330
331        @Override
332        public void onGetIncludedService(String address, int srvcType,
333                int srvcInstId, ParcelUuid srvcUuid,
334                int inclSrvcType, int inclSrvcInstId,
335                ParcelUuid inclSrvcUuid) {
336            // no op
337        }
338
339        @Override
340        public void onGetCharacteristic(String address, int srvcType,
341                int srvcInstId, ParcelUuid srvcUuid,
342                int charInstId, ParcelUuid charUuid,
343                int charProps) {
344            // no op
345        }
346
347        @Override
348        public void onGetDescriptor(String address, int srvcType,
349                int srvcInstId, ParcelUuid srvcUuid,
350                int charInstId, ParcelUuid charUuid,
351                int descInstId, ParcelUuid descUuid) {
352            // no op
353        }
354
355        @Override
356        public void onSearchComplete(String address, int status) {
357            // no op
358        }
359
360        @Override
361        public void onCharacteristicRead(String address, int status, int srvcType,
362                int srvcInstId, ParcelUuid srvcUuid,
363                int charInstId, ParcelUuid charUuid, byte[] value) {
364            // no op
365        }
366
367        @Override
368        public void onCharacteristicWrite(String address, int status, int srvcType,
369                int srvcInstId, ParcelUuid srvcUuid,
370                int charInstId, ParcelUuid charUuid) {
371            // no op
372        }
373
374        @Override
375        public void onNotify(String address, int srvcType,
376                int srvcInstId, ParcelUuid srvcUuid,
377                int charInstId, ParcelUuid charUuid,
378                byte[] value) {
379            // no op
380        }
381
382        @Override
383        public void onDescriptorRead(String address, int status, int srvcType,
384                int srvcInstId, ParcelUuid srvcUuid,
385                int charInstId, ParcelUuid charUuid,
386                int descInstId, ParcelUuid descrUuid, byte[] value) {
387            // no op
388        }
389
390        @Override
391        public void onDescriptorWrite(String address, int status, int srvcType,
392                int srvcInstId, ParcelUuid srvcUuid,
393                int charInstId, ParcelUuid charUuid,
394                int descInstId, ParcelUuid descrUuid) {
395            // no op
396        }
397
398        @Override
399        public void onExecuteWrite(String address, int status) {
400            // no op
401        }
402
403        @Override
404        public void onReadRemoteRssi(String address, int rssi, int status) {
405            // no op
406        }
407
408        @Override
409        public void onMultiAdvertiseCallback(int status) {
410            // TODO: This logic needs to be re-visited to account
411            // for whether the scan has actually been started
412            // or not. Toggling the isAdvertising does not seem
413            // correct.
414            synchronized (this) {
415                if (status == AdvertiseCallback.ADVERTISE_SUCCESS) {
416                    isAdvertising = !isAdvertising;
417                    if (!isAdvertising) {
418                        try {
419                            mBluetoothGatt.unregisterClient(mClientIf);
420                            mClientIf = -1;
421                        } catch (RemoteException e) {
422                            Log.e(TAG, "remote exception when unregistering", e);
423                        }
424                    } else {
425                        mAdvertiseCallback.onStartSuccess(null);
426                    }
427                } else {
428                    if (!isAdvertising)
429                        mAdvertiseCallback.onStartFailure(status);
430                }
431                notifyAll();
432            }
433
434        }
435
436        /**
437         * Callback reporting LE ATT MTU.
438         *
439         * @hide
440         */
441        @Override
442        public void onConfigureMTU(String address, int mtu, int status) {
443            // no op
444        }
445
446        @Override
447        public void onConnectionCongested(String address, boolean congested) {
448            // no op
449        }
450
451        @Override
452        public void onBatchScanResults(List<ScanResult> results) {
453            // no op
454        }
455
456        @Override
457        public void onFoundOrLost(boolean onFound, String address, int rssi,
458                byte[] advData) {
459            // no op
460        }
461    }
462
463    //TODO: move this api to a common util class.
464    private void checkAdapterState() {
465        if (mBluetoothAdapter.getState() != mBluetoothAdapter.STATE_ON) {
466            throw new IllegalStateException("BT Adapter is not turned ON");
467        }
468    }
469
470    private void postCallbackFailure(final AdvertiseCallback callback, final int error) {
471        mHandler.post(new Runnable() {
472                @Override
473            public void run() {
474                callback.onStartFailure(error);
475            }
476        });
477    }
478}
479