blob: 3dcf30d30029291383893791f3ee09fdf0378902 [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.le.AdvertiseData;
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.Utils;
import com.android.bluetooth.btservice.AdapterService;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
/**
* Manages Bluetooth LE Periodic scans
*
* @hide
*/
class PeriodicScanManager {
private static final boolean DBG = GattServiceConfig.DBG;
private static final String TAG = GattServiceConfig.TAG_PREFIX + "SyncManager";
private final AdapterService mAdapterService;
Map<IBinder, SyncInfo> mSyncs = Collections.synchronizedMap(new HashMap<>());
static int sTempRegistrationId = -1;
/**
* Constructor of {@link SyncManager}.
*/
PeriodicScanManager(AdapterService adapterService) {
logd("advertise manager created");
mAdapterService = adapterService;
}
void start() {
initializeNative();
}
void cleanup() {
logd("cleanup()");
cleanupNative();
mSyncs.clear();
sTempRegistrationId = -1;
}
class SyncInfo {
/* When id is negative, the registration is ongoing. When the registration finishes, id
* becomes equal to sync_handle */
public Integer id;
public SyncDeathRecipient deathRecipient;
public IPeriodicAdvertisingCallback callback;
SyncInfo(Integer id, SyncDeathRecipient deathRecipient,
IPeriodicAdvertisingCallback callback) {
this.id = id;
this.deathRecipient = deathRecipient;
this.callback = callback;
}
}
IBinder toBinder(IPeriodicAdvertisingCallback e) {
return ((IInterface) e).asBinder();
}
class SyncDeathRecipient implements IBinder.DeathRecipient {
IPeriodicAdvertisingCallback callback;
public 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 sync_handle) {
Map.Entry<IBinder, SyncInfo> entry = null;
for (Map.Entry<IBinder, SyncInfo> e : mSyncs.entrySet()) {
if (e.getValue().id == sync_handle) {
entry = e;
break;
}
}
return entry;
}
void onSyncStarted(int reg_id, int sync_handle, int sid, int address_type, String address,
int phy, int interval, int status) throws Exception {
logd("onSyncStarted() - reg_id=" + reg_id + ", sync_handle=" + sync_handle + ", status="
+ status);
Map.Entry<IBinder, SyncInfo> entry = findSync(reg_id);
if (entry == null) {
Log.i(TAG, "onSyncStarted() - no callback found for reg_id " + reg_id);
// Sync was stopped before it was properly registered.
stopSyncNative(sync_handle);
return;
}
IPeriodicAdvertisingCallback callback = entry.getValue().callback;
if (status == 0) {
entry.setValue(new SyncInfo(sync_handle, entry.getValue().deathRecipient, callback));
} else {
IBinder binder = entry.getKey();
binder.unlinkToDeath(entry.getValue().deathRecipient, 0);
mSyncs.remove(binder);
}
// TODO: fix callback arguments
// callback.onSyncStarted(sync_handle, tx_power, status);
}
void onSyncReport(int sync_handle, int tx_power, int rssi, int data_status, byte[] data)
throws Exception {
logd("onSyncReport() - sync_handle=" + sync_handle);
Map.Entry<IBinder, SyncInfo> entry = findSync(sync_handle);
if (entry == null) {
Log.i(TAG, "onSyncReport() - no callback found for sync_handle " + sync_handle);
return;
}
IPeriodicAdvertisingCallback callback = entry.getValue().callback;
PeriodicAdvertisingReport report = new PeriodicAdvertisingReport(
sync_handle, tx_power, rssi, data_status, ScanRecord.parseFromBytes(data));
callback.onPeriodicAdvertisingReport(report);
}
void onSyncLost(int sync_handle) throws Exception {
logd("onSyncLost() - sync_handle=" + sync_handle);
Map.Entry<IBinder, SyncInfo> entry = findSync(sync_handle);
if (entry == null) {
Log.i(TAG, "onSyncLost() - no callback found for sync_handle " + sync_handle);
return;
}
IPeriodicAdvertisingCallback callback = entry.getValue().callback;
mSyncs.remove(entry);
callback.onSyncLost(sync_handle);
}
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();
int cb_id = --sTempRegistrationId;
mSyncs.put(binder, new SyncInfo(cb_id, deathRecipient, callback));
logd("startSync() - reg_id=" + cb_id + ", callback: " + binder);
startSyncNative(sid, address, skip, timeout, cb_id);
}
void stopSync(IPeriodicAdvertisingCallback callback) {
IBinder binder = toBinder(callback);
logd("stopSync() " + binder);
SyncInfo sync = mSyncs.remove(binder);
if (sync == null) {
Log.e(TAG, "stopSync() - no client found for callback");
return;
}
Integer sync_handle = sync.id;
binder.unlinkToDeath(sync.deathRecipient, 0);
if (sync_handle < 0) {
Log.i(TAG, "stopSync() - not finished registration yet");
// Sync will be freed once initiated in onSyncStarted()
return;
}
stopSyncNative(sync_handle);
}
private void logd(String s) {
if (DBG) {
Log.d(TAG, s);
}
}
static {
classInitNative();
}
private native static void classInitNative();
private native void initializeNative();
private native void cleanupNative();
private native void startSyncNative(int sid, String address, int skip, int timeout, int reg_id);
private native void stopSyncNative(int sync_handle);
}