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.le.IPeriodicAdvertisingCallback;
20 import android.bluetooth.le.PeriodicAdvertisingReport;
21 import android.bluetooth.le.ScanRecord;
22 import android.bluetooth.le.ScanResult;
23 import android.os.IBinder;
24 import android.os.IInterface;
25 import android.os.RemoteException;
26 import android.util.Log;
27 
28 import com.android.bluetooth.btservice.AdapterService;
29 
30 import java.util.Collections;
31 import java.util.HashMap;
32 import java.util.Map;
33 
34 /**
35  * Manages Bluetooth LE Periodic scans
36  *
37  * @hide
38  */
39 class PeriodicScanManager {
40     private static final boolean DBG = GattServiceConfig.DBG;
41     private static final String TAG = GattServiceConfig.TAG_PREFIX + "SyncManager";
42 
43     private final AdapterService mAdapterService;
44     Map<IBinder, SyncInfo> mSyncs = Collections.synchronizedMap(new HashMap<>());
45     static int sTempRegistrationId = -1;
46 
47     /**
48      * Constructor of {@link SyncManager}.
49      */
PeriodicScanManager(AdapterService adapterService)50     PeriodicScanManager(AdapterService adapterService) {
51         if (DBG) {
52             Log.d(TAG, "advertise manager created");
53         }
54         mAdapterService = adapterService;
55     }
56 
start()57     void start() {
58         initializeNative();
59     }
60 
cleanup()61     void cleanup() {
62         if (DBG) {
63             Log.d(TAG, "cleanup()");
64         }
65         cleanupNative();
66         mSyncs.clear();
67         sTempRegistrationId = -1;
68     }
69 
70     class SyncInfo {
71         /* When id is negative, the registration is ongoing. When the registration finishes, id
72          * becomes equal to sync_handle */
73         public Integer id;
74         public SyncDeathRecipient deathRecipient;
75         public IPeriodicAdvertisingCallback callback;
76 
SyncInfo(Integer id, SyncDeathRecipient deathRecipient, IPeriodicAdvertisingCallback callback)77         SyncInfo(Integer id, SyncDeathRecipient deathRecipient,
78                 IPeriodicAdvertisingCallback callback) {
79             this.id = id;
80             this.deathRecipient = deathRecipient;
81             this.callback = callback;
82         }
83     }
84 
toBinder(IPeriodicAdvertisingCallback e)85     IBinder toBinder(IPeriodicAdvertisingCallback e) {
86         return ((IInterface) e).asBinder();
87     }
88 
89     class SyncDeathRecipient implements IBinder.DeathRecipient {
90         public IPeriodicAdvertisingCallback callback;
91 
SyncDeathRecipient(IPeriodicAdvertisingCallback callback)92         SyncDeathRecipient(IPeriodicAdvertisingCallback callback) {
93             this.callback = callback;
94         }
95 
96         @Override
binderDied()97         public void binderDied() {
98             if (DBG) {
99                 Log.d(TAG, "Binder is dead - unregistering advertising set");
100             }
101             stopSync(callback);
102         }
103     }
104 
findSync(int syncHandle)105     Map.Entry<IBinder, SyncInfo> findSync(int syncHandle) {
106         Map.Entry<IBinder, SyncInfo> entry = null;
107         for (Map.Entry<IBinder, SyncInfo> e : mSyncs.entrySet()) {
108             if (e.getValue().id == syncHandle) {
109                 entry = e;
110                 break;
111             }
112         }
113         return entry;
114     }
115 
onSyncStarted(int regId, int syncHandle, int sid, int addressType, String address, int phy, int interval, int status)116     void onSyncStarted(int regId, int syncHandle, int sid, int addressType, String address, int phy,
117             int interval, int status) throws Exception {
118         if (DBG) {
119             Log.d(TAG,
120                     "onSyncStarted() - regId=" + regId + ", syncHandle=" + syncHandle + ", status="
121                             + status);
122         }
123 
124         Map.Entry<IBinder, SyncInfo> entry = findSync(regId);
125         if (entry == null) {
126             Log.i(TAG, "onSyncStarted() - no callback found for regId " + regId);
127             // Sync was stopped before it was properly registered.
128             stopSyncNative(syncHandle);
129             return;
130         }
131 
132         IPeriodicAdvertisingCallback callback = entry.getValue().callback;
133         if (status == 0) {
134             entry.setValue(new SyncInfo(syncHandle, entry.getValue().deathRecipient, callback));
135         } else {
136             IBinder binder = entry.getKey();
137             binder.unlinkToDeath(entry.getValue().deathRecipient, 0);
138             mSyncs.remove(binder);
139         }
140 
141         // TODO: fix callback arguments
142         // callback.onSyncStarted(syncHandle, tx_power, status);
143     }
144 
onSyncReport(int syncHandle, int txPower, int rssi, int dataStatus, byte[] data)145     void onSyncReport(int syncHandle, int txPower, int rssi, int dataStatus, byte[] data)
146             throws Exception {
147         if (DBG) {
148             Log.d(TAG, "onSyncReport() - syncHandle=" + syncHandle);
149         }
150 
151         Map.Entry<IBinder, SyncInfo> entry = findSync(syncHandle);
152         if (entry == null) {
153             Log.i(TAG, "onSyncReport() - no callback found for syncHandle " + syncHandle);
154             return;
155         }
156 
157         IPeriodicAdvertisingCallback callback = entry.getValue().callback;
158         PeriodicAdvertisingReport report =
159                 new PeriodicAdvertisingReport(syncHandle, txPower, rssi, dataStatus,
160                         ScanRecord.parseFromBytes(data));
161         callback.onPeriodicAdvertisingReport(report);
162     }
163 
onSyncLost(int syncHandle)164     void onSyncLost(int syncHandle) throws Exception {
165         if (DBG) {
166             Log.d(TAG, "onSyncLost() - syncHandle=" + syncHandle);
167         }
168 
169         Map.Entry<IBinder, SyncInfo> entry = findSync(syncHandle);
170         if (entry == null) {
171             Log.i(TAG, "onSyncLost() - no callback found for syncHandle " + syncHandle);
172             return;
173         }
174 
175         IPeriodicAdvertisingCallback callback = entry.getValue().callback;
176         mSyncs.remove(entry);
177         callback.onSyncLost(syncHandle);
178     }
179 
startSync(ScanResult scanResult, int skip, int timeout, IPeriodicAdvertisingCallback callback)180     void startSync(ScanResult scanResult, int skip, int timeout,
181             IPeriodicAdvertisingCallback callback) {
182         SyncDeathRecipient deathRecipient = new SyncDeathRecipient(callback);
183         IBinder binder = toBinder(callback);
184         try {
185             binder.linkToDeath(deathRecipient, 0);
186         } catch (RemoteException e) {
187             throw new IllegalArgumentException("Can't link to periodic scanner death");
188         }
189 
190         String address = scanResult.getDevice().getAddress();
191         int sid = scanResult.getAdvertisingSid();
192 
193         int cbId = --sTempRegistrationId;
194         mSyncs.put(binder, new SyncInfo(cbId, deathRecipient, callback));
195 
196         if (DBG) {
197             Log.d(TAG, "startSync() - reg_id=" + cbId + ", callback: " + binder);
198         }
199         startSyncNative(sid, address, skip, timeout, cbId);
200     }
201 
stopSync(IPeriodicAdvertisingCallback callback)202     void stopSync(IPeriodicAdvertisingCallback callback) {
203         IBinder binder = toBinder(callback);
204         if (DBG) {
205             Log.d(TAG, "stopSync() " + binder);
206         }
207 
208         SyncInfo sync = mSyncs.remove(binder);
209         if (sync == null) {
210             Log.e(TAG, "stopSync() - no client found for callback");
211             return;
212         }
213 
214         Integer syncHandle = sync.id;
215         binder.unlinkToDeath(sync.deathRecipient, 0);
216 
217         if (syncHandle < 0) {
218             Log.i(TAG, "stopSync() - not finished registration yet");
219             // Sync will be freed once initiated in onSyncStarted()
220             return;
221         }
222 
223         stopSyncNative(syncHandle);
224     }
225 
226     static {
classInitNative()227         classInitNative();
228     }
229 
classInitNative()230     private static native void classInitNative();
231 
initializeNative()232     private native void initializeNative();
233 
cleanupNative()234     private native void cleanupNative();
235 
startSyncNative(int sid, String address, int skip, int timeout, int regId)236     private native void startSyncNative(int sid, String address, int skip, int timeout, int regId);
237 
stopSyncNative(int syncHandle)238     private native void stopSyncNative(int syncHandle);
239 }
240