blob: 5f8ba52e512087862b2d58539f5f0416412b3990 [file] [log] [blame]
/******************************************************************************
*
* Copyright 2021 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.groupclient;
import static com.android.bluetooth.Utils.enforceBluetoothPrivilegedPermission;
import android.bluetooth.BluetoothDeviceGroup;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothProfile;
import android.bluetooth.BluetoothUuid;
import android.bluetooth.DeviceGroup;
import android.bluetooth.IBluetoothDeviceGroup;
import android.bluetooth.IBluetoothGroupCallback;
import android.bluetooth.BluetoothGroupCallback;
import android.content.AttributionSource;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.os.HandlerThread;
import android.os.IBinder;
import android.os.RemoteException;
import android.os.ParcelUuid;
import android.os.SystemProperties;
import android.util.Log;
import com.android.bluetooth.btservice.AdapterService;
import com.android.bluetooth.btservice.Config;
import com.android.bluetooth.btservice.ProfileService;
import com.android.bluetooth.btservice.ServiceFactory;
import com.android.bluetooth.Utils;
import java.util.ArrayList;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.UUID;
/**
* Provides Bluetooth CSIP Client profile, as a service in the Bluetooth application.
* @hide
*/
public class GroupService extends ProfileService {
private static final boolean DBG = true;
private static final String TAG = "BluetoothGroupService";
protected static final boolean VDBG = true;//Log.isLoggable(TAG, Log.VERBOSE);
private GroupScanner mGroupScanner;
private static GroupService sGroupService;
private AdapterService mAdapterService;
GroupClientNativeInterface mGroupNativeInterface;
GroupAppMap mAppMap = new GroupAppMap();
private static CopyOnWriteArrayList<DeviceGroup> mCoordinatedSets
= new CopyOnWriteArrayList<DeviceGroup>();
private static HashMap<Integer, byte[]> setSirkMap = new HashMap<Integer, byte[]>();
private static final int INVALID_APP_ID = 0x10;
private static final int INVALID_SET_ID = 0x10;
private static final UUID EMPTY_UUID = UUID.fromString("00000000-0000-0000-0000-000000000000");
/* parameters to hold details for ongoing set discovery and pending set discovery */
private SetDiscoveryRequest mCurrentSetDisc = null;
private SetDiscoveryRequest mPendingSetDisc = null;
/* Constants for Coordinated set properties */
private static final String SET_ID = "SET_ID";
private static final String INCLUDING_SRVC = "INCLUDING_SRVC";
private static final String SIZE = "SIZE";
private static final String SIRK = "SIRK";
private static final String LOCK_SUPPORT = "LOCK_SUPPORT";
private class SetDiscoveryRequest {
private int mAppId = INVALID_APP_ID;
private int mSetId = INVALID_SET_ID;
private boolean mDiscInProgress = false;
SetDiscoveryRequest() {
mAppId = INVALID_APP_ID;
mSetId = INVALID_SET_ID;
mDiscInProgress = false;
}
SetDiscoveryRequest(int appId, int setId, boolean inProgress) {
mAppId = appId;
mSetId = setId;
mDiscInProgress = inProgress;
}
}
private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
if (action == null) {
Log.e(TAG, "Received intent with null action");
return;
}
switch (action) {
case BluetoothDevice.ACTION_BOND_STATE_CHANGED:
BluetoothDevice device = intent.getParcelableExtra(
BluetoothDevice.EXTRA_DEVICE);
int bondState = intent.getIntExtra(BluetoothDevice.EXTRA_BOND_STATE,
BluetoothDevice.ERROR);
if (bondState == BluetoothDevice.BOND_NONE) {
int setId = getRemoteDeviceGroupId(device, null);
if (setId < BluetoothDeviceGroup.INVALID_GROUP_ID) {
Log.i(TAG, " Group Device "+ device +
" unpaired. Group ID: " + setId);
removeSetMemberFromCSet(setId, device);
}
}
break;
}
}
};
@Override
protected IProfileServiceBinder initBinder() {
return new GroupBinder(this);
}
@Override
protected boolean start() {
if (DBG) {
Log.d(TAG, "start()");
}
mGroupNativeInterface = Objects.requireNonNull(GroupClientNativeInterface.getInstance(),
"GroupClientNativeInterface cannot be null when GroupService starts");
mAdapterService = Objects.requireNonNull(AdapterService.getAdapterService(),
"AdapterService cannot be null when GroupService starts");
mGroupScanner = new GroupScanner(this);
mGroupNativeInterface.init();
setGroupService(this);
// register receiver for Bluetooth State change
IntentFilter filter = new IntentFilter();
filter.addAction(BluetoothDevice.ACTION_BOND_STATE_CHANGED);
registerReceiver(mReceiver, filter);
return true;
}
private static synchronized void setGroupService(GroupService instance) {
if (DBG) {
Log.d(TAG, "setGroupService(): set to: " + instance);
}
sGroupService = instance;
}
protected boolean stop() {
if (DBG) {
Log.d(TAG, "stop()");
}
if (mGroupScanner != null) {
mGroupScanner.cleanup();
}
if (sGroupService == null) {
Log.w(TAG, "stop() called already..");
return true;
}
unregisterReceiver(mReceiver);
// Cleanup native interface
mGroupNativeInterface.cleanup();
mGroupNativeInterface = null;
// Mark service as stopped
setGroupService(null);
// cleanup initializations
mGroupScanner = null;
mAdapterService = null;
return true;
}
@Override
protected void cleanup() {
if (DBG) {
Log.d(TAG, "cleanup()");
}
// Cleanup native interface
if (mGroupNativeInterface != null) {
mGroupNativeInterface.cleanup();
mGroupNativeInterface = null;
}
// cleanup initializations
mGroupScanner = null;
mAdapterService = null;
}
/**
* Get the GroupService instance
* @return GroupService instance
*/
public static synchronized GroupService getGroupService() {
if (sGroupService == null) {
Log.w(TAG, "getGroupService(): service is NULL");
return null;
}
if (!sGroupService.isAvailable()) {
Log.w(TAG, "getGroupService(): service is not available");
return null;
}
return sGroupService;
}
/* API to load coordinated set from bonded device on BT ON */
public static void loadDeviceGroupFromBondedDevice (
BluetoothDevice device, String setDetails) {
String[] csets = setDetails.split(" ");
if (VDBG) Log.v(TAG, " Device is part of " + csets.length + " device groups");
for (String setInfo: csets) {
String[] setProperties = setInfo.split("~");
int setId = INVALID_SET_ID, size = 0;
UUID inclSrvcUuid = UUID.fromString("00000000-0000-0000-0000-000000000000");
boolean lockSupport = false;
for (String property: setProperties) {
if (VDBG) Log.v(TAG, "Property = " + property);
String[] propSplit = property.split(":");
if (propSplit[0].equals(SET_ID)) {
setId = Integer.parseInt(propSplit[1]);
} else if (propSplit[0].equals(INCLUDING_SRVC)) {
inclSrvcUuid = UUID.fromString(propSplit[1]);
} else if (propSplit[0].equals(SIZE)) {
size = Integer.parseInt(propSplit[1]);
} else if (propSplit[0].equals(SIRK) && setId != 16) {
setSirkMap.put(setId, GroupScanner.hexStringToByteArray(propSplit[1]));
} else if (propSplit[0].equals(LOCK_SUPPORT)) {
lockSupport = Boolean.parseBoolean(propSplit[1]);
}
}
DeviceGroup set = getCoordinatedSet(setId, false);
if (set == null) {
List<BluetoothDevice> members = new ArrayList<BluetoothDevice>();
members.add(device);
set = new DeviceGroup(setId, size, members,
new ParcelUuid(inclSrvcUuid), lockSupport);
mCoordinatedSets.add(set);
} else {
if (!set.getDeviceGroupMembers().contains(device)) {
set.getDeviceGroupMembers().add(device);
}
}
if (VDBG) Log.v(TAG, "Device " + device + " loaded in Group ("+ setId +")"
+ " Devices: " + set.getDeviceGroupMembers());
}
}
/* API to accept PSRI data from EIR packet */
public void handleEIRGroupData(BluetoothDevice device, String data) {
mGroupScanner.handleEIRGroupData(device, data.getBytes());
}
public static void setAdvanceAudioSupport() {
Log.d(TAG, "setAdvanceAudioSupport: Setting support from LEA Module");
if (SystemProperties.get("persist.vendor.service.bt.adv_audio_mask").isEmpty()) {
SystemProperties.set("persist.vendor.service.bt.adv_audio_mask",
String.valueOf(Config.ADV_AUDIO_UNICAST_FEAT_MASK |
Config.ADV_AUDIO_BCA_FEAT_MASK |
Config.ADV_AUDIO_BCS_FEAT_MASK));
}
}
private static class GroupBinder
extends IBluetoothDeviceGroup.Stub implements IProfileServiceBinder {
private GroupService mService;
private GroupService getService() {
if (mService != null && mService.isAvailable()) {
return mService;
}
return null;
}
GroupBinder(GroupService service) {
if (DBG) {
Log.v(TAG, "GroupBinder()");
}
mService = service;
}
@Override
public void cleanup() {
mService = null;
}
@Override
public void connect(int appId, BluetoothDevice device, AttributionSource source) {
if (DBG) {
Log.d(TAG, "connect Device " + device);
}
GroupService service = getService();
if (service == null || !Utils.checkConnectPermissionForDataDelivery(
service, source, "connect")) {
return;
}
service.connect(appId, device);
}
@Override
public void disconnect(int appId, BluetoothDevice device, AttributionSource source) {
if (DBG) {
Log.d(TAG, "disconnect Device " + device);
}
GroupService service = getService();
if (service == null || !Utils.checkConnectPermissionForDataDelivery(
service, source, "disconnect")) {
return;
}
service.disconnect(appId, device);
}
@Override
public void registerGroupClientApp(ParcelUuid uuid,
IBluetoothGroupCallback callback, AttributionSource source) {
if (VDBG) {
Log.d(TAG, "registerGroupClientApp");
}
GroupService service = getService();
if (service == null || !Utils.checkConnectPermissionForDataDelivery(
service, source, "registerGroupClientApp")) {
return;
}
service.registerGroupClientApp(uuid.getUuid(), callback, null);
}
@Override
public void unregisterGroupClientApp(int appId, AttributionSource source) {
if (VDBG) {
Log.d(TAG, "unregisterGroupClientApp");
}
GroupService service = getService();
if (service == null || !Utils.checkConnectPermissionForDataDelivery(
service, source, "unregisterGroupClientApp")) {
return;
}
service.unregisterGroupClientApp(appId);
}
@Override
public void setExclusiveAccess(int appId, int groupId, List<BluetoothDevice> devices,
int value, AttributionSource source) {
if (VDBG) {
Log.d(TAG, "setExclusiveAccess");
}
GroupService service = getService();
if (service == null || !Utils.checkConnectPermissionForDataDelivery(
service, source, "setExclusiveAccess")) {
return;
}
service.setLockValue(appId, groupId, devices, value);
}
@Override
public void startGroupDiscovery(int appId, int groupId
, AttributionSource source) throws RemoteException {
if (VDBG) {
Log.d(TAG, "startGroupDiscovery");
}
GroupService service = getService();
if (service == null || !Utils.checkConnectPermissionForDataDelivery(service,
source, "startGroupDiscovery")) {
return;
}
service.startSetDiscovery(appId, groupId);
}
@Override
public void stopGroupDiscovery(int appId, int groupId, AttributionSource source) {
if (VDBG) {
Log.d(TAG, "stopGroupDiscovery");
}
GroupService service = getService();
if (service == null || !Utils.checkConnectPermissionForDataDelivery(
service, source, "stopGroupDiscovery")) {
return;
}
enforceBluetoothPrivilegedPermission(service);
service.stopSetDiscovery(appId, groupId);
}
@Override
public void getExclusiveAccessStatus(int appId, int groupId,
List<BluetoothDevice> devices, AttributionSource source) {
if (DBG) {
Log.d(TAG, "getExclusiveAccessStatus");
}
GroupService service = getService();
if (service == null || !Utils.checkConnectPermissionForDataDelivery(
service, source, "getExclusiveAccessStatus")) {
return;
}
enforceBluetoothPrivilegedPermission(service);
}
@Override
public List<DeviceGroup> getDiscoveredGroups(boolean mPublicAddr
, AttributionSource source) {
if (DBG) {
Log.d(TAG, "getDiscoveredGroups");
}
GroupService service = getService();
if (service == null || !Utils.checkConnectPermissionForDataDelivery(
service, source, "getDiscoveredGroups")) {
return null;
}
return service.getDiscoveredCoordinatedSets(mPublicAddr);
}
@Override
public DeviceGroup getDeviceGroup(int setId, boolean mPublicAddr,
AttributionSource source) {
if (DBG) {
Log.d(TAG, "getDeviceGroup");
}
GroupService service = getService();
if (service == null || !Utils.checkConnectPermissionForDataDelivery(
service, source, "getDeviceGroup")) {
return null;
}
return (service.getCoordinatedSet(setId, mPublicAddr));
}
@Override
public int getRemoteDeviceGroupId (BluetoothDevice device, ParcelUuid uuid,
boolean mPublicAddr, AttributionSource source) {
if (DBG) {
Log.d(TAG, "getRemoteDeviceGroupId");
}
GroupService service = getService();
if (service == null || !Utils.checkConnectPermissionForDataDelivery(
service, source, "getRemoteDeviceGroupId")) {
return INVALID_SET_ID;
}
return service.getRemoteDeviceGroupId(device, uuid, mPublicAddr);
}
@Override
public boolean isGroupDiscoveryInProgress (int setId, AttributionSource source) {
if (DBG) {
Log.d(TAG, "isGroupDiscoveryInProgress");
}
GroupService service = getService();
if (service == null || !Utils.checkScanPermissionForDataDelivery(
service, source, "isGroupDiscoveryInProgress")) {
return false;
}
return service.isSetDiscoveryInProgress(setId);
}
};
/**
* DeathReceipient handler to unregister applications those are
* disconnected ungracefully (ie. crash or forced close).
*/
class GroupAppDeathRecipient implements IBinder.DeathRecipient {
int mAppId;
GroupAppDeathRecipient(int appId) {
mAppId = appId;
Log.i(TAG, "GroupAppDeathRecipient");
}
@Override
public void binderDied() {
if (DBG) {
Log.d(TAG, "Binder is dead - unregistering app (" + mAppId + ")!");
}
mAppMap.remove(mAppId);
unregisterGroupClientApp(mAppId);
}
}
/* for registration of other Bluetooth profile in Bluetooth App Space*/
public void registerGroupClientModule(BluetoothGroupCallback callback) {
Log.d(TAG, "registerGroupClientModule");
UUID uuid;
if (mGroupNativeInterface == null) return;
// Generate an unique UUID for Bluetooth Modules which is not used by others apps
do {
uuid = UUID.randomUUID();
} while(mAppMap.appUuids.contains(uuid));
registerGroupClientApp(uuid, null, callback);
}
/* Registers CSIP App or module with CSIP native layer */
public void registerGroupClientApp(UUID uuid, IBluetoothGroupCallback appCb,
BluetoothGroupCallback localCallback) {
if (DBG) {
Log.d(TAG, "registerGroupClientApp: UUID = " + uuid.toString());
}
boolean isLocal = false;
if (localCallback != null) {
isLocal = true;
}
mAppMap.add(uuid, isLocal, appCb, localCallback);
mGroupNativeInterface.registerCsipApp(uuid.getLeastSignificantBits(),
uuid.getMostSignificantBits());
}
/* Unregisters Bluetooth module (BT profile) with CSIP*/
public void unregisterGroupClientModule(int appId) {
unregisterGroupClientApp(appId);
}
/* Unregisters App/Module with CSIP*/
public void unregisterGroupClientApp(int appId) {
if (DBG) {
Log.d(TAG, "unregisterGroupClientApp: appId = " + appId);
}
if (mGroupNativeInterface == null) return;
mAppMap.remove(appId);
mGroupNativeInterface.unregisterCsipApp(appId);
}
/* API to request change in lock value */
public void setLockValue(int appId, int setId, List<BluetoothDevice> devices,
int value) {
if (DBG) {
Log.d(TAG, "setExclusiveAccess: appId = " + appId + ", setId: " + setId +
", value = " + value + ", set Members = " + devices);
}
if (mGroupNativeInterface == null) return;
// appId and setId validation is done at stack layer
mGroupNativeInterface.setLockValue(appId, setId, devices, value);
}
/* Starts the set members discovery for the requested coordinated set */
public void startSetDiscovery(int appId, int setId) throws RemoteException {
if (DBG) {
Log.d(TAG, "startGroupDiscovery. setId = " + setId + " Initiating appId = " + appId);
}
// Get Apllication details
GroupAppMap.GroupClientApp app = mAppMap.getById(appId);
if (app == null) {
Log.e(TAG, "Application not found for appId: " + appId);
return;
}
DeviceGroup cSet = getCoordinatedSet(setId, true);
if (cSet == null || !setSirkMap.containsKey(setId)) {
Log.e(TAG, "Invalid Group Id: " + setId);
mCurrentSetDisc = null;
app.appCb.onGroupDiscoveryStatusChanged(setId, BluetoothDeviceGroup.GROUP_DISCOVERY_STOPPED,
BluetoothDeviceGroup.DISCOVERY_NOT_STARTED_INVALID_PARAMS);
return;
}
/* check if all set members are already discovered */
int setSize = cSet.getDeviceGroupSize();
if (setSize != 0 && cSet.getTotalDiscoveredGroupDevices() >= setSize) {
app.appCb.onGroupDiscoveryStatusChanged(setId,
BluetoothDeviceGroup.GROUP_DISCOVERY_STOPPED,
BluetoothDeviceGroup.DISCOVERY_COMPLETED);
return;
}
if (mCurrentSetDisc != null && mCurrentSetDisc.mDiscInProgress) {
Log.e(TAG, "Group Discovery is already in Progress for Group: "
+ mCurrentSetDisc.mSetId + " from AppId: " + mCurrentSetDisc.mAppId
+ " Stop current Group discovery");
mPendingSetDisc = new SetDiscoveryRequest(appId, setId, false);
mGroupScanner.stopSetDiscovery(mCurrentSetDisc.mSetId, 0);
return;
} else if (mCurrentSetDisc == null) {
mCurrentSetDisc = new SetDiscoveryRequest(appId, setId, false);
}
int transport;
byte[] sirk;
sirk = setSirkMap.get(setId);
/*TODO: Optimize logic if device type is UNKNOWN */
try {
BluetoothDevice device = cSet.getDeviceGroupMembers().get(0);
transport = device.getType();
} catch (IndexOutOfBoundsException e) {
Log.e(TAG, "Invalid Group- No device found : " + e);
mCurrentSetDisc = null;
return;
}
mGroupScanner.startSetDiscovery(setId, sirk, transport,
cSet.getDeviceGroupSize(), cSet.getDeviceGroupMembers());
mCurrentSetDisc.mDiscInProgress = true;
try {
if (app.appCb != null) {
app.appCb.onGroupDiscoveryStatusChanged(setId,
BluetoothDeviceGroup.GROUP_DISCOVERY_STARTED,
BluetoothDeviceGroup.DISCOVERY_STARTED_BY_APPL);
}
} catch (RemoteException e) {
Log.e(TAG, "Exception : " + e);
}
}
/* Stops the set members discovery for the requested coordinated set */
public void stopSetDiscovery(int appId, int setId) {
if (DBG) {
Log.d(TAG, "stopGroupDiscovery: appId = " + appId + " groupId = " + setId);
}
// Get Apllication details
GroupAppMap.GroupClientApp app = mAppMap.getById(appId);
if (app == null) {
Log.e(TAG, "Application not found for appId: " + appId);
return;
}
// check if requesting app is stopping the discovery
if (mCurrentSetDisc == null || mCurrentSetDisc.mAppId != appId) {
Log.e(TAG, " Either no discovery in progress or Stop Request from"
+ " App which has not started Group Discovery");
return;
}
mGroupScanner.stopSetDiscovery(setId, BluetoothDeviceGroup.DISCOVERY_STOPPED_BY_APPL);
}
/* Reading lock status of coordinated set for ordered access procedure */
public void getLockStatus(int setId, List<BluetoothDevice> devices) {
//TODO: Future enhancement
}
/* To connect to Coordinated Set Device */
public void connect (int appId, BluetoothDevice device) {
Log.d(TAG, "connect Device: " + device + ", appId: " + appId);
if (mGroupNativeInterface == null) return;
mGroupNativeInterface.connectSetDevice(appId, device);
}
/* To disconnect from Coordinated Set Device */
public void disconnect (int appId, BluetoothDevice device) {
Log.d(TAG, "disconnect Device: " + device + ", appId: " + appId);
if (mGroupNativeInterface == null) return;
mGroupNativeInterface.disconnectSetDevice(appId, device);
}
public List<DeviceGroup> getDiscoveredCoordinatedSets() {
return getDiscoveredCoordinatedSets(true);
}
/* returns all discovered coordinated sets */
public List<DeviceGroup> getDiscoveredCoordinatedSets(boolean mPublicAddr) {
if (DBG) {
Log.d(TAG, "getDiscoveredGroups");
}
/* Add logic to replace random addresses to public addresses if requested */
// Iterate on coordinated sets
// check address type. Replace with public if requested
if (mPublicAddr) {
List<DeviceGroup> coordinatedSets = new ArrayList<DeviceGroup>();
AdapterService adapterService = Objects.requireNonNull(
AdapterService.getAdapterService(),
"AdapterService cannot be null");
for (DeviceGroup set: mCoordinatedSets) {
DeviceGroup cSet = new DeviceGroup(
set.getDeviceGroupId(), set.getDeviceGroupSize(),
new ArrayList<BluetoothDevice>(),
set.getIncludingServiceUUID(), set.isExclusiveAccessSupported());
for (BluetoothDevice device: set.getDeviceGroupMembers()) {
BluetoothDevice publicDevice = device;
if (adapterService.isIgnoreDevice(device)) {
publicDevice = adapterService.getIdentityAddress(device);
}
cSet.getDeviceGroupMembers().add(publicDevice);
}
coordinatedSets.add(cSet);
}
return coordinatedSets;
}
return mCoordinatedSets;
}
public static DeviceGroup getCoordinatedSet(int setId) {
return getCoordinatedSet(setId, true);
}
/* returns requested coordinated set */
public static DeviceGroup getCoordinatedSet(int setId, boolean mPublicAddr) {
if (DBG) {
Log.d(TAG, "getDeviceGroup : groupId = " + setId
+ " mPublicAddr: " + mPublicAddr);
}
AdapterService adapterService = Objects.requireNonNull(
AdapterService.getAdapterService(), "AdapterService cannot be null");
for (DeviceGroup cSet: mCoordinatedSets) {
if (cSet.getDeviceGroupId() == setId) {
if (!mPublicAddr) {
return cSet;
// Public addresses are requested. Replace address with public addr
} else {
DeviceGroup set = new DeviceGroup(
cSet.getDeviceGroupId(), cSet.getDeviceGroupSize(),
new ArrayList<BluetoothDevice>(),
cSet.getIncludingServiceUUID(), cSet.isExclusiveAccessSupported());
for (BluetoothDevice device: cSet.getDeviceGroupMembers()) {
if (device.getBondState() == BluetoothDevice.BOND_BONDED) {
BluetoothDevice publicDevice = device;
if (adapterService.isIgnoreDevice(device)) {
publicDevice = adapterService.getIdentityAddress(device);
}
set.getDeviceGroupMembers().add(publicDevice);
}
}
return set;
}
}
}
return null;
}
public boolean isSetDiscoveryInProgress (int setId) {
if (DBG) {
Log.d(TAG, "isGroupDiscoveryInProgress: groupId = " + setId);
}
if (mCurrentSetDisc != null && mCurrentSetDisc.mSetId == setId
&& mCurrentSetDisc.mDiscInProgress)
return true;
return false;
}
public int getRemoteDeviceGroupId (BluetoothDevice device, ParcelUuid uuid) {
return getRemoteDeviceGroupId(device, uuid, true);
}
public int getRemoteDeviceGroupId (BluetoothDevice device, ParcelUuid uuid,
boolean mPublicAddr) {
if (DBG) {
Log.d(TAG, "getRemoteDeviceGroupId: device = " + device + " uuid = " + uuid
+ ", mPublicAddr = " + mPublicAddr);
}
if (mAdapterService == null) {
Log.e(TAG, "AdapterService instance is NULL. Return.");
return INVALID_SET_ID;
}
BluetoothDevice setDevice = null;
if (mPublicAddr && mAdapterService.isIgnoreDevice(device)) {
setDevice = mAdapterService.getIdentityAddress(device);
}
if (uuid == null) {
uuid = new ParcelUuid(EMPTY_UUID);
}
for (DeviceGroup cSet: mCoordinatedSets) {
if ((cSet.getDeviceGroupMembers().contains(device) ||
cSet.getDeviceGroupMembers().contains(setDevice))
&& cSet.getIncludingServiceUUID().equals(uuid)) {
return cSet.getDeviceGroupId();
}
}
return INVALID_SET_ID;
}
/* This API is called when pairing with LE Audio capable set member fails or
* when set member is unpaired. Removing the set member from list gives option
* to user to rediscover it */
public void removeSetMemberFromCSet(int setId, BluetoothDevice device) {
Log.d(TAG, "removeDeviceFromDeviceGroup: setId = " + setId + ", Device: " + device);
DeviceGroup cSet = getCoordinatedSet(setId, false);
if (cSet != null) {
cSet.getDeviceGroupMembers().remove(device);
if (cSet.getDeviceGroupMembers().size() == 0) {
Log.i(TAG, "Last device unpaired. Removing Device Group from database");
mCoordinatedSets.remove(cSet);
return;
}
}
cSet = getCoordinatedSet(setId, true);
if (cSet != null) {
cSet.getDeviceGroupMembers().remove(device);
if (cSet.getDeviceGroupMembers().size() == 0) {
Log.i(TAG, "Last device unpaired. Removing Device Group from database");
mCoordinatedSets.remove(cSet);
}
}
}
public void printAllCoordinatedSets() {
if (VDBG) {
for (DeviceGroup set: mCoordinatedSets) {
Log.i(TAG, "GROUP_ID: " + set.getDeviceGroupId()
+ ", size = " + set.getDeviceGroupSize()
+ ", discovered = " + set.getTotalDiscoveredGroupDevices()
+ ", Including Srvc Uuid = "+ set.getIncludingServiceUUID()
+ ", devices = " + set.getDeviceGroupMembers());
}
}
}
/* Callback received from CSIP native layer when an APP/module has been registered */
protected void onCsipAppRegistered (int status, int appId, UUID uuid) {
Log.d(TAG, "onGroupClientAppRegistered: appId: " + appId + ", UUID: " + uuid.toString());
GroupAppMap.GroupClientApp app = mAppMap.getByUuid(uuid);
if (app == null) {
Log.e(TAG, "Application not found for UUID: " + uuid.toString());
return;
}
app.appId = appId;
// Give callback to the application that app has been registered
try {
if (app.isRegistered && app.isLocal) {
app.mCallback.onGroupClientAppRegistered(status, appId);
} else if (app.isRegistered && !app.isLocal) {
app.linkToDeath(new GroupAppDeathRecipient(appId));
app.appCb.onGroupClientAppRegistered(status, appId);
}
} catch (RemoteException e) {
Log.e(TAG, "Exception : " + e);
}
}
/* When CSIP Profile connection state has been changed */
protected void onConnectionStateChanged(int appId, BluetoothDevice device,
int state, int status) {
Log.d(TAG, "onConnectionStateChanged: appId: " + appId + ", device: " + device
+ ", State: " + state + ", Status: " + status);
GroupAppMap.GroupClientApp app = mAppMap.getById(appId);
if (app == null) {
Log.e(TAG, "Application not found for appId: " + appId);
return;
}
try {
if (app.isRegistered && app.isLocal) {
app.mCallback.onConnectionStateChanged(state, device);
} else if (app.isRegistered && !app.isLocal) {
app.appCb.onConnectionStateChanged(state, device);
}
} catch (RemoteException e) {
Log.e(TAG, "Exception : " + e);
}
}
/* When a new set member is discovered as a part of Set Discovery procedure */
protected void onSetMemberFound (int setId, BluetoothDevice device) {
Log.d(TAG, "onGroupDeviceFound: groupId: " + setId + ", device: " + device);
for (DeviceGroup cSet: mCoordinatedSets) {
if (cSet.getDeviceGroupId() == setId
&& !cSet.getDeviceGroupMembers().contains(device)) {
cSet.getDeviceGroupMembers().add(device);
break;
}
}
// Give callback to adapterservice to initiate bonding if required
mAdapterService.processGroupMember(setId, device);
// Give callback to the application that started Set Discovery
GroupAppMap.GroupClientApp app = mAppMap.getById(mCurrentSetDisc.mAppId);
if (app == null) {
Log.e(TAG, "Application not found for appId: " + mCurrentSetDisc.mAppId);
return;
}
try {
if (app.appCb != null) {
app.appCb.onGroupDeviceFound(setId, device);
}
} catch (RemoteException e) {
Log.e(TAG, "Exception : " + e);
}
}
/* When set discovery procedure has been completed */
protected void onSetDiscoveryCompleted (int setId,
int totalDiscovered, int reason) {
Log.d(TAG, "onGroupDiscoveryCompleted: groupId: " + setId + ", totalDiscovered = "
+ totalDiscovered + "reason: " + reason);
// mark Set Discovery procedure as completed
mCurrentSetDisc.mDiscInProgress = false;
// Give callback to the application that Set Discovery has been completed
GroupAppMap.GroupClientApp app = mAppMap.getById(mCurrentSetDisc.mAppId);
if (app == null) {
Log.e(TAG, "Application not found for appId: " + mCurrentSetDisc.mAppId);
return;
}
try {
if (app.appCb != null) {
app.appCb.onGroupDiscoveryStatusChanged(setId,
BluetoothDeviceGroup.GROUP_DISCOVERY_STOPPED, reason);
}
DeviceGroup cSet = getCoordinatedSet(setId, false);
if (VDBG && cSet != null) {
Log.i(TAG, "Device Group: groupId" + setId + ", devices: "
+ cSet.getDeviceGroupMembers());
}
if (mPendingSetDisc != null) {
mCurrentSetDisc = mPendingSetDisc;
mPendingSetDisc = null;
startSetDiscovery(mCurrentSetDisc.mAppId, mCurrentSetDisc.mSetId);
} else {
mCurrentSetDisc = null;
}
} catch (RemoteException e) {
Log.e(TAG, "Exception : " + e);
}
}
/* Callback received from CSIP native layer when a new Coordinated set has been
* identified with remote device */
protected void onNewSetFound(int setId, BluetoothDevice device, int size,
byte[] sirk, UUID pSrvcUuid, boolean lockSupport) {
Log.d(TAG, "onNewGroupFound: Address : " + device + ", groupId: " + setId
+ ", size: " + size + ", uuid: " + pSrvcUuid.toString());
// Form Coordinated Set Object and store in ArrayList
List<BluetoothDevice> devices = new ArrayList<BluetoothDevice>();
devices.add(device);
DeviceGroup cSet = new DeviceGroup(setId, size, devices,
new ParcelUuid(pSrvcUuid), lockSupport);
mCoordinatedSets.add(cSet);
// Store sirk in hashmap of setId, sirk
setSirkMap.put(setId, sirk);
// Give Callback to all registered application
try {
for (GroupAppMap.GroupClientApp app: mAppMap.mApps) {
if (app.isRegistered && !app.isLocal) {
if (app.appCb != null)//temp check
app.appCb.onNewGroupFound(setId, device, new ParcelUuid(pSrvcUuid));
}
}
} catch (RemoteException e) {
Log.e(TAG, "Exception : " + e);
}
}
/* Callback received from CSIP native layer when undiscovered set member is connected */
protected void onNewSetMemberFound (int setId, BluetoothDevice device) {
Log.d(TAG, "onNewGroupDeviceFound: groupId = " + setId + ", Device = " + device);
if (mAdapterService == null) {
Log.e(TAG, "AdapterService instance is NULL. Return.");
return;
}
if (mAdapterService.isIgnoreDevice(device)) {
device = mAdapterService.getIdentityAddress(device);
}
// check if this device is already part of an already existing coordinated set
/* Scenario: When a device was not discovered during initial set discovery
* procedure and later user had explicitely paired with this device
* from pair new device UI option. Not required to send onSetMemberFound
* callback to application*/
if (setSirkMap.containsKey(setId)) {
for (DeviceGroup cSet: mCoordinatedSets) {
if (cSet.getDeviceGroupId() == setId &&
(!cSet.getDeviceGroupMembers().contains(device))) {
cSet.getDeviceGroupMembers().add(device);
break;
}
}
return;
}
}
/* callback received when lock status is changed for requested coordinated set*/
protected void onLockStatusChanged (int appId, int setId, int value, int status,
List<BluetoothDevice> devices) {
Log.d(TAG, "onExclusiveAccessChanged: appId = " + appId + ", groupId = " + setId +
", value = " + value + ", status = " + status + ", devices = " + devices);
GroupAppMap.GroupClientApp app = mAppMap.getById(appId);
if (app == null) {
Log.e(TAG, "Application not found for appId: " + appId);
return;
}
try {
if (app.isRegistered && app.isLocal) {
app.mCallback.onExclusiveAccessChanged(setId, value, status, devices);
} else if (app.isRegistered && !app.isLocal) {
app.appCb.onExclusiveAccessChanged(setId, value, status, devices);
}
} catch (RemoteException e) {
Log.e(TAG, "Exception : " + e);
}
}
/* Callback received when earlier denied lock is now available */
protected void onLockAvailable (int appId, int setId, BluetoothDevice device) {
Log.d(TAG, "onExclusiveAccessAvailable: Remote(" + device + "), Group Id: "
+ setId + ", App Id: " + appId);
GroupAppMap.GroupClientApp app = mAppMap.getById(appId);
if (app == null) {
Log.e(TAG, "Application not found for appId: " + appId);
return;
}
try {
if (app.isRegistered && app.isLocal) {
app.mCallback.onExclusiveAccessAvailable(setId, device);
} else if (app.isRegistered && !app.isLocal) {
app.appCb.onExclusiveAccessAvailable(setId, device);
}
} catch (RemoteException e) {
Log.e(TAG, "Exception : " + e);
}
}
/* Callback received when set size has been changed */
/* TODO: Scanarios are unknown. Actions are to be decided */
protected void onSetSizeChanged (int setId, int size, BluetoothDevice device) {
Log.d(TAG, "onGroupSizeChanged: Group Id: " + setId + ", New Size: " + size +
", Notifying device: " + device);
// TODO: Logic to be incorporated once use case is understood
}
/* Callback received when set SIRK has been changed */
/* TODO: Scanarios are unknown. Actions are to be decided */
protected void onSetSirkChanged(int setId, byte[] sirk, BluetoothDevice device) {
Log.d(TAG, "onGroupIdChanged Group Id: " + setId + ", Notifying device: " + device);
// TODO: Logic to be incorporated once use case is understood
}
}