AdvertiseManager.java revision 7f4a31f44f61bf765dad1d3b570e314fa1a98f85
1/*
2 * Copyright (C) 2017 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 com.android.bluetooth.gatt;
18
19import android.bluetooth.le.AdvertiseData;
20import android.bluetooth.le.AdvertisingSetParameters;
21import android.bluetooth.le.IAdvertisingSetCallback;
22import android.bluetooth.le.PeriodicAdvertisingParameters;
23import android.os.Handler;
24import android.os.HandlerThread;
25import android.os.IBinder;
26import android.os.IInterface;
27import android.os.Looper;
28import android.os.Message;
29import android.os.RemoteException;
30import android.util.Log;
31import com.android.bluetooth.Utils;
32import com.android.bluetooth.btservice.AdapterService;
33import java.util.Collections;
34import java.util.HashMap;
35import java.util.HashSet;
36import java.util.Map;
37import java.util.Set;
38import java.util.UUID;
39import java.util.concurrent.CountDownLatch;
40import java.util.concurrent.TimeUnit;
41
42/**
43 * Manages Bluetooth LE advertising operations and interacts with bluedroid stack. TODO: add tests.
44 *
45 * @hide
46 */
47class AdvertiseManager {
48    private static final boolean DBG = GattServiceConfig.DBG;
49    private static final String TAG = GattServiceConfig.TAG_PREFIX + "AdvertiseManager";
50
51    private final GattService mService;
52    private final AdapterService mAdapterService;
53    private Handler mHandler;
54    Map<IBinder, AdvertiserInfo> mAdvertisers = Collections.synchronizedMap(new HashMap<>());
55    static int sTempRegistrationId = -1;
56
57    /**
58     * Constructor of {@link AdvertiseManager}.
59     */
60    AdvertiseManager(GattService service, AdapterService adapterService) {
61        logd("advertise manager created");
62        mService = service;
63        mAdapterService = adapterService;
64    }
65
66    /**
67     * Start a {@link HandlerThread} that handles advertising operations.
68     */
69    void start() {
70        initializeNative();
71        HandlerThread thread = new HandlerThread("BluetoothAdvertiseManager");
72        thread.start();
73        mHandler = new Handler(thread.getLooper());
74    }
75
76    void cleanup() {
77        logd("cleanup()");
78        cleanupNative();
79        mAdvertisers.clear();
80        sTempRegistrationId = -1;
81
82        if (mHandler != null) {
83            // Shut down the thread
84            mHandler.removeCallbacksAndMessages(null);
85            Looper looper = mHandler.getLooper();
86            if (looper != null) {
87                looper.quit();
88            }
89            mHandler = null;
90        }
91    }
92
93    class AdvertiserInfo {
94        /* When id is negative, the registration is ongoing. When the registration finishes, id
95         * becomes equal to advertiser_id */
96        public Integer id;
97        public AdvertisingSetDeathRecipient deathRecipient;
98        public IAdvertisingSetCallback callback;
99
100        AdvertiserInfo(Integer id, AdvertisingSetDeathRecipient deathRecipient,
101                IAdvertisingSetCallback callback) {
102            this.id = id;
103            this.deathRecipient = deathRecipient;
104            this.callback = callback;
105        }
106    }
107
108    IBinder toBinder(IAdvertisingSetCallback e) {
109        return ((IInterface) e).asBinder();
110    }
111
112    class AdvertisingSetDeathRecipient implements IBinder.DeathRecipient {
113        IAdvertisingSetCallback callback;
114
115        public AdvertisingSetDeathRecipient(IAdvertisingSetCallback callback) {
116            this.callback = callback;
117        }
118
119        @Override
120        public void binderDied() {
121            if (DBG) Log.d(TAG, "Binder is dead - unregistering advertising set");
122            stopAdvertisingSet(callback);
123        }
124    }
125
126    Map.Entry<IBinder, AdvertiserInfo> findAdvertiser(int advertiser_id) {
127        Map.Entry<IBinder, AdvertiserInfo> entry = null;
128        for (Map.Entry<IBinder, AdvertiserInfo> e : mAdvertisers.entrySet()) {
129            if (e.getValue().id == advertiser_id) {
130                entry = e;
131                break;
132            }
133        }
134        return entry;
135    }
136
137    void onAdvertisingSetStarted(int reg_id, int advertiser_id, int tx_power, int status)
138            throws Exception {
139        if (DBG)
140            Log.d(TAG, "onAdvertisingSetStarted() - reg_id=" + reg_id + ", advertiser_id="
141                            + advertiser_id + ", status=" + status);
142
143        Map.Entry<IBinder, AdvertiserInfo> entry = findAdvertiser(reg_id);
144
145        if (entry == null) {
146            Log.i(TAG, "onAdvertisingSetStarted() - no callback found for reg_id " + reg_id);
147            // Advertising set was stopped before it was properly registered.
148            stopAdvertisingSetNative(advertiser_id);
149            return;
150        }
151
152        IAdvertisingSetCallback callback = entry.getValue().callback;
153        if (status == 0) {
154            entry.setValue(
155                    new AdvertiserInfo(advertiser_id, entry.getValue().deathRecipient, callback));
156        } else {
157            IBinder binder = entry.getKey();
158            binder.unlinkToDeath(entry.getValue().deathRecipient, 0);
159            mAdvertisers.remove(binder);
160        }
161
162        callback.onAdvertisingSetStarted(advertiser_id, tx_power, status);
163    }
164
165    void onAdvertisingEnabled(int advertiser_id, boolean enable, int status) throws Exception {
166        logd("onAdvertisingSetEnabled() - advertiser_id=" + advertiser_id + ", enable=" + enable
167                + ", status=" + status);
168
169        Map.Entry<IBinder, AdvertiserInfo> entry = findAdvertiser(advertiser_id);
170        if (entry == null) {
171            Log.i(TAG, "onAdvertisingSetEnable() - no callback found for advertiser_id "
172                            + advertiser_id);
173            return;
174        }
175
176        IAdvertisingSetCallback callback = entry.getValue().callback;
177        callback.onAdvertisingEnabled(advertiser_id, enable, status);
178    }
179
180    void startAdvertisingSet(AdvertisingSetParameters parameters, AdvertiseData advertiseData,
181            AdvertiseData scanResponse, PeriodicAdvertisingParameters periodicParameters,
182            AdvertiseData periodicData, int duration, int maxExtAdvEvents,
183            IAdvertisingSetCallback callback) {
184        AdvertisingSetDeathRecipient deathRecipient = new AdvertisingSetDeathRecipient(callback);
185        IBinder binder = toBinder(callback);
186        try {
187            binder.linkToDeath(deathRecipient, 0);
188        } catch (RemoteException e) {
189            throw new IllegalArgumentException("Can't link to advertiser's death");
190        }
191
192        String deviceName = AdapterService.getAdapterService().getName();
193        byte[] adv_data = AdvertiseHelper.advertiseDataToBytes(advertiseData, deviceName);
194        byte[] scan_response = AdvertiseHelper.advertiseDataToBytes(scanResponse, deviceName);
195        byte[] periodic_data = AdvertiseHelper.advertiseDataToBytes(periodicData, deviceName);
196
197        int cb_id = --sTempRegistrationId;
198        mAdvertisers.put(binder, new AdvertiserInfo(cb_id, deathRecipient, callback));
199
200        logd("startAdvertisingSet() - reg_id=" + cb_id + ", callback: " + binder);
201        startAdvertisingSetNative(parameters, adv_data, scan_response, periodicParameters,
202                periodic_data, duration, maxExtAdvEvents, cb_id);
203    }
204
205    void stopAdvertisingSet(IAdvertisingSetCallback callback) {
206        IBinder binder = toBinder(callback);
207        if (DBG) Log.d(TAG, "stopAdvertisingSet() " + binder);
208
209        AdvertiserInfo adv = mAdvertisers.remove(binder);
210        if (adv == null) {
211            Log.e(TAG, "stopAdvertisingSet() - no client found for callback");
212            return;
213        }
214
215        Integer advertiser_id = adv.id;
216        binder.unlinkToDeath(adv.deathRecipient, 0);
217
218        if (advertiser_id < 0) {
219            Log.i(TAG, "stopAdvertisingSet() - advertiser not finished registration yet");
220            // Advertiser will be freed once initiated in onAdvertisingSetStarted()
221            return;
222        }
223
224        stopAdvertisingSetNative(advertiser_id);
225    }
226
227    void enableAdvertisingSet(int advertiserId, boolean enable, int duration, int maxExtAdvEvents) {
228        enableAdvertisingSetNative(advertiserId, enable, duration, maxExtAdvEvents);
229    }
230
231    void setAdvertisingData(int advertiserId, AdvertiseData data) {
232        String deviceName = AdapterService.getAdapterService().getName();
233        setAdvertisingDataNative(
234                advertiserId, AdvertiseHelper.advertiseDataToBytes(data, deviceName));
235    }
236
237    void setScanResponseData(int advertiserId, AdvertiseData data) {
238        String deviceName = AdapterService.getAdapterService().getName();
239        setScanResponseDataNative(
240                advertiserId, AdvertiseHelper.advertiseDataToBytes(data, deviceName));
241    }
242
243    void setAdvertisingParameters(int advertiserId, AdvertisingSetParameters parameters) {
244        setAdvertisingParametersNative(advertiserId, parameters);
245    }
246
247    void setPeriodicAdvertisingParameters(
248            int advertiserId, PeriodicAdvertisingParameters parameters) {
249        setPeriodicAdvertisingParametersNative(advertiserId, parameters);
250    }
251
252    void setPeriodicAdvertisingData(int advertiserId, AdvertiseData data) {
253        String deviceName = AdapterService.getAdapterService().getName();
254        setPeriodicAdvertisingDataNative(
255                advertiserId, AdvertiseHelper.advertiseDataToBytes(data, deviceName));
256    }
257
258    void setPeriodicAdvertisingEnable(int advertiserId, boolean enable) {
259        setPeriodicAdvertisingEnableNative(advertiserId, enable);
260    }
261
262    void onAdvertisingDataSet(int advertiser_id, int status) throws Exception {
263        logd("onAdvertisingDataSet() advertiser_id=" + advertiser_id + ", status=" + status);
264
265        Map.Entry<IBinder, AdvertiserInfo> entry = findAdvertiser(advertiser_id);
266        if (entry == null) {
267            Log.i(TAG, "onAdvertisingDataSet() - bad advertiser_id " + advertiser_id);
268            return;
269        }
270
271        IAdvertisingSetCallback callback = entry.getValue().callback;
272        callback.onAdvertisingDataSet(advertiser_id, status);
273    }
274
275    void onScanResponseDataSet(int advertiser_id, int status) throws Exception {
276        logd("onScanResponseDataSet() advertiser_id=" + advertiser_id + ", status=" + status);
277
278        Map.Entry<IBinder, AdvertiserInfo> entry = findAdvertiser(advertiser_id);
279        if (entry == null) {
280            Log.i(TAG, "onScanResponseDataSet() - bad advertiser_id " + advertiser_id);
281            return;
282        }
283
284        IAdvertisingSetCallback callback = entry.getValue().callback;
285        callback.onScanResponseDataSet(advertiser_id, status);
286    }
287
288    void onAdvertisingParametersUpdated(int advertiser_id, int tx_power, int status)
289            throws Exception {
290        logd("onAdvertisingParametersUpdated() advertiser_id=" + advertiser_id + ", tx_power="
291                + tx_power + ", status=" + status);
292
293        Map.Entry<IBinder, AdvertiserInfo> entry = findAdvertiser(advertiser_id);
294        if (entry == null) {
295            Log.i(TAG, "onAdvertisingParametersUpdated() - bad advertiser_id " + advertiser_id);
296            return;
297        }
298
299        IAdvertisingSetCallback callback = entry.getValue().callback;
300        callback.onAdvertisingParametersUpdated(advertiser_id, tx_power, status);
301    }
302
303    void onPeriodicAdvertisingParametersUpdated(int advertiser_id, int status) throws Exception {
304        logd("onPeriodicAdvertisingParametersUpdated() advertiser_id=" + advertiser_id + ", status="
305                + status);
306
307        Map.Entry<IBinder, AdvertiserInfo> entry = findAdvertiser(advertiser_id);
308        if (entry == null) {
309            Log.i(TAG, "onPeriodicAdvertisingParametersUpdated() - bad advertiser_id "
310                            + advertiser_id);
311            return;
312        }
313
314        IAdvertisingSetCallback callback = entry.getValue().callback;
315        callback.onPeriodicAdvertisingParametersUpdated(advertiser_id, status);
316    }
317
318    void onPeriodicAdvertisingDataSet(int advertiser_id, int status) throws Exception {
319        logd("onPeriodicAdvertisingDataSet() advertiser_id=" + advertiser_id + ", status="
320                + status);
321
322        Map.Entry<IBinder, AdvertiserInfo> entry = findAdvertiser(advertiser_id);
323        if (entry == null) {
324            Log.i(TAG, "onPeriodicAdvertisingDataSet() - bad advertiser_id " + advertiser_id);
325            return;
326        }
327
328        IAdvertisingSetCallback callback = entry.getValue().callback;
329        callback.onPeriodicAdvertisingDataSet(advertiser_id, status);
330    }
331
332    void onPeriodicAdvertisingEnabled(int advertiser_id, boolean enable, int status)
333            throws Exception {
334        logd("onPeriodicAdvertisingEnabled() advertiser_id=" + advertiser_id + ", status="
335                + status);
336
337        Map.Entry<IBinder, AdvertiserInfo> entry = findAdvertiser(advertiser_id);
338        if (entry == null) {
339            Log.i(TAG, "onAdvertisingSetEnable() - bad advertiser_id " + advertiser_id);
340            return;
341        }
342
343        IAdvertisingSetCallback callback = entry.getValue().callback;
344        callback.onPeriodicAdvertisingEnabled(advertiser_id, enable, status);
345    }
346
347    private void logd(String s) {
348        if (DBG) {
349            Log.d(TAG, s);
350        }
351    }
352
353    static {
354        classInitNative();
355    }
356
357    private native static void classInitNative();
358    private native void initializeNative();
359    private native void cleanupNative();
360    private native void startAdvertisingSetNative(AdvertisingSetParameters parameters,
361            byte[] advertiseData, byte[] scanResponse,
362            PeriodicAdvertisingParameters periodicParameters, byte[] periodicData, int duration,
363            int maxExtAdvEvents, int reg_id);
364    private native void stopAdvertisingSetNative(int advertiser_id);
365    private native void enableAdvertisingSetNative(
366            int advertiserId, boolean enable, int duration, int maxExtAdvEvents);
367    private native void setAdvertisingDataNative(int advertiserId, byte[] data);
368    private native void setScanResponseDataNative(int advertiserId, byte[] data);
369    private native void setAdvertisingParametersNative(
370            int advertiserId, AdvertisingSetParameters parameters);
371    private native void setPeriodicAdvertisingParametersNative(
372            int advertiserId, PeriodicAdvertisingParameters parameters);
373    private native void setPeriodicAdvertisingDataNative(int advertiserId, byte[] data);
374    private native void setPeriodicAdvertisingEnableNative(int advertiserId, boolean enable);
375}
376