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