• 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 
onBigInfoReport(int syncHandle, boolean encrypted)261     void onBigInfoReport(int syncHandle, boolean encrypted)
262         throws Exception {
263         if (DBG) {
264             Log.d(TAG, "onBigInfoReport() - syncHandle=" + syncHandle +
265                     " , encrypted=" + encrypted);
266         }
267         Map<IBinder, SyncInfo> syncMap = findAllSync(syncHandle);
268         if (syncMap.isEmpty()) {
269             Log.i(TAG, "onBigInfoReport() - no callback found for syncHandle " + syncHandle);
270             return;
271         }
272         for (Map.Entry<IBinder, SyncInfo> e : syncMap.entrySet()) {
273             IPeriodicAdvertisingCallback callback = e.getValue().callback;
274             callback.onBigInfoAdvertisingReport(syncHandle, encrypted);
275         }
276     }
277 
startSync(ScanResult scanResult, int skip, int timeout, IPeriodicAdvertisingCallback callback)278     void startSync(ScanResult scanResult, int skip, int timeout,
279             IPeriodicAdvertisingCallback callback) {
280         SyncDeathRecipient deathRecipient = new SyncDeathRecipient(callback);
281         IBinder binder = toBinder(callback);
282         try {
283             binder.linkToDeath(deathRecipient, 0);
284         } catch (RemoteException e) {
285             throw new IllegalArgumentException("Can't link to periodic scanner death");
286         }
287 
288         String address = scanResult.getDevice().getAddress();
289         int sid = scanResult.getAdvertisingSid();
290         if (DBG) {
291             Log.d(TAG, "startSync for Device: " + address + " sid: " + sid);
292         }
293         synchronized (mSyncs) {
294             Map.Entry<IBinder, SyncInfo> entry = findMatchingSync(sid, address);
295             if (entry != null) {
296                 //Found matching sync. Copy sync handle
297                 if (DBG) {
298                     Log.d(TAG, "startSync: Matching entry found");
299                 }
300                 mSyncs.put(binder, new SyncInfo(entry.getValue().id, sid, address,
301                         entry.getValue().skip, entry.getValue().timeout, deathRecipient,
302                         callback));
303                 if (entry.getValue().id >= 0) {
304                     try {
305                         callback.onSyncEstablished(entry.getValue().id,
306                                                    mAdapter.getRemoteDevice(address),
307                                                    sid, entry.getValue().skip,
308                                                    entry.getValue().timeout, 0 /*success*/);
309                     } catch (RemoteException e) {
310                         throw new IllegalArgumentException("Can't invoke callback");
311                     }
312                 } else {
313                     Log.d(TAG, "startSync(): sync pending for same remote");
314                 }
315                 return;
316             }
317         }
318 
319         int cbId = --sTempRegistrationId;
320         mSyncs.put(binder, new SyncInfo(cbId, sid, address, skip, timeout,
321                 deathRecipient, callback));
322 
323         if (DBG) {
324             Log.d(TAG, "startSync() - reg_id=" + cbId + ", callback: " + binder);
325         }
326         startSyncNative(sid, address, skip, timeout, cbId);
327     }
328 
stopSync(IPeriodicAdvertisingCallback callback)329     void stopSync(IPeriodicAdvertisingCallback callback) {
330         IBinder binder = toBinder(callback);
331         if (DBG) {
332             Log.d(TAG, "stopSync() " + binder);
333         }
334         SyncInfo sync = null;
335         synchronized (mSyncs) {
336             sync = mSyncs.remove(binder);
337         }
338         if (sync == null) {
339             Log.e(TAG, "stopSync() - no client found for callback");
340             return;
341         }
342 
343         Integer syncHandle = sync.id;
344         binder.unlinkToDeath(sync.deathRecipient, 0);
345         Log.d(TAG, "stopSync: " + syncHandle);
346 
347         synchronized (mSyncs) {
348             Map.Entry<IBinder, SyncInfo> entry = findSync(syncHandle);
349             if (entry != null) {
350                 Log.d(TAG, "stopSync() - another app synced to same PA, not stopping sync");
351                 return;
352             }
353         }
354         Log.d(TAG, "calling stopSyncNative: " + syncHandle.intValue());
355         if (syncHandle < 0) {
356             Log.i(TAG, "cancelSync() - sync not established yet");
357             cancelSyncNative(sync.advSid, sync.address);
358         } else {
359             stopSyncNative(syncHandle.intValue());
360         }
361     }
362 
onSyncTransferredCallback(int paSource, int status, String bda)363     void onSyncTransferredCallback(int paSource, int status, String bda) {
364         Log.d(TAG, "onSyncTransferredCallback()");
365         Map.Entry<IBinder, SyncTransferInfo> entry = findSyncTransfer(bda);
366         if (entry != null) {
367             mSyncTransfers.remove(entry);
368             IPeriodicAdvertisingCallback callback = entry.getValue().callback;
369             try {
370                 callback.onSyncTransferred(mAdapter.getRemoteDevice(bda), status);
371             } catch (RemoteException e) {
372                 throw new IllegalArgumentException("Can't find callback for sync transfer");
373             }
374         }
375     }
376 
transferSync(BluetoothDevice bda, int serviceData, int syncHandle)377     void transferSync(BluetoothDevice bda, int serviceData, int syncHandle) {
378         Log.d(TAG, "transferSync()");
379         Map.Entry<IBinder, SyncInfo> entry = findSync(syncHandle);
380         if (entry == null) {
381             Log.d(TAG, "transferSync: callback not registered");
382             return;
383         }
384         //check for duplicate transfers
385         mSyncTransfers.put(entry.getKey(), new SyncTransferInfo(bda.getAddress(),
386                            entry.getValue().callback));
387         syncTransferNative(PA_SOURCE_REMOTE, bda.getAddress(), serviceData, syncHandle);
388     }
389 
transferSetInfo(BluetoothDevice bda, int serviceData, int advHandle, IPeriodicAdvertisingCallback callback)390     void transferSetInfo(BluetoothDevice bda, int serviceData,
391                   int advHandle, IPeriodicAdvertisingCallback callback) {
392         SyncDeathRecipient deathRecipient = new SyncDeathRecipient(callback);
393         IBinder binder = toBinder(callback);
394         if (DBG) {
395             Log.d(TAG, "transferSetInfo() " + binder);
396         }
397         try {
398             binder.linkToDeath(deathRecipient, 0);
399         } catch (RemoteException e) {
400             throw new IllegalArgumentException("Can't link to periodic scanner death");
401         }
402         mSyncTransfers.put(binder, new SyncTransferInfo(bda.getAddress(), callback));
403         transferSetInfoNative(PA_SOURCE_LOCAL, bda.getAddress(), serviceData, advHandle);
404     }
405 
406     static {
classInitNative()407         classInitNative();
408     }
409 
classInitNative()410     private static native void classInitNative();
411 
initializeNative()412     private native void initializeNative();
413 
cleanupNative()414     private native void cleanupNative();
415 
startSyncNative(int sid, String address, int skip, int timeout, int regId)416     private native void startSyncNative(int sid, String address, int skip, int timeout, int regId);
417 
stopSyncNative(int syncHandle)418     private native void stopSyncNative(int syncHandle);
419 
cancelSyncNative(int sid, String address)420     private native void cancelSyncNative(int sid, String address);
421 
syncTransferNative(int paSource, String address, int serviceData, int syncHandle)422     private native void syncTransferNative(int paSource, String address, int serviceData,
423                                            int syncHandle);
424 
transferSetInfoNative(int paSource, String address, int serviceData, int advHandle)425     private native void transferSetInfoNative(int paSource, String address, int serviceData,
426                                               int advHandle);
427 }
428