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