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        if (DBG) Log.d(TAG, "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        if (DBG) Log.d(TAG, "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
144        Map.Entry<IBinder, AdvertiserInfo> entry = findAdvertiser(reg_id);
145
146        if (entry == null) {
147            Log.i(TAG, "onAdvertisingSetStarted() - no callback found for reg_id " + reg_id);
148            // Advertising set was stopped before it was properly registered.
149            stopAdvertisingSetNative(advertiser_id);
150            return;
151        }
152
153        IAdvertisingSetCallback callback = entry.getValue().callback;
154        if (status == 0) {
155            entry.setValue(
156                    new AdvertiserInfo(advertiser_id, entry.getValue().deathRecipient, callback));
157        } else {
158            IBinder binder = entry.getKey();
159            binder.unlinkToDeath(entry.getValue().deathRecipient, 0);
160            mAdvertisers.remove(binder);
161        }
162
163        callback.onAdvertisingSetStarted(advertiser_id, tx_power, status);
164    }
165
166    void onAdvertisingEnabled(int advertiser_id, boolean enable, int status) throws Exception {
167        if (DBG) {
168            Log.d(TAG, "onAdvertisingSetEnabled() - advertiser_id=" + advertiser_id + ", enable="
169                            + enable + ", status=" + status);
170        }
171
172        Map.Entry<IBinder, AdvertiserInfo> entry = findAdvertiser(advertiser_id);
173        if (entry == null) {
174            Log.i(TAG, "onAdvertisingSetEnable() - no callback found for advertiser_id "
175                            + advertiser_id);
176            return;
177        }
178
179        IAdvertisingSetCallback callback = entry.getValue().callback;
180        callback.onAdvertisingEnabled(advertiser_id, enable, status);
181    }
182
183    void startAdvertisingSet(AdvertisingSetParameters parameters, AdvertiseData advertiseData,
184            AdvertiseData scanResponse, PeriodicAdvertisingParameters periodicParameters,
185            AdvertiseData periodicData, int duration, int maxExtAdvEvents,
186            IAdvertisingSetCallback callback) {
187        AdvertisingSetDeathRecipient deathRecipient = new AdvertisingSetDeathRecipient(callback);
188        IBinder binder = toBinder(callback);
189        try {
190            binder.linkToDeath(deathRecipient, 0);
191        } catch (RemoteException e) {
192            throw new IllegalArgumentException("Can't link to advertiser's death");
193        }
194
195        String deviceName = AdapterService.getAdapterService().getName();
196        byte[] adv_data = AdvertiseHelper.advertiseDataToBytes(advertiseData, deviceName);
197        byte[] scan_response = AdvertiseHelper.advertiseDataToBytes(scanResponse, deviceName);
198        byte[] periodic_data = AdvertiseHelper.advertiseDataToBytes(periodicData, deviceName);
199
200        int cb_id = --sTempRegistrationId;
201        mAdvertisers.put(binder, new AdvertiserInfo(cb_id, deathRecipient, callback));
202
203        if (DBG) Log.d(TAG, "startAdvertisingSet() - reg_id=" + cb_id + ", callback: " + binder);
204        startAdvertisingSetNative(parameters, adv_data, scan_response, periodicParameters,
205                periodic_data, duration, maxExtAdvEvents, cb_id);
206    }
207
208    void onOwnAddressRead(int advertiser_id, int addressType, String address)
209            throws RemoteException {
210        if (DBG) Log.d(TAG, "onOwnAddressRead() advertiser_id=" + advertiser_id);
211
212        Map.Entry<IBinder, AdvertiserInfo> entry = findAdvertiser(advertiser_id);
213        if (entry == null) {
214            Log.i(TAG, "onOwnAddressRead() - bad advertiser_id " + advertiser_id);
215            return;
216        }
217
218        IAdvertisingSetCallback callback = entry.getValue().callback;
219        callback.onOwnAddressRead(advertiser_id, addressType, address);
220    }
221
222    void getOwnAddress(int advertiserId) {
223        getOwnAddressNative(advertiserId);
224    }
225
226    void stopAdvertisingSet(IAdvertisingSetCallback callback) {
227        IBinder binder = toBinder(callback);
228        if (DBG) Log.d(TAG, "stopAdvertisingSet() " + binder);
229
230        AdvertiserInfo adv = mAdvertisers.remove(binder);
231        if (adv == null) {
232            Log.e(TAG, "stopAdvertisingSet() - no client found for callback");
233            return;
234        }
235
236        Integer advertiser_id = adv.id;
237        binder.unlinkToDeath(adv.deathRecipient, 0);
238
239        if (advertiser_id < 0) {
240            Log.i(TAG, "stopAdvertisingSet() - advertiser not finished registration yet");
241            // Advertiser will be freed once initiated in onAdvertisingSetStarted()
242            return;
243        }
244
245        stopAdvertisingSetNative(advertiser_id);
246
247        try {
248            callback.onAdvertisingSetStopped(advertiser_id);
249        } catch (RemoteException e) {
250            Log.i(TAG, "error sending onAdvertisingSetStopped callback", e);
251        }
252    }
253
254    void enableAdvertisingSet(int advertiserId, boolean enable, int duration, int maxExtAdvEvents) {
255        enableAdvertisingSetNative(advertiserId, enable, duration, maxExtAdvEvents);
256    }
257
258    void setAdvertisingData(int advertiserId, AdvertiseData data) {
259        String deviceName = AdapterService.getAdapterService().getName();
260        setAdvertisingDataNative(
261                advertiserId, AdvertiseHelper.advertiseDataToBytes(data, deviceName));
262    }
263
264    void setScanResponseData(int advertiserId, AdvertiseData data) {
265        String deviceName = AdapterService.getAdapterService().getName();
266        setScanResponseDataNative(
267                advertiserId, AdvertiseHelper.advertiseDataToBytes(data, deviceName));
268    }
269
270    void setAdvertisingParameters(int advertiserId, AdvertisingSetParameters parameters) {
271        setAdvertisingParametersNative(advertiserId, parameters);
272    }
273
274    void setPeriodicAdvertisingParameters(
275            int advertiserId, PeriodicAdvertisingParameters parameters) {
276        setPeriodicAdvertisingParametersNative(advertiserId, parameters);
277    }
278
279    void setPeriodicAdvertisingData(int advertiserId, AdvertiseData data) {
280        String deviceName = AdapterService.getAdapterService().getName();
281        setPeriodicAdvertisingDataNative(
282                advertiserId, AdvertiseHelper.advertiseDataToBytes(data, deviceName));
283    }
284
285    void setPeriodicAdvertisingEnable(int advertiserId, boolean enable) {
286        setPeriodicAdvertisingEnableNative(advertiserId, enable);
287    }
288
289    void onAdvertisingDataSet(int advertiser_id, int status) throws Exception {
290        if (DBG) {
291            Log.d(TAG,
292                    "onAdvertisingDataSet() advertiser_id=" + advertiser_id + ", status=" + status);
293        }
294
295        Map.Entry<IBinder, AdvertiserInfo> entry = findAdvertiser(advertiser_id);
296        if (entry == null) {
297            Log.i(TAG, "onAdvertisingDataSet() - bad advertiser_id " + advertiser_id);
298            return;
299        }
300
301        IAdvertisingSetCallback callback = entry.getValue().callback;
302        callback.onAdvertisingDataSet(advertiser_id, status);
303    }
304
305    void onScanResponseDataSet(int advertiser_id, int status) throws Exception {
306        if (DBG)
307            Log.d(TAG, "onScanResponseDataSet() advertiser_id=" + advertiser_id + ", status="
308                            + status);
309
310        Map.Entry<IBinder, AdvertiserInfo> entry = findAdvertiser(advertiser_id);
311        if (entry == null) {
312            Log.i(TAG, "onScanResponseDataSet() - bad advertiser_id " + advertiser_id);
313            return;
314        }
315
316        IAdvertisingSetCallback callback = entry.getValue().callback;
317        callback.onScanResponseDataSet(advertiser_id, status);
318    }
319
320    void onAdvertisingParametersUpdated(int advertiser_id, int tx_power, int status)
321            throws Exception {
322        if (DBG) {
323            Log.d(TAG, "onAdvertisingParametersUpdated() advertiser_id=" + advertiser_id
324                            + ", tx_power=" + tx_power + ", status=" + status);
325        }
326
327        Map.Entry<IBinder, AdvertiserInfo> entry = findAdvertiser(advertiser_id);
328        if (entry == null) {
329            Log.i(TAG, "onAdvertisingParametersUpdated() - bad advertiser_id " + advertiser_id);
330            return;
331        }
332
333        IAdvertisingSetCallback callback = entry.getValue().callback;
334        callback.onAdvertisingParametersUpdated(advertiser_id, tx_power, status);
335    }
336
337    void onPeriodicAdvertisingParametersUpdated(int advertiser_id, int status) throws Exception {
338        if (DBG) {
339            Log.d(TAG, "onPeriodicAdvertisingParametersUpdated() advertiser_id=" + advertiser_id
340                            + ", status=" + status);
341        }
342
343        Map.Entry<IBinder, AdvertiserInfo> entry = findAdvertiser(advertiser_id);
344        if (entry == null) {
345            Log.i(TAG, "onPeriodicAdvertisingParametersUpdated() - bad advertiser_id "
346                            + advertiser_id);
347            return;
348        }
349
350        IAdvertisingSetCallback callback = entry.getValue().callback;
351        callback.onPeriodicAdvertisingParametersUpdated(advertiser_id, status);
352    }
353
354    void onPeriodicAdvertisingDataSet(int advertiser_id, int status) throws Exception {
355        if (DBG) {
356            Log.d(TAG, "onPeriodicAdvertisingDataSet() advertiser_id=" + advertiser_id + ", status="
357                            + status);
358        }
359
360        Map.Entry<IBinder, AdvertiserInfo> entry = findAdvertiser(advertiser_id);
361        if (entry == null) {
362            Log.i(TAG, "onPeriodicAdvertisingDataSet() - bad advertiser_id " + advertiser_id);
363            return;
364        }
365
366        IAdvertisingSetCallback callback = entry.getValue().callback;
367        callback.onPeriodicAdvertisingDataSet(advertiser_id, status);
368    }
369
370    void onPeriodicAdvertisingEnabled(int advertiser_id, boolean enable, int status)
371            throws Exception {
372        if (DBG) {
373            Log.d(TAG, "onPeriodicAdvertisingEnabled() advertiser_id=" + advertiser_id + ", status="
374                            + status);
375        }
376
377        Map.Entry<IBinder, AdvertiserInfo> entry = findAdvertiser(advertiser_id);
378        if (entry == null) {
379            Log.i(TAG, "onAdvertisingSetEnable() - bad advertiser_id " + advertiser_id);
380            return;
381        }
382
383        IAdvertisingSetCallback callback = entry.getValue().callback;
384        callback.onPeriodicAdvertisingEnabled(advertiser_id, enable, status);
385    }
386
387    static {
388        classInitNative();
389    }
390
391    private native static void classInitNative();
392    private native void initializeNative();
393    private native void cleanupNative();
394    private native void startAdvertisingSetNative(AdvertisingSetParameters parameters,
395            byte[] advertiseData, byte[] scanResponse,
396            PeriodicAdvertisingParameters periodicParameters, byte[] periodicData, int duration,
397            int maxExtAdvEvents, int reg_id);
398    private native void getOwnAddressNative(int advertiserId);
399    private native void stopAdvertisingSetNative(int advertiser_id);
400    private native void enableAdvertisingSetNative(
401            int advertiserId, boolean enable, int duration, int maxExtAdvEvents);
402    private native void setAdvertisingDataNative(int advertiserId, byte[] data);
403    private native void setScanResponseDataNative(int advertiserId, byte[] data);
404    private native void setAdvertisingParametersNative(
405            int advertiserId, AdvertisingSetParameters parameters);
406    private native void setPeriodicAdvertisingParametersNative(
407            int advertiserId, PeriodicAdvertisingParameters parameters);
408    private native void setPeriodicAdvertisingDataNative(int advertiserId, byte[] data);
409    private native void setPeriodicAdvertisingEnableNative(int advertiserId, boolean enable);
410}
411