blob: 83659e3ac9c1014484c90ec42b8a8457a8a4ae5c [file] [log] [blame]
/*
* Copyright (C) 2017 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.bluetooth.gatt;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.le.IPeriodicAdvertisingCallback;
import android.bluetooth.le.PeriodicAdvertisingReport;
import android.bluetooth.le.ScanRecord;
import android.bluetooth.le.ScanResult;
import android.os.IBinder;
import android.os.IInterface;
import android.os.RemoteException;
import android.util.Log;
import com.android.bluetooth.btservice.AdapterService;
import com.android.internal.annotations.VisibleForTesting;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
/**
* Manages Bluetooth LE Periodic scans
*
* @hide
*/
@VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
public class PeriodicScanManager {
private static final boolean DBG = GattServiceConfig.DBG;
private static final String TAG = GattServiceConfig.TAG_PREFIX + "SyncManager";
private final BluetoothAdapter mAdapter;
private final PeriodicScanNativeInterface mNativeInterface;
Map<IBinder, SyncInfo> mSyncs = new ConcurrentHashMap<>();
Map<IBinder, SyncTransferInfo> mSyncTransfers = Collections.synchronizedMap(new HashMap<>());
static int sTempRegistrationId = -1;
private static final int PA_SOURCE_LOCAL = 1;
private static final int PA_SOURCE_REMOTE = 2;
/**
* Constructor of {@link SyncManager}.
*/
PeriodicScanManager(AdapterService adapterService) {
if (DBG) {
Log.d(TAG, "advertise manager created");
}
mAdapter = BluetoothAdapter.getDefaultAdapter();
mNativeInterface = PeriodicScanNativeInterface.getInstance();
}
void start() {
mNativeInterface.init(this);
}
void cleanup() {
if (DBG) {
Log.d(TAG, "cleanup()");
}
mNativeInterface.cleanup();
mSyncs.clear();
sTempRegistrationId = -1;
}
class SyncTransferInfo {
public String address;
public SyncDeathRecipient deathRecipient;
public IPeriodicAdvertisingCallback callback;
SyncTransferInfo(String address, IPeriodicAdvertisingCallback callback) {
this.address = address;
this.callback = callback;
}
}
class SyncInfo {
/* When id is negative, the registration is ongoing. When the registration finishes, id
* becomes equal to sync_handle */
public Integer id;
public Integer advSid;
public String address;
public Integer skip;
public Integer timeout;
public SyncDeathRecipient deathRecipient;
public IPeriodicAdvertisingCallback callback;
SyncInfo(Integer id, Integer advSid, String address, Integer skip, Integer timeout,
SyncDeathRecipient deathRecipient,
IPeriodicAdvertisingCallback callback) {
this.id = id;
this.advSid = advSid;
this.address = address;
this.skip = skip;
this.timeout = timeout;
this.deathRecipient = deathRecipient;
this.callback = callback;
}
}
Map.Entry<IBinder, SyncTransferInfo> findSyncTransfer(String address) {
Map.Entry<IBinder, SyncTransferInfo> entry = null;
for (Map.Entry<IBinder, SyncTransferInfo> e : mSyncTransfers.entrySet()) {
if (e.getValue().address.equals(address)) {
entry = e;
break;
}
}
return entry;
}
IBinder toBinder(IPeriodicAdvertisingCallback e) {
return ((IInterface) e).asBinder();
}
class SyncDeathRecipient implements IBinder.DeathRecipient {
public IPeriodicAdvertisingCallback callback;
SyncDeathRecipient(IPeriodicAdvertisingCallback callback) {
this.callback = callback;
}
@Override
public void binderDied() {
if (DBG) {
Log.d(TAG, "Binder is dead - unregistering advertising set");
}
stopSync(callback);
}
}
Map.Entry<IBinder, SyncInfo> findSync(int syncHandle) {
Map.Entry<IBinder, SyncInfo> entry = null;
for (Map.Entry<IBinder, SyncInfo> e : mSyncs.entrySet()) {
if (e.getValue().id == syncHandle) {
entry = e;
break;
}
}
return entry;
}
Map.Entry<IBinder, SyncInfo> findMatchingSync(int advSid, String address) {
Map.Entry<IBinder, SyncInfo> entry = null;
for (Map.Entry<IBinder, SyncInfo> e : mSyncs.entrySet()) {
if (e.getValue().advSid == advSid && e.getValue().address.equals(address)) {
return entry = e;
}
}
return entry;
}
Map<IBinder, SyncInfo> findAllSync(int syncHandle) {
Map<IBinder, SyncInfo> syncMap = new HashMap<IBinder, SyncInfo>();
for (Map.Entry<IBinder, SyncInfo> e : mSyncs.entrySet()) {
if (e.getValue().id != syncHandle) {
continue;
}
syncMap.put(e.getKey(), new SyncInfo(e.getValue().id,
e.getValue().advSid,
e.getValue().address,
e.getValue().skip,
e.getValue().timeout,
e.getValue().deathRecipient,
e.getValue().callback));
}
return syncMap;
}
void onSyncStarted(int regId, int syncHandle, int sid, int addressType, String address, int phy,
int interval, int status) throws Exception {
Map<IBinder, SyncInfo> syncMap = findAllSync(regId);
if (syncMap.size() == 0) {
Log.d(TAG, "onSyncStarted() - no callback found for regId " + regId);
mNativeInterface.stopSync(syncHandle);
return;
}
synchronized (mSyncs) {
for (Map.Entry<IBinder, SyncInfo> e : mSyncs.entrySet()) {
if (e.getValue().id != regId) {
continue;
}
IPeriodicAdvertisingCallback callback = e.getValue().callback;
if (status == 0) {
Log.d(TAG, "onSyncStarted: updating id with syncHandle " + syncHandle);
e.setValue(new SyncInfo(syncHandle, sid, address, e.getValue().skip,
e.getValue().timeout, e.getValue().deathRecipient,
callback));
callback.onSyncEstablished(syncHandle, mAdapter.getRemoteDevice(address),
sid, e.getValue().skip, e.getValue().timeout,
status);
} else {
callback.onSyncEstablished(syncHandle, mAdapter.getRemoteDevice(address),
sid, e.getValue().skip, e.getValue().timeout,
status);
IBinder binder = e.getKey();
binder.unlinkToDeath(e.getValue().deathRecipient, 0);
mSyncs.remove(binder);
}
}
}
}
void onSyncReport(int syncHandle, int txPower, int rssi, int dataStatus, byte[] data)
throws Exception {
Map<IBinder, SyncInfo> syncMap = findAllSync(syncHandle);
if (syncMap.isEmpty()) {
Log.i(TAG, "onSyncReport() - no callback found for syncHandle " + syncHandle);
return;
}
for (Map.Entry<IBinder, SyncInfo> e :syncMap.entrySet()) {
IPeriodicAdvertisingCallback callback = e.getValue().callback;
PeriodicAdvertisingReport report =
new PeriodicAdvertisingReport(syncHandle, txPower, rssi, dataStatus,
ScanRecord.parseFromBytes(data));
callback.onPeriodicAdvertisingReport(report);
}
}
void onSyncLost(int syncHandle) throws Exception {
Map<IBinder, SyncInfo> syncMap = findAllSync(syncHandle);
if (syncMap.isEmpty()) {
Log.i(TAG, "onSyncLost() - no callback found for syncHandle " + syncHandle);
return;
}
for (Map.Entry<IBinder, SyncInfo> e :syncMap.entrySet()) {
IPeriodicAdvertisingCallback callback = e.getValue().callback;
IBinder binder = toBinder(callback);
synchronized (mSyncs) {
mSyncs.remove(binder);
}
callback.onSyncLost(syncHandle);
}
}
void onBigInfoReport(int syncHandle, boolean encrypted) throws Exception {
Map<IBinder, SyncInfo> syncMap = findAllSync(syncHandle);
if (syncMap.isEmpty()) {
Log.i(TAG, "onBigInfoReport() - no callback found for syncHandle " + syncHandle);
return;
}
for (Map.Entry<IBinder, SyncInfo> e : syncMap.entrySet()) {
IPeriodicAdvertisingCallback callback = e.getValue().callback;
callback.onBigInfoAdvertisingReport(syncHandle, encrypted);
}
}
void startSync(ScanResult scanResult, int skip, int timeout,
IPeriodicAdvertisingCallback callback) {
SyncDeathRecipient deathRecipient = new SyncDeathRecipient(callback);
IBinder binder = toBinder(callback);
try {
binder.linkToDeath(deathRecipient, 0);
} catch (RemoteException e) {
throw new IllegalArgumentException("Can't link to periodic scanner death");
}
String address = scanResult.getDevice().getAddress();
int sid = scanResult.getAdvertisingSid();
if (DBG) {
Log.d(TAG, "startSync for Device: " + address + " sid: " + sid);
}
synchronized (mSyncs) {
Map.Entry<IBinder, SyncInfo> entry = findMatchingSync(sid, address);
if (entry != null) {
//Found matching sync. Copy sync handle
if (DBG) {
Log.d(TAG, "startSync: Matching entry found");
}
mSyncs.put(binder, new SyncInfo(entry.getValue().id, sid, address,
entry.getValue().skip, entry.getValue().timeout, deathRecipient,
callback));
if (entry.getValue().id >= 0) {
try {
callback.onSyncEstablished(entry.getValue().id,
mAdapter.getRemoteDevice(address),
sid, entry.getValue().skip,
entry.getValue().timeout, 0 /*success*/);
} catch (RemoteException e) {
throw new IllegalArgumentException("Can't invoke callback");
}
} else {
Log.d(TAG, "startSync(): sync pending for same remote");
}
return;
}
}
int cbId = --sTempRegistrationId;
mSyncs.put(binder, new SyncInfo(cbId, sid, address, skip, timeout,
deathRecipient, callback));
if (DBG) {
Log.d(TAG, "startSync() - reg_id=" + cbId + ", callback: " + binder);
}
mNativeInterface.startSync(sid, address, skip, timeout, cbId);
}
void stopSync(IPeriodicAdvertisingCallback callback) {
IBinder binder = toBinder(callback);
if (DBG) {
Log.d(TAG, "stopSync() " + binder);
}
SyncInfo sync = null;
synchronized (mSyncs) {
sync = mSyncs.remove(binder);
}
if (sync == null) {
Log.e(TAG, "stopSync() - no client found for callback");
return;
}
Integer syncHandle = sync.id;
binder.unlinkToDeath(sync.deathRecipient, 0);
Log.d(TAG, "stopSync: " + syncHandle);
synchronized (mSyncs) {
Map.Entry<IBinder, SyncInfo> entry = findSync(syncHandle);
if (entry != null) {
Log.d(TAG, "stopSync() - another app synced to same PA, not stopping sync");
return;
}
}
Log.d(TAG, "calling stopSyncNative: " + syncHandle.intValue());
if (syncHandle < 0) {
Log.i(TAG, "cancelSync() - sync not established yet");
mNativeInterface.cancelSync(sync.advSid, sync.address);
} else {
mNativeInterface.stopSync(syncHandle.intValue());
}
}
void onSyncTransferredCallback(int paSource, int status, String bda) {
Map.Entry<IBinder, SyncTransferInfo> entry = findSyncTransfer(bda);
if (entry != null) {
mSyncTransfers.remove(entry);
IPeriodicAdvertisingCallback callback = entry.getValue().callback;
try {
callback.onSyncTransferred(mAdapter.getRemoteDevice(bda), status);
} catch (RemoteException e) {
throw new IllegalArgumentException("Can't find callback for sync transfer");
}
}
}
void transferSync(BluetoothDevice bda, int serviceData, int syncHandle) {
Log.d(TAG, "transferSync()");
Map.Entry<IBinder, SyncInfo> entry = findSync(syncHandle);
if (entry == null) {
Log.d(TAG, "transferSync: callback not registered");
return;
}
//check for duplicate transfers
mSyncTransfers.put(entry.getKey(), new SyncTransferInfo(bda.getAddress(),
entry.getValue().callback));
mNativeInterface.syncTransfer(bda, serviceData, syncHandle);
}
void transferSetInfo(BluetoothDevice bda, int serviceData,
int advHandle, IPeriodicAdvertisingCallback callback) {
SyncDeathRecipient deathRecipient = new SyncDeathRecipient(callback);
IBinder binder = toBinder(callback);
if (DBG) {
Log.d(TAG, "transferSetInfo() " + binder);
}
try {
binder.linkToDeath(deathRecipient, 0);
} catch (RemoteException e) {
throw new IllegalArgumentException("Can't link to periodic scanner death");
}
mSyncTransfers.put(binder, new SyncTransferInfo(bda.getAddress(), callback));
mNativeInterface.transferSetInfo(bda, serviceData, advHandle);
}
}