BluetoothLeAdvertiser.java revision 8e5270fdf5639461d67e9a898a85520abac6053d
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.IBluetoothGatt;
22import android.bluetooth.IBluetoothGattCallback;
23import android.bluetooth.IBluetoothManager;
24import android.os.Handler;
25import android.os.Looper;
26import android.os.ParcelUuid;
27import android.os.RemoteException;
28import android.util.Log;
29
30import java.util.HashMap;
31import java.util.List;
32import java.util.Map;
33import java.util.UUID;
34
35/**
36 * This class provides a way to perform Bluetooth LE advertise operations, such as start and stop
37 * advertising. An advertiser can broadcast up to 31 bytes of advertisement data represented by
38 * {@link AdvertisementData}.
39 * <p>
40 * To get an instance of {@link BluetoothLeAdvertiser}, call the
41 * {@link BluetoothAdapter#getBluetoothLeAdvertiser()} method.
42 * <p>
43 * Note most of the methods here require {@link android.Manifest.permission#BLUETOOTH_ADMIN}
44 * permission.
45 *
46 * @see AdvertisementData
47 */
48public final class BluetoothLeAdvertiser {
49
50    private static final String TAG = "BluetoothLeAdvertiser";
51
52    private final IBluetoothManager mBluetoothManager;
53    private final Handler mHandler;
54    private BluetoothAdapter mBluetoothAdapter;
55    private final Map<AdvertiseCallback, AdvertiseCallbackWrapper>
56            mLeAdvertisers = new HashMap<AdvertiseCallback, AdvertiseCallbackWrapper>();
57
58    /**
59     * Use BluetoothAdapter.getLeAdvertiser() instead.
60     *
61     * @param bluetoothManager
62     * @hide
63     */
64    public BluetoothLeAdvertiser(IBluetoothManager bluetoothManager) {
65        mBluetoothManager = bluetoothManager;
66        mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
67        mHandler = new Handler(Looper.getMainLooper());
68    }
69
70    /**
71     * Start Bluetooth LE Advertising. The {@code advertiseData} would be broadcasted after the
72     * operation succeeds. Returns immediately, the operation status are delivered through
73     * {@code callback}.
74     * <p>
75     * Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN} permission.
76     *
77     * @param settings Settings for Bluetooth LE advertising.
78     * @param advertiseData Advertisement data to be broadcasted.
79     * @param callback Callback for advertising status.
80     */
81    public void startAdvertising(AdvertiseSettings settings,
82            AdvertisementData advertiseData, final AdvertiseCallback callback) {
83        startAdvertising(settings, advertiseData, null, callback);
84    }
85
86    /**
87     * Start Bluetooth LE Advertising. The {@code advertiseData} would be broadcasted after the
88     * operation succeeds. The {@code scanResponse} would be returned when the scanning device sends
89     * active scan request. Method returns immediately, the operation status are delivered through
90     * {@code callback}.
91     * <p>
92     * Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN}
93     *
94     * @param settings Settings for Bluetooth LE advertising.
95     * @param advertiseData Advertisement data to be advertised in advertisement packet.
96     * @param scanResponse Scan response associated with the advertisement data.
97     * @param callback Callback for advertising status.
98     */
99    public void startAdvertising(AdvertiseSettings settings,
100            AdvertisementData advertiseData, AdvertisementData scanResponse,
101            final AdvertiseCallback callback) {
102        if (callback == null) {
103            throw new IllegalArgumentException("callback cannot be null");
104        }
105        if (mLeAdvertisers.containsKey(callback)) {
106            postCallbackFailure(callback, AdvertiseCallback.ADVERTISE_FAILED_ALREADY_STARTED);
107            return;
108        }
109        IBluetoothGatt gatt;
110        try {
111            gatt = mBluetoothManager.getBluetoothGatt();
112        } catch (RemoteException e) {
113            Log.e(TAG, "failed to get bluetooth gatt - ", e);
114            postCallbackFailure(callback, AdvertiseCallback.ADVERTISE_FAILED_CONTROLLER_FAILURE);
115            return;
116        }
117        if (!mBluetoothAdapter.isMultipleAdvertisementSupported()) {
118            postCallbackFailure(callback, AdvertiseCallback.ADVERTISE_FAILED_FEATURE_UNSUPPORTED);
119            return;
120        }
121        AdvertiseCallbackWrapper wrapper = new AdvertiseCallbackWrapper(callback, advertiseData,
122                scanResponse, settings, gatt);
123        UUID uuid = UUID.randomUUID();
124        try {
125            gatt.registerClient(new ParcelUuid(uuid), wrapper);
126            if (wrapper.advertiseStarted()) {
127                mLeAdvertisers.put(callback, wrapper);
128            }
129        } catch (RemoteException e) {
130            Log.e(TAG, "failed to stop advertising", e);
131        }
132    }
133
134    /**
135     * Stop Bluetooth LE advertising. The {@code callback} must be the same one use in
136     * {@link BluetoothLeAdvertiser#startAdvertising}.
137     * <p>
138     * Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN} permission.
139     *
140     * @param callback {@link AdvertiseCallback} for delivering stopping advertising status.
141     */
142    public void stopAdvertising(final AdvertiseCallback callback) {
143        if (callback == null) {
144            throw new IllegalArgumentException("callback cannot be null");
145        }
146        AdvertiseCallbackWrapper wrapper = mLeAdvertisers.get(callback);
147        if (wrapper == null) {
148            postCallbackFailure(callback, AdvertiseCallback.ADVERTISE_FAILED_NOT_STARTED);
149            return;
150        }
151        try {
152            IBluetoothGatt gatt = mBluetoothManager.getBluetoothGatt();
153            if (gatt == null) {
154                postCallbackFailure(callback,
155                        AdvertiseCallback.ADVERTISE_FAILED_GATT_SERVICE_FAILURE);
156            }
157            gatt.stopMultiAdvertising(wrapper.mLeHandle);
158            if (wrapper.advertiseStopped()) {
159                mLeAdvertisers.remove(callback);
160            }
161        } catch (RemoteException e) {
162            Log.e(TAG, "failed to stop advertising", e);
163            postCallbackFailure(callback,
164                    AdvertiseCallback.ADVERTISE_FAILED_GATT_SERVICE_FAILURE);
165        }
166    }
167
168    /**
169     * Bluetooth GATT interface callbacks for advertising.
170     */
171    private static class AdvertiseCallbackWrapper extends IBluetoothGattCallback.Stub {
172        private static final int LE_CALLBACK_TIMEOUT_MILLIS = 2000;
173        private final AdvertiseCallback mAdvertiseCallback;
174        private final AdvertisementData mAdvertisement;
175        private final AdvertisementData mScanResponse;
176        private final AdvertiseSettings mSettings;
177        private final IBluetoothGatt mBluetoothGatt;
178
179        // mLeHandle 0: not registered
180        // -1: scan stopped
181        // >0: registered and scan started
182        private int mLeHandle;
183        private boolean isAdvertising = false;
184
185        public AdvertiseCallbackWrapper(AdvertiseCallback advertiseCallback,
186                AdvertisementData advertiseData, AdvertisementData scanResponse,
187                AdvertiseSettings settings,
188                IBluetoothGatt bluetoothGatt) {
189            mAdvertiseCallback = advertiseCallback;
190            mAdvertisement = advertiseData;
191            mScanResponse = scanResponse;
192            mSettings = settings;
193            mBluetoothGatt = bluetoothGatt;
194            mLeHandle = 0;
195        }
196
197        public boolean advertiseStarted() {
198            boolean started = false;
199            synchronized (this) {
200                if (mLeHandle == -1) {
201                    return false;
202                }
203                try {
204                    wait(LE_CALLBACK_TIMEOUT_MILLIS);
205                } catch (InterruptedException e) {
206                    Log.e(TAG, "Callback reg wait interrupted: ", e);
207                }
208                started = (mLeHandle > 0 && isAdvertising);
209            }
210            return started;
211        }
212
213        public boolean advertiseStopped() {
214            synchronized (this) {
215                try {
216                    wait(LE_CALLBACK_TIMEOUT_MILLIS);
217                } catch (InterruptedException e) {
218                    Log.e(TAG, "Callback reg wait interrupted: " + e);
219                }
220                return !isAdvertising;
221            }
222        }
223
224        /**
225         * Application interface registered - app is ready to go
226         */
227        @Override
228        public void onClientRegistered(int status, int clientIf) {
229            Log.d(TAG, "onClientRegistered() - status=" + status + " clientIf=" + clientIf);
230            synchronized (this) {
231                if (status == BluetoothGatt.GATT_SUCCESS) {
232                    mLeHandle = clientIf;
233                    try {
234                        mBluetoothGatt.startMultiAdvertising(mLeHandle, mAdvertisement,
235                                mScanResponse, mSettings);
236                    } catch (RemoteException e) {
237                        Log.e(TAG, "fail to start le advertise: " + e);
238                        mLeHandle = -1;
239                        notifyAll();
240                    }
241                } else {
242                    // registration failed
243                    mLeHandle = -1;
244                    notifyAll();
245                }
246            }
247        }
248
249        @Override
250        public void onClientConnectionState(int status, int clientIf,
251                boolean connected, String address) {
252            // no op
253        }
254
255        @Override
256        public void onScanResult(String address, int rssi, byte[] advData) {
257            // no op
258        }
259
260        @Override
261        public void onGetService(String address, int srvcType,
262                int srvcInstId, ParcelUuid srvcUuid) {
263            // no op
264        }
265
266        @Override
267        public void onGetIncludedService(String address, int srvcType,
268                int srvcInstId, ParcelUuid srvcUuid,
269                int inclSrvcType, int inclSrvcInstId,
270                ParcelUuid inclSrvcUuid) {
271            // no op
272        }
273
274        @Override
275        public void onGetCharacteristic(String address, int srvcType,
276                int srvcInstId, ParcelUuid srvcUuid,
277                int charInstId, ParcelUuid charUuid,
278                int charProps) {
279            // no op
280        }
281
282        @Override
283        public void onGetDescriptor(String address, int srvcType,
284                int srvcInstId, ParcelUuid srvcUuid,
285                int charInstId, ParcelUuid charUuid,
286                int descInstId, ParcelUuid descUuid) {
287            // no op
288        }
289
290        @Override
291        public void onSearchComplete(String address, int status) {
292            // no op
293        }
294
295        @Override
296        public void onCharacteristicRead(String address, int status, int srvcType,
297                int srvcInstId, ParcelUuid srvcUuid,
298                int charInstId, ParcelUuid charUuid, byte[] value) {
299            // no op
300        }
301
302        @Override
303        public void onCharacteristicWrite(String address, int status, int srvcType,
304                int srvcInstId, ParcelUuid srvcUuid,
305                int charInstId, ParcelUuid charUuid) {
306            // no op
307        }
308
309        @Override
310        public void onNotify(String address, int srvcType,
311                int srvcInstId, ParcelUuid srvcUuid,
312                int charInstId, ParcelUuid charUuid,
313                byte[] value) {
314            // no op
315        }
316
317        @Override
318        public void onDescriptorRead(String address, int status, int srvcType,
319                int srvcInstId, ParcelUuid srvcUuid,
320                int charInstId, ParcelUuid charUuid,
321                int descInstId, ParcelUuid descrUuid, byte[] value) {
322            // no op
323        }
324
325        @Override
326        public void onDescriptorWrite(String address, int status, int srvcType,
327                int srvcInstId, ParcelUuid srvcUuid,
328                int charInstId, ParcelUuid charUuid,
329                int descInstId, ParcelUuid descrUuid) {
330            // no op
331        }
332
333        @Override
334        public void onExecuteWrite(String address, int status) {
335            // no op
336        }
337
338        @Override
339        public void onReadRemoteRssi(String address, int rssi, int status) {
340            // no op
341        }
342
343        @Override
344        public void onAdvertiseStateChange(int advertiseState, int status) {
345            // no op
346        }
347
348        @Override
349        public void onMultiAdvertiseCallback(int status) {
350            synchronized (this) {
351                if (status == 0) {
352                    isAdvertising = !isAdvertising;
353                    if (!isAdvertising) {
354                        try {
355                            mBluetoothGatt.unregisterClient(mLeHandle);
356                            mLeHandle = -1;
357                        } catch (RemoteException e) {
358                            Log.e(TAG, "remote exception when unregistering", e);
359                        }
360                    }
361                    mAdvertiseCallback.onSuccess(null);
362                } else {
363                    mAdvertiseCallback.onFailure(status);
364                }
365                notifyAll();
366            }
367
368        }
369
370        /**
371         * Callback reporting LE ATT MTU.
372         *
373         * @hide
374         */
375        @Override
376        public void onConfigureMTU(String address, int mtu, int status) {
377            // no op
378        }
379
380        @Override
381        public void onConnectionCongested(String address, boolean congested) {
382            // no op
383        }
384
385        @Override
386        public void onBatchScanResults(List<ScanResult> results) {
387            // no op
388        }
389    }
390
391    private void postCallbackFailure(final AdvertiseCallback callback, final int error) {
392        mHandler.post(new Runnable() {
393                @Override
394            public void run() {
395                callback.onFailure(error);
396            }
397        });
398    }
399}
400