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