• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
17 package com.android.bluetooth.gatt;
18 
19 import android.bluetooth.BluetoothAdapter;
20 import android.bluetooth.BluetoothDevice;
21 import android.bluetooth.le.IPeriodicAdvertisingCallback;
22 import android.bluetooth.le.PeriodicAdvertisingReport;
23 import android.bluetooth.le.ScanRecord;
24 import android.bluetooth.le.ScanResult;
25 import android.os.IBinder;
26 import android.os.IInterface;
27 import android.os.RemoteException;
28 import android.util.Log;
29 
30 import com.android.bluetooth.btservice.AdapterService;
31 import com.android.internal.annotations.VisibleForTesting;
32 
33 import java.util.Collections;
34 import java.util.HashMap;
35 import java.util.Map;
36 import java.util.concurrent.ConcurrentHashMap;
37 
38 /**
39  * Manages Bluetooth LE Periodic scans
40  *
41  * @hide
42  */
43 @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
44 public class PeriodicScanManager {
45     private static final boolean DBG = GattServiceConfig.DBG;
46     private static final String TAG = GattServiceConfig.TAG_PREFIX + "SyncManager";
47 
48     private final BluetoothAdapter mAdapter;
49     Map<IBinder, SyncInfo> mSyncs = new ConcurrentHashMap<>();
50     Map<IBinder, SyncTransferInfo> mSyncTransfers = Collections.synchronizedMap(new HashMap<>());
51     static int sTempRegistrationId = -1;
52     private static final int PA_SOURCE_LOCAL = 1;
53     private static final int PA_SOURCE_REMOTE = 2;
54     /**
55      * Constructor of {@link SyncManager}.
56      */
PeriodicScanManager(AdapterService adapterService)57     PeriodicScanManager(AdapterService adapterService) {
58         if (DBG) {
59             Log.d(TAG, "advertise manager created");
60         }
61         mAdapter = BluetoothAdapter.getDefaultAdapter();
62     }
63 
start()64     void start() {
65         initializeNative();
66     }
67 
cleanup()68     void cleanup() {
69         if (DBG) {
70             Log.d(TAG, "cleanup()");
71         }
72         cleanupNative();
73         mSyncs.clear();
74         sTempRegistrationId = -1;
75     }
76 
77     class SyncTransferInfo {
78         public String address;
79         public SyncDeathRecipient deathRecipient;
80         public IPeriodicAdvertisingCallback callback;
81 
SyncTransferInfo(String address, IPeriodicAdvertisingCallback callback)82         SyncTransferInfo(String address, IPeriodicAdvertisingCallback callback) {
83             this.address = address;
84             this.callback = callback;
85         }
86     }
87 
88     class SyncInfo {
89         /* When id is negative, the registration is ongoing. When the registration finishes, id
90          * becomes equal to sync_handle */
91         public Integer id;
92         public Integer advSid;
93         public String address;
94         public Integer skip;
95         public Integer timeout;
96         public SyncDeathRecipient deathRecipient;
97         public IPeriodicAdvertisingCallback callback;
98 
SyncInfo(Integer id, Integer advSid, String address, Integer skip, Integer timeout, SyncDeathRecipient deathRecipient, IPeriodicAdvertisingCallback callback)99         SyncInfo(Integer id, Integer advSid, String address, Integer skip, Integer timeout,
100                 SyncDeathRecipient deathRecipient,
101                 IPeriodicAdvertisingCallback callback) {
102             this.id = id;
103             this.advSid = advSid;
104             this.address = address;
105             this.skip = skip;
106             this.timeout = timeout;
107             this.deathRecipient = deathRecipient;
108             this.callback = callback;
109         }
110     }
111 
findSyncTransfer(String address)112     Map.Entry<IBinder, SyncTransferInfo> findSyncTransfer(String address) {
113         Map.Entry<IBinder, SyncTransferInfo> entry = null;
114         for (Map.Entry<IBinder, SyncTransferInfo> e : mSyncTransfers.entrySet()) {
115             if (e.getValue().address.equals(address)) {
116                 entry = e;
117                 break;
118             }
119         }
120         return entry;
121     }
122 
toBinder(IPeriodicAdvertisingCallback e)123     IBinder toBinder(IPeriodicAdvertisingCallback e) {
124         return ((IInterface) e).asBinder();
125     }
126 
127     class SyncDeathRecipient implements IBinder.DeathRecipient {
128         public IPeriodicAdvertisingCallback callback;
129 
SyncDeathRecipient(IPeriodicAdvertisingCallback callback)130         SyncDeathRecipient(IPeriodicAdvertisingCallback callback) {
131             this.callback = callback;
132         }
133 
134         @Override
binderDied()135         public void binderDied() {
136             if (DBG) {
137                 Log.d(TAG, "Binder is dead - unregistering advertising set");
138             }
139             stopSync(callback);
140         }
141     }
142 
findSync(int syncHandle)143     Map.Entry<IBinder, SyncInfo> findSync(int syncHandle) {
144         Map.Entry<IBinder, SyncInfo> entry = null;
145         for (Map.Entry<IBinder, SyncInfo> e : mSyncs.entrySet()) {
146             if (e.getValue().id == syncHandle) {
147                 entry = e;
148                 break;
149             }
150         }
151         return entry;
152     }
153 
findMatchingSync(int advSid, String address)154     Map.Entry<IBinder, SyncInfo> findMatchingSync(int advSid, String address) {
155         Map.Entry<IBinder, SyncInfo> entry = null;
156         for (Map.Entry<IBinder, SyncInfo> e : mSyncs.entrySet()) {
157             if (e.getValue().advSid == advSid && e.getValue().address.equals(address)) {
158                 return entry = e;
159             }
160         }
161         return entry;
162     }
163 
findAllSync(int syncHandle)164     Map<IBinder, SyncInfo> findAllSync(int syncHandle) {
165         Map<IBinder, SyncInfo> syncMap = new HashMap<IBinder, SyncInfo>();
166         for (Map.Entry<IBinder, SyncInfo> e : mSyncs.entrySet()) {
167             if (e.getValue().id != syncHandle) {
168                 continue;
169             }
170             syncMap.put(e.getKey(), new SyncInfo(e.getValue().id,
171                                                  e.getValue().advSid,
172                                                  e.getValue().address,
173                                                  e.getValue().skip,
174                                                  e.getValue().timeout,
175                                                  e.getValue().deathRecipient,
176                                                  e.getValue().callback));
177         }
178         return syncMap;
179     }
180 
onSyncStarted(int regId, int syncHandle, int sid, int addressType, String address, int phy, int interval, int status)181     void onSyncStarted(int regId, int syncHandle, int sid, int addressType, String address, int phy,
182             int interval, int status) throws Exception {
183         if (DBG) {
184             Log.d(TAG,
185                     "onSyncStarted() - regId=" + regId + ", syncHandle=" + syncHandle + ", status="
186                             + status);
187         }
188         Map<IBinder, SyncInfo> syncMap = findAllSync(regId);
189         if (syncMap.size() == 0) {
190             Log.d(TAG, "onSyncStarted() - no callback found for regId " + regId);
191             stopSyncNative(syncHandle);
192             return;
193         }
194 
195         synchronized (mSyncs) {
196             for (Map.Entry<IBinder, SyncInfo> e : mSyncs.entrySet()) {
197                 if (e.getValue().id != regId) {
198                     continue;
199                 }
200                 IPeriodicAdvertisingCallback callback = e.getValue().callback;
201                 if (status == 0) {
202                     Log.d(TAG, "onSyncStarted: updating id with syncHandle " + syncHandle);
203                     e.setValue(new SyncInfo(syncHandle, sid, address, e.getValue().skip,
204                                             e.getValue().timeout, e.getValue().deathRecipient,
205                                             callback));
206                     callback.onSyncEstablished(syncHandle, mAdapter.getRemoteDevice(address),
207                                                sid, e.getValue().skip, e.getValue().timeout,
208                                                status);
209                 } else {
210                     callback.onSyncEstablished(syncHandle, mAdapter.getRemoteDevice(address),
211                                                sid, e.getValue().skip, e.getValue().timeout,
212                                                status);
213                     IBinder binder = e.getKey();
214                     binder.unlinkToDeath(e.getValue().deathRecipient, 0);
215                     mSyncs.remove(binder);
216                 }
217             }
218         }
219     }
220 
onSyncReport(int syncHandle, int txPower, int rssi, int dataStatus, byte[] data)221     void onSyncReport(int syncHandle, int txPower, int rssi, int dataStatus, byte[] data)
222             throws Exception {
223         if (DBG) {
224             Log.d(TAG, "onSyncReport() - syncHandle=" + syncHandle);
225         }
226 
227         Map<IBinder, SyncInfo> syncMap = findAllSync(syncHandle);
228         if (syncMap.isEmpty()) {
229             Log.i(TAG, "onSyncReport() - no callback found for syncHandle " + syncHandle);
230             return;
231         }
232         for (Map.Entry<IBinder, SyncInfo> e :syncMap.entrySet()) {
233             IPeriodicAdvertisingCallback callback = e.getValue().callback;
234             PeriodicAdvertisingReport report =
235                     new PeriodicAdvertisingReport(syncHandle, txPower, rssi, dataStatus,
236                             ScanRecord.parseFromBytes(data));
237             callback.onPeriodicAdvertisingReport(report);
238         }
239     }
240 
onSyncLost(int syncHandle)241     void onSyncLost(int syncHandle) throws Exception {
242         if (DBG) {
243             Log.d(TAG, "onSyncLost() - syncHandle=" + syncHandle);
244         }
245         Map<IBinder, SyncInfo> syncMap = findAllSync(syncHandle);
246         if (syncMap.isEmpty()) {
247             Log.i(TAG, "onSyncLost() - no callback found for syncHandle " + syncHandle);
248             return;
249         }
250         for (Map.Entry<IBinder, SyncInfo> e :syncMap.entrySet()) {
251             IPeriodicAdvertisingCallback callback = e.getValue().callback;
252             IBinder binder = toBinder(callback);
253             synchronized (mSyncs) {
254                 mSyncs.remove(binder);
255             }
256             callback.onSyncLost(syncHandle);
257 
258         }
259     }
260 
startSync(ScanResult scanResult, int skip, int timeout, IPeriodicAdvertisingCallback callback)261     void startSync(ScanResult scanResult, int skip, int timeout,
262             IPeriodicAdvertisingCallback callback) {
263         SyncDeathRecipient deathRecipient = new SyncDeathRecipient(callback);
264         IBinder binder = toBinder(callback);
265         try {
266             binder.linkToDeath(deathRecipient, 0);
267         } catch (RemoteException e) {
268             throw new IllegalArgumentException("Can't link to periodic scanner death");
269         }
270 
271         String address = scanResult.getDevice().getAddress();
272         int sid = scanResult.getAdvertisingSid();
273         if (DBG) {
274             Log.d(TAG, "startSync for Device: " + address + " sid: " + sid);
275         }
276         synchronized (mSyncs) {
277             Map.Entry<IBinder, SyncInfo> entry = findMatchingSync(sid, address);
278             if (entry != null) {
279                 //Found matching sync. Copy sync handle
280                 if (DBG) {
281                     Log.d(TAG, "startSync: Matching entry found");
282                 }
283                 mSyncs.put(binder, new SyncInfo(entry.getValue().id, sid, address,
284                         entry.getValue().skip, entry.getValue().timeout, deathRecipient,
285                         callback));
286                 if (entry.getValue().id >= 0) {
287                     try {
288                         callback.onSyncEstablished(entry.getValue().id,
289                                                    mAdapter.getRemoteDevice(address),
290                                                    sid, entry.getValue().skip,
291                                                    entry.getValue().timeout, 0 /*success*/);
292                     } catch (RemoteException e) {
293                         throw new IllegalArgumentException("Can't invoke callback");
294                     }
295                 } else {
296                     Log.d(TAG, "startSync(): sync pending for same remote");
297                 }
298                 return;
299             }
300         }
301 
302         int cbId = --sTempRegistrationId;
303         mSyncs.put(binder, new SyncInfo(cbId, sid, address, skip, timeout,
304                 deathRecipient, callback));
305 
306         if (DBG) {
307             Log.d(TAG, "startSync() - reg_id=" + cbId + ", callback: " + binder);
308         }
309         startSyncNative(sid, address, skip, timeout, cbId);
310     }
311 
stopSync(IPeriodicAdvertisingCallback callback)312     void stopSync(IPeriodicAdvertisingCallback callback) {
313         IBinder binder = toBinder(callback);
314         if (DBG) {
315             Log.d(TAG, "stopSync() " + binder);
316         }
317         SyncInfo sync = null;
318         synchronized (mSyncs) {
319             sync = mSyncs.remove(binder);
320         }
321         if (sync == null) {
322             Log.e(TAG, "stopSync() - no client found for callback");
323             return;
324         }
325 
326         Integer syncHandle = sync.id;
327         binder.unlinkToDeath(sync.deathRecipient, 0);
328         Log.d(TAG, "stopSync: " + syncHandle);
329 
330         synchronized (mSyncs) {
331             Map.Entry<IBinder, SyncInfo> entry = findSync(syncHandle);
332             if (entry != null) {
333                 Log.d(TAG, "stopSync() - another app synced to same PA, not stopping sync");
334                 return;
335             }
336         }
337         Log.d(TAG, "calling stopSyncNative: " + syncHandle.intValue());
338         if (syncHandle < 0) {
339             Log.i(TAG, "cancelSync() - sync not established yet");
340             cancelSyncNative(sync.advSid, sync.address);
341         } else {
342             stopSyncNative(syncHandle.intValue());
343         }
344     }
345 
onSyncTransferredCallback(int paSource, int status, String bda)346     void onSyncTransferredCallback(int paSource, int status, String bda) {
347         Log.d(TAG, "onSyncTransferredCallback()");
348         Map.Entry<IBinder, SyncTransferInfo> entry = findSyncTransfer(bda);
349         if (entry != null) {
350             mSyncTransfers.remove(entry);
351             IPeriodicAdvertisingCallback callback = entry.getValue().callback;
352             try {
353                 callback.onSyncTransferred(mAdapter.getRemoteDevice(bda), status);
354             } catch (RemoteException e) {
355                 throw new IllegalArgumentException("Can't find callback for sync transfer");
356             }
357         }
358     }
359 
transferSync(BluetoothDevice bda, int serviceData, int syncHandle)360     void transferSync(BluetoothDevice bda, int serviceData, int syncHandle) {
361         Log.d(TAG, "transferSync()");
362         Map.Entry<IBinder, SyncInfo> entry = findSync(syncHandle);
363         if (entry == null) {
364             Log.d(TAG, "transferSync: callback not registered");
365             return;
366         }
367         //check for duplicate transfers
368         mSyncTransfers.put(entry.getKey(), new SyncTransferInfo(bda.getAddress(),
369                            entry.getValue().callback));
370         syncTransferNative(PA_SOURCE_REMOTE, bda.getAddress(), serviceData, syncHandle);
371     }
372 
transferSetInfo(BluetoothDevice bda, int serviceData, int advHandle, IPeriodicAdvertisingCallback callback)373     void transferSetInfo(BluetoothDevice bda, int serviceData,
374                   int advHandle, IPeriodicAdvertisingCallback callback) {
375         SyncDeathRecipient deathRecipient = new SyncDeathRecipient(callback);
376         IBinder binder = toBinder(callback);
377         if (DBG) {
378             Log.d(TAG, "transferSetInfo() " + binder);
379         }
380         try {
381             binder.linkToDeath(deathRecipient, 0);
382         } catch (RemoteException e) {
383             throw new IllegalArgumentException("Can't link to periodic scanner death");
384         }
385         mSyncTransfers.put(binder, new SyncTransferInfo(bda.getAddress(), callback));
386         transferSetInfoNative(PA_SOURCE_LOCAL, bda.getAddress(), serviceData, advHandle);
387     }
388 
389     static {
classInitNative()390         classInitNative();
391     }
392 
classInitNative()393     private static native void classInitNative();
394 
initializeNative()395     private native void initializeNative();
396 
cleanupNative()397     private native void cleanupNative();
398 
startSyncNative(int sid, String address, int skip, int timeout, int regId)399     private native void startSyncNative(int sid, String address, int skip, int timeout, int regId);
400 
stopSyncNative(int syncHandle)401     private native void stopSyncNative(int syncHandle);
402 
cancelSyncNative(int sid, String address)403     private native void cancelSyncNative(int sid, String address);
404 
syncTransferNative(int paSource, String address, int serviceData, int syncHandle)405     private native void syncTransferNative(int paSource, String address, int serviceData,
406                                            int syncHandle);
407 
transferSetInfoNative(int paSource, String address, int serviceData, int advHandle)408     private native void transferSetInfoNative(int paSource, String address, int serviceData,
409                                               int advHandle);
410 }
411