blob: 85917a471ec536817e526275043856d6d56627c7 [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.AdvertisingSetParameters;
import android.bluetooth.le.IAdvertisingSetCallback;
import android.bluetooth.le.PeriodicAdvertisingParameters;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.IBinder;
import android.os.IInterface;
import android.os.Looper;
import android.os.RemoteException;
import android.util.Log;
import com.android.bluetooth.btservice.AdapterService;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
/**
* Manages Bluetooth LE advertising operations and interacts with bluedroid stack. TODO: add tests.
*
* @hide
*/
class AdvertiseManager {
private static final boolean DBG = GattServiceConfig.DBG;
private static final String TAG = GattServiceConfig.TAG_PREFIX + "AdvertiseManager";
private final GattService mService;
private final AdapterService mAdapterService;
private Handler mHandler;
Map<IBinder, AdvertiserInfo> mAdvertisers = Collections.synchronizedMap(new HashMap<>());
static int sTempRegistrationId = -1;
/**
* Constructor of {@link AdvertiseManager}.
*/
AdvertiseManager(GattService service, AdapterService adapterService) {
if (DBG) {
Log.d(TAG, "advertise manager created");
}
mService = service;
mAdapterService = adapterService;
}
/**
* Start a {@link HandlerThread} that handles advertising operations.
*/
void start() {
initializeNative();
HandlerThread thread = new HandlerThread("BluetoothAdvertiseManager");
thread.start();
mHandler = new Handler(thread.getLooper());
}
void cleanup() {
if (DBG) {
Log.d(TAG, "cleanup()");
}
cleanupNative();
mAdvertisers.clear();
sTempRegistrationId = -1;
if (mHandler != null) {
// Shut down the thread
mHandler.removeCallbacksAndMessages(null);
Looper looper = mHandler.getLooper();
if (looper != null) {
looper.quit();
}
mHandler = null;
}
}
class AdvertiserInfo {
/* When id is negative, the registration is ongoing. When the registration finishes, id
* becomes equal to advertiser_id */
public Integer id;
public AdvertisingSetDeathRecipient deathRecipient;
public IAdvertisingSetCallback callback;
AdvertiserInfo(Integer id, AdvertisingSetDeathRecipient deathRecipient,
IAdvertisingSetCallback callback) {
this.id = id;
this.deathRecipient = deathRecipient;
this.callback = callback;
}
}
IBinder toBinder(IAdvertisingSetCallback e) {
return ((IInterface) e).asBinder();
}
class AdvertisingSetDeathRecipient implements IBinder.DeathRecipient {
public IAdvertisingSetCallback callback;
AdvertisingSetDeathRecipient(IAdvertisingSetCallback callback) {
this.callback = callback;
}
@Override
public void binderDied() {
if (DBG) {
Log.d(TAG, "Binder is dead - unregistering advertising set");
}
stopAdvertisingSet(callback);
}
}
Map.Entry<IBinder, AdvertiserInfo> findAdvertiser(int advertiserId) {
Map.Entry<IBinder, AdvertiserInfo> entry = null;
for (Map.Entry<IBinder, AdvertiserInfo> e : mAdvertisers.entrySet()) {
if (e.getValue().id == advertiserId) {
entry = e;
break;
}
}
return entry;
}
void onAdvertisingSetStarted(int regId, int advertiserId, int txPower, int status)
throws Exception {
if (DBG) {
Log.d(TAG,
"onAdvertisingSetStarted() - regId=" + regId + ", advertiserId=" + advertiserId
+ ", status=" + status);
}
Map.Entry<IBinder, AdvertiserInfo> entry = findAdvertiser(regId);
if (entry == null) {
Log.i(TAG, "onAdvertisingSetStarted() - no callback found for regId " + regId);
// Advertising set was stopped before it was properly registered.
stopAdvertisingSetNative(advertiserId);
return;
}
IAdvertisingSetCallback callback = entry.getValue().callback;
if (status == 0) {
entry.setValue(
new AdvertiserInfo(advertiserId, entry.getValue().deathRecipient, callback));
} else {
IBinder binder = entry.getKey();
binder.unlinkToDeath(entry.getValue().deathRecipient, 0);
mAdvertisers.remove(binder);
}
callback.onAdvertisingSetStarted(advertiserId, txPower, status);
}
void onAdvertisingEnabled(int advertiserId, boolean enable, int status) throws Exception {
if (DBG) {
Log.d(TAG, "onAdvertisingSetEnabled() - advertiserId=" + advertiserId + ", enable="
+ enable + ", status=" + status);
}
Map.Entry<IBinder, AdvertiserInfo> entry = findAdvertiser(advertiserId);
if (entry == null) {
Log.i(TAG, "onAdvertisingSetEnable() - no callback found for advertiserId "
+ advertiserId);
return;
}
IAdvertisingSetCallback callback = entry.getValue().callback;
callback.onAdvertisingEnabled(advertiserId, enable, status);
}
void startAdvertisingSet(AdvertisingSetParameters parameters, AdvertiseData advertiseData,
AdvertiseData scanResponse, PeriodicAdvertisingParameters periodicParameters,
AdvertiseData periodicData, int duration, int maxExtAdvEvents,
IAdvertisingSetCallback callback) {
AdvertisingSetDeathRecipient deathRecipient = new AdvertisingSetDeathRecipient(callback);
IBinder binder = toBinder(callback);
try {
binder.linkToDeath(deathRecipient, 0);
} catch (RemoteException e) {
throw new IllegalArgumentException("Can't link to advertiser's death");
}
String deviceName = AdapterService.getAdapterService().getName();
byte[] advDataBytes = AdvertiseHelper.advertiseDataToBytes(advertiseData, deviceName);
byte[] scanResponseBytes = AdvertiseHelper.advertiseDataToBytes(scanResponse, deviceName);
byte[] periodicDataBytes = AdvertiseHelper.advertiseDataToBytes(periodicData, deviceName);
int cbId = --sTempRegistrationId;
mAdvertisers.put(binder, new AdvertiserInfo(cbId, deathRecipient, callback));
if (DBG) {
Log.d(TAG, "startAdvertisingSet() - reg_id=" + cbId + ", callback: " + binder);
}
startAdvertisingSetNative(parameters, advDataBytes, scanResponseBytes, periodicParameters,
periodicDataBytes, duration, maxExtAdvEvents, cbId);
}
void onOwnAddressRead(int advertiserId, int addressType, String address)
throws RemoteException {
if (DBG) {
Log.d(TAG, "onOwnAddressRead() advertiserId=" + advertiserId);
}
Map.Entry<IBinder, AdvertiserInfo> entry = findAdvertiser(advertiserId);
if (entry == null) {
Log.i(TAG, "onOwnAddressRead() - bad advertiserId " + advertiserId);
return;
}
IAdvertisingSetCallback callback = entry.getValue().callback;
callback.onOwnAddressRead(advertiserId, addressType, address);
}
void getOwnAddress(int advertiserId) {
getOwnAddressNative(advertiserId);
}
void stopAdvertisingSet(IAdvertisingSetCallback callback) {
IBinder binder = toBinder(callback);
if (DBG) {
Log.d(TAG, "stopAdvertisingSet() " + binder);
}
AdvertiserInfo adv = mAdvertisers.remove(binder);
if (adv == null) {
Log.e(TAG, "stopAdvertisingSet() - no client found for callback");
return;
}
Integer advertiserId = adv.id;
binder.unlinkToDeath(adv.deathRecipient, 0);
if (advertiserId < 0) {
Log.i(TAG, "stopAdvertisingSet() - advertiser not finished registration yet");
// Advertiser will be freed once initiated in onAdvertisingSetStarted()
return;
}
stopAdvertisingSetNative(advertiserId);
try {
callback.onAdvertisingSetStopped(advertiserId);
} catch (RemoteException e) {
Log.i(TAG, "error sending onAdvertisingSetStopped callback", e);
}
}
void enableAdvertisingSet(int advertiserId, boolean enable, int duration, int maxExtAdvEvents) {
enableAdvertisingSetNative(advertiserId, enable, duration, maxExtAdvEvents);
}
void setAdvertisingData(int advertiserId, AdvertiseData data) {
String deviceName = AdapterService.getAdapterService().getName();
setAdvertisingDataNative(advertiserId,
AdvertiseHelper.advertiseDataToBytes(data, deviceName));
}
void setScanResponseData(int advertiserId, AdvertiseData data) {
String deviceName = AdapterService.getAdapterService().getName();
setScanResponseDataNative(advertiserId,
AdvertiseHelper.advertiseDataToBytes(data, deviceName));
}
void setAdvertisingParameters(int advertiserId, AdvertisingSetParameters parameters) {
setAdvertisingParametersNative(advertiserId, parameters);
}
void setPeriodicAdvertisingParameters(int advertiserId,
PeriodicAdvertisingParameters parameters) {
setPeriodicAdvertisingParametersNative(advertiserId, parameters);
}
void setPeriodicAdvertisingData(int advertiserId, AdvertiseData data) {
String deviceName = AdapterService.getAdapterService().getName();
setPeriodicAdvertisingDataNative(advertiserId,
AdvertiseHelper.advertiseDataToBytes(data, deviceName));
}
void setPeriodicAdvertisingEnable(int advertiserId, boolean enable) {
setPeriodicAdvertisingEnableNative(advertiserId, enable);
}
void onAdvertisingDataSet(int advertiserId, int status) throws Exception {
if (DBG) {
Log.d(TAG,
"onAdvertisingDataSet() advertiserId=" + advertiserId + ", status=" + status);
}
Map.Entry<IBinder, AdvertiserInfo> entry = findAdvertiser(advertiserId);
if (entry == null) {
Log.i(TAG, "onAdvertisingDataSet() - bad advertiserId " + advertiserId);
return;
}
IAdvertisingSetCallback callback = entry.getValue().callback;
callback.onAdvertisingDataSet(advertiserId, status);
}
void onScanResponseDataSet(int advertiserId, int status) throws Exception {
if (DBG) {
Log.d(TAG,
"onScanResponseDataSet() advertiserId=" + advertiserId + ", status=" + status);
}
Map.Entry<IBinder, AdvertiserInfo> entry = findAdvertiser(advertiserId);
if (entry == null) {
Log.i(TAG, "onScanResponseDataSet() - bad advertiserId " + advertiserId);
return;
}
IAdvertisingSetCallback callback = entry.getValue().callback;
callback.onScanResponseDataSet(advertiserId, status);
}
void onAdvertisingParametersUpdated(int advertiserId, int txPower, int status)
throws Exception {
if (DBG) {
Log.d(TAG,
"onAdvertisingParametersUpdated() advertiserId=" + advertiserId + ", txPower="
+ txPower + ", status=" + status);
}
Map.Entry<IBinder, AdvertiserInfo> entry = findAdvertiser(advertiserId);
if (entry == null) {
Log.i(TAG, "onAdvertisingParametersUpdated() - bad advertiserId " + advertiserId);
return;
}
IAdvertisingSetCallback callback = entry.getValue().callback;
callback.onAdvertisingParametersUpdated(advertiserId, txPower, status);
}
void onPeriodicAdvertisingParametersUpdated(int advertiserId, int status) throws Exception {
if (DBG) {
Log.d(TAG, "onPeriodicAdvertisingParametersUpdated() advertiserId=" + advertiserId
+ ", status=" + status);
}
Map.Entry<IBinder, AdvertiserInfo> entry = findAdvertiser(advertiserId);
if (entry == null) {
Log.i(TAG,
"onPeriodicAdvertisingParametersUpdated() - bad advertiserId " + advertiserId);
return;
}
IAdvertisingSetCallback callback = entry.getValue().callback;
callback.onPeriodicAdvertisingParametersUpdated(advertiserId, status);
}
void onPeriodicAdvertisingDataSet(int advertiserId, int status) throws Exception {
if (DBG) {
Log.d(TAG, "onPeriodicAdvertisingDataSet() advertiserId=" + advertiserId + ", status="
+ status);
}
Map.Entry<IBinder, AdvertiserInfo> entry = findAdvertiser(advertiserId);
if (entry == null) {
Log.i(TAG, "onPeriodicAdvertisingDataSet() - bad advertiserId " + advertiserId);
return;
}
IAdvertisingSetCallback callback = entry.getValue().callback;
callback.onPeriodicAdvertisingDataSet(advertiserId, status);
}
void onPeriodicAdvertisingEnabled(int advertiserId, boolean enable, int status)
throws Exception {
if (DBG) {
Log.d(TAG, "onPeriodicAdvertisingEnabled() advertiserId=" + advertiserId + ", status="
+ status);
}
Map.Entry<IBinder, AdvertiserInfo> entry = findAdvertiser(advertiserId);
if (entry == null) {
Log.i(TAG, "onAdvertisingSetEnable() - bad advertiserId " + advertiserId);
return;
}
IAdvertisingSetCallback callback = entry.getValue().callback;
callback.onPeriodicAdvertisingEnabled(advertiserId, enable, status);
}
static {
classInitNative();
}
private static native void classInitNative();
private native void initializeNative();
private native void cleanupNative();
private native void startAdvertisingSetNative(AdvertisingSetParameters parameters,
byte[] advertiseData, byte[] scanResponse,
PeriodicAdvertisingParameters periodicParameters, byte[] periodicData, int duration,
int maxExtAdvEvents, int regId);
private native void getOwnAddressNative(int advertiserId);
private native void stopAdvertisingSetNative(int advertiserId);
private native void enableAdvertisingSetNative(int advertiserId, boolean enable, int duration,
int maxExtAdvEvents);
private native void setAdvertisingDataNative(int advertiserId, byte[] data);
private native void setScanResponseDataNative(int advertiserId, byte[] data);
private native void setAdvertisingParametersNative(int advertiserId,
AdvertisingSetParameters parameters);
private native void setPeriodicAdvertisingParametersNative(int advertiserId,
PeriodicAdvertisingParameters parameters);
private native void setPeriodicAdvertisingDataNative(int advertiserId, byte[] data);
private native void setPeriodicAdvertisingEnableNative(int advertiserId, boolean enable);
}