Loading packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothEventManager.java +5 −2 Original line number Diff line number Diff line Loading @@ -18,6 +18,7 @@ package com.android.settingslib.bluetooth; import android.bluetooth.BluetoothA2dp; import android.bluetooth.BluetoothAdapter; import android.bluetooth.BluetoothCsipSetCoordinator; import android.bluetooth.BluetoothDevice; import android.bluetooth.BluetoothHeadset; import android.bluetooth.BluetoothHearingAid; Loading Loading @@ -351,8 +352,10 @@ public class BluetoothEventManager { cachedDevice.onBondingStateChanged(bondState); if (bondState == BluetoothDevice.BOND_NONE) { /* Check if we need to remove other Hearing Aid devices */ if (cachedDevice.getHiSyncId() != BluetoothHearingAid.HI_SYNC_ID_INVALID) { // Check if we need to remove other Coordinated set member devices / Hearing Aid // devices if (cachedDevice.getGroupId() != BluetoothCsipSetCoordinator.GROUP_ID_INVALID || cachedDevice.getHiSyncId() != BluetoothHearingAid.HI_SYNC_ID_INVALID) { mDeviceManager.onDeviceUnpaired(cachedDevice); } int reason = intent.getIntExtra(BluetoothDevice.EXTRA_REASON, Loading packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java +73 −0 Original line number Diff line number Diff line Loading @@ -18,6 +18,7 @@ package com.android.settingslib.bluetooth; import android.bluetooth.BluetoothAdapter; import android.bluetooth.BluetoothClass; import android.bluetooth.BluetoothCsipSetCoordinator; import android.bluetooth.BluetoothDevice; import android.bluetooth.BluetoothHearingAid; import android.bluetooth.BluetoothProfile; Loading @@ -41,7 +42,9 @@ import com.android.settingslib.Utils; import java.util.ArrayList; import java.util.Collection; import java.util.HashSet; import java.util.List; import java.util.Set; import java.util.concurrent.CopyOnWriteArrayList; /** Loading @@ -66,6 +69,7 @@ public class CachedBluetoothDevice implements Comparable<CachedBluetoothDevice> private final Object mProfileLock = new Object(); BluetoothDevice mDevice; private long mHiSyncId; private int mGroupId; // Need this since there is no method for getting RSSI short mRssi; // mProfiles and mRemovedProfiles does not do swap() between main and sub device. It is Loading Loading @@ -100,6 +104,8 @@ public class CachedBluetoothDevice implements Comparable<CachedBluetoothDevice> private boolean mIsA2dpProfileConnectedFail = false; private boolean mIsHeadsetProfileConnectedFail = false; private boolean mIsHearingAidProfileConnectedFail = false; // Group member devices for the coordinated set private Set<CachedBluetoothDevice> mMemberDevices = new HashSet<CachedBluetoothDevice>(); // Group second device for Hearing Aid private CachedBluetoothDevice mSubDevice; Loading Loading @@ -133,6 +139,7 @@ public class CachedBluetoothDevice implements Comparable<CachedBluetoothDevice> mDevice = device; fillData(); mHiSyncId = BluetoothHearingAid.HI_SYNC_ID_INVALID; mGroupId = BluetoothCsipSetCoordinator.GROUP_ID_INVALID; } /** Loading Loading @@ -317,6 +324,24 @@ public class CachedBluetoothDevice implements Comparable<CachedBluetoothDevice> return mIsCoordinatedSetMember; } /** * Get the coordinated set group id. * * @return the group id. */ public int getGroupId() { return mGroupId; } /** * Set the coordinated set group id. * * @param id the group id from the CSIP. */ public void setGroupId(int id) { mGroupId = id; } void onBondingDockConnect() { // Attempt to connect if UUIDs are available. Otherwise, // we will connect when the ACTION_UUID intent arrives. Loading Loading @@ -1191,4 +1216,52 @@ public class CachedBluetoothDevice implements Comparable<CachedBluetoothDevice> mSubDevice.mJustDiscovered = tmpJustDiscovered; fetchActiveDevices(); } /** * @return a set of member devices that are in the same coordinated set with this device. */ public Set<CachedBluetoothDevice> getMemberDevice() { return mMemberDevices; } /** * Store the member devices that are in the same coordinated set. */ public void setMemberDevice(CachedBluetoothDevice memberDevice) { mMemberDevices.add(memberDevice); } /** * Remove a device from the member device sets. */ public void removeMemberDevice(CachedBluetoothDevice memberDevice) { mMemberDevices.remove(memberDevice); } /** * In order to show the preference for the whole group, we always set the main device as the * first connected device in the coordinated set, and then switch the content of the main * device and member devices. * * @param prevMainDevice the previous Main device, it will be added into the member device set. * @param newMainDevie the new Main device, it will be removed from the member device set. */ public void switchMemberDeviceContent(CachedBluetoothDevice prevMainDevice, CachedBluetoothDevice newMainDevie) { // Backup from main device final BluetoothDevice tmpDevice = mDevice; final short tmpRssi = mRssi; final boolean tmpJustDiscovered = mJustDiscovered; // Set main device from sub device mDevice = newMainDevie.mDevice; mRssi = newMainDevie.mRssi; mJustDiscovered = newMainDevie.mJustDiscovered; setMemberDevice(prevMainDevice); mMemberDevices.remove(newMainDevie); // Set sub device from backup newMainDevie.mDevice = tmpDevice; newMainDevie.mRssi = tmpRssi; newMainDevie.mJustDiscovered = tmpJustDiscovered; fetchActiveDevices(); } } packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceManager.java +103 −14 Original line number Diff line number Diff line Loading @@ -18,6 +18,7 @@ package com.android.settingslib.bluetooth; import android.bluetooth.BluetoothAdapter; import android.bluetooth.BluetoothDevice; import android.bluetooth.BluetoothProfile; import android.content.Context; import android.util.Log; Loading @@ -26,6 +27,7 @@ import com.android.internal.annotations.VisibleForTesting; import java.util.ArrayList; import java.util.Collection; import java.util.List; import java.util.Set; /** * CachedBluetoothDeviceManager manages the set of remote Bluetooth devices. Loading @@ -41,11 +43,14 @@ public class CachedBluetoothDeviceManager { final List<CachedBluetoothDevice> mCachedDevices = new ArrayList<CachedBluetoothDevice>(); @VisibleForTesting HearingAidDeviceManager mHearingAidDeviceManager; @VisibleForTesting CsipDeviceManager mCsipDeviceManager; CachedBluetoothDeviceManager(Context context, LocalBluetoothManager localBtManager) { mContext = context; mBtManager = localBtManager; mHearingAidDeviceManager = new HearingAidDeviceManager(localBtManager, mCachedDevices); mCsipDeviceManager = new CsipDeviceManager(localBtManager, mCachedDevices); } public synchronized Collection<CachedBluetoothDevice> getCachedDevicesCopy() { Loading Loading @@ -79,7 +84,16 @@ public class CachedBluetoothDeviceManager { if (cachedDevice.getDevice().equals(device)) { return cachedDevice; } // Check sub devices if it exists // Check the member devices for the coordinated set if it exists final Set<CachedBluetoothDevice> memberDevices = cachedDevice.getMemberDevice(); if (memberDevices != null) { for (CachedBluetoothDevice memberDevice : memberDevices) { if (memberDevice.getDevice().equals(device)) { return memberDevice; } } } // Check sub devices for hearing aid if it exists CachedBluetoothDevice subDevice = cachedDevice.getSubDevice(); if (subDevice != null && subDevice.getDevice().equals(device)) { return subDevice; Loading @@ -102,8 +116,10 @@ public class CachedBluetoothDeviceManager { newDevice = findDevice(device); if (newDevice == null) { newDevice = new CachedBluetoothDevice(mContext, profileManager, device); mCsipDeviceManager.initCsipDeviceIfNeeded(newDevice); mHearingAidDeviceManager.initHearingAidDeviceIfNeeded(newDevice); if (!mHearingAidDeviceManager.setSubDeviceIfNeeded(newDevice)) { if (!mCsipDeviceManager.setMemberDeviceIfNeeded(newDevice) && !mHearingAidDeviceManager.setSubDeviceIfNeeded(newDevice)) { mCachedDevices.add(newDevice); mBtManager.getEventManager().dispatchDeviceAdded(newDevice); } Loading @@ -114,13 +130,23 @@ public class CachedBluetoothDeviceManager { } /** * Returns device summary of the pair of the hearing aid passed as the parameter. * Returns device summary of the pair of the hearing aid / CSIP passed as the parameter. * * @param CachedBluetoothDevice device * @return Device summary, or if the pair does not exist or if it is not a hearing aid, * then {@code null}. * @return Device summary, or if the pair does not exist or if it is not a hearing aid or * a CSIP set member, then {@code null}. */ public synchronized String getSubDeviceSummary(CachedBluetoothDevice device) { final Set<CachedBluetoothDevice> memberDevices = device.getMemberDevice(); if (memberDevices != null) { for (CachedBluetoothDevice memberDevice : memberDevices) { if (!memberDevice.isConnected()) { return null; } } return device.getConnectionSummary(); } CachedBluetoothDevice subDevice = device.getSubDevice(); if (subDevice != null && subDevice.isConnected()) { return subDevice.getConnectionSummary(); Loading @@ -132,12 +158,22 @@ public class CachedBluetoothDeviceManager { * Search for existing sub device {@link CachedBluetoothDevice}. * * @param device the address of the Bluetooth device * @return true for found sub device or false. * @return true for found sub / member device or false. */ public synchronized boolean isSubDevice(BluetoothDevice device) { for (CachedBluetoothDevice cachedDevice : mCachedDevices) { if (!cachedDevice.getDevice().equals(device)) { // Check sub devices if it exists // Check the member devices of the coordinated set if it exists Set<CachedBluetoothDevice> memberDevices = cachedDevice.getMemberDevice(); if (memberDevices != null) { for (CachedBluetoothDevice memberDevice : memberDevices) { if (memberDevice.getDevice().equals(device)) { return true; } } continue; } // Check sub devices of hearing aid if it exists CachedBluetoothDevice subDevice = cachedDevice.getSubDevice(); if (subDevice != null && subDevice.getDevice().equals(device)) { return true; Loading @@ -156,6 +192,14 @@ public class CachedBluetoothDeviceManager { mHearingAidDeviceManager.updateHearingAidsDevices(); } /** * Updates the Csip devices; specifically the GroupId's. This routine is called when the * CSIS is connected and the GroupId's are now available. */ public synchronized void updateCsipDevices() { mCsipDeviceManager.updateCsipDevices(); } /** * Attempts to get the name of a remote device, otherwise returns the address. * Loading Loading @@ -185,6 +229,16 @@ public class CachedBluetoothDeviceManager { private void clearNonBondedSubDevices() { for (int i = mCachedDevices.size() - 1; i >= 0; i--) { CachedBluetoothDevice cachedDevice = mCachedDevices.get(i); final Set<CachedBluetoothDevice> memberDevices = cachedDevice.getMemberDevice(); if (memberDevices != null) { for (CachedBluetoothDevice memberDevice : memberDevices) { // Member device exists and it is not bonded if (memberDevice.getDevice().getBondState() == BluetoothDevice.BOND_NONE) { cachedDevice.removeMemberDevice(memberDevice); } } return; } CachedBluetoothDevice subDevice = cachedDevice.getSubDevice(); if (subDevice != null && subDevice.getDevice().getBondState() == BluetoothDevice.BOND_NONE) { Loading @@ -201,6 +255,13 @@ public class CachedBluetoothDeviceManager { for (int i = mCachedDevices.size() - 1; i >= 0; i--) { CachedBluetoothDevice cachedDevice = mCachedDevices.get(i); cachedDevice.setJustDiscovered(false); final Set<CachedBluetoothDevice> memberDevices = cachedDevice.getMemberDevice(); if (memberDevices != null) { for (CachedBluetoothDevice memberDevice : memberDevices) { memberDevice.setJustDiscovered(false); } return; } final CachedBluetoothDevice subDevice = cachedDevice.getSubDevice(); if (subDevice != null) { subDevice.setJustDiscovered(false); Loading @@ -214,12 +275,21 @@ public class CachedBluetoothDeviceManager { if (bluetoothState == BluetoothAdapter.STATE_TURNING_OFF) { for (int i = mCachedDevices.size() - 1; i >= 0; i--) { CachedBluetoothDevice cachedDevice = mCachedDevices.get(i); final Set<CachedBluetoothDevice> memberDevices = cachedDevice.getMemberDevice(); if (memberDevices != null) { for (CachedBluetoothDevice memberDevice : memberDevices) { if (memberDevice.getBondState() != BluetoothDevice.BOND_BONDED) { cachedDevice.removeMemberDevice(memberDevice); } } } else { CachedBluetoothDevice subDevice = cachedDevice.getSubDevice(); if (subDevice != null) { if (subDevice.getBondState() != BluetoothDevice.BOND_BONDED) { cachedDevice.setSubDevice(null); } } } if (cachedDevice.getBondState() != BluetoothDevice.BOND_BONDED) { cachedDevice.setJustDiscovered(false); mCachedDevices.remove(i); Loading @@ -229,13 +299,32 @@ public class CachedBluetoothDeviceManager { } public synchronized boolean onProfileConnectionStateChangedIfProcessed(CachedBluetoothDevice cachedDevice, int state) { cachedDevice, int state, int profileId) { if (profileId == BluetoothProfile.HEARING_AID) { return mHearingAidDeviceManager.onProfileConnectionStateChangedIfProcessed(cachedDevice, state); } if (profileId == BluetoothProfile.CSIP_SET_COORDINATOR) { return mCsipDeviceManager.onProfileConnectionStateChangedIfProcessed(cachedDevice, state); } return false; } public synchronized void onDeviceUnpaired(CachedBluetoothDevice device) { CachedBluetoothDevice mainDevice = mHearingAidDeviceManager.findMainDevice(device); CachedBluetoothDevice mainDevice = mCsipDeviceManager.findMainDevice(device); final Set<CachedBluetoothDevice> memberDevices = device.getMemberDevice(); if (memberDevices != null) { // Main device is unpaired, to unpair the member device for (CachedBluetoothDevice memberDevice : memberDevices) { memberDevice.unpair(); device.removeMemberDevice(memberDevice); } } else if (mainDevice != null) { // the member device unpaired, to unpair main device mainDevice.unpair(); } mainDevice = mHearingAidDeviceManager.findMainDevice(device); CachedBluetoothDevice subDevice = device.getSubDevice(); if (subDevice != null) { // Main device is unpaired, to unpair sub device Loading packages/SettingsLib/src/com/android/settingslib/bluetooth/CsipDeviceManager.java 0 → 100644 +247 −0 Original line number Diff line number Diff line /* * Copyright (C) 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.settingslib.bluetooth; import android.bluetooth.BluetoothCsipSetCoordinator; import android.bluetooth.BluetoothDevice; import android.bluetooth.BluetoothProfile; import android.bluetooth.BluetoothUuid; import android.os.ParcelUuid; import android.util.Log; import com.android.internal.annotations.VisibleForTesting; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; /** * CsipDeviceManager manages the set of remote CSIP Bluetooth devices. */ public class CsipDeviceManager { private static final String TAG = "CsipDeviceManager"; private static final boolean DEBUG = BluetoothUtils.D; private final LocalBluetoothManager mBtManager; private final List<CachedBluetoothDevice> mCachedDevices; CsipDeviceManager(LocalBluetoothManager localBtManager, List<CachedBluetoothDevice> cachedDevices) { mBtManager = localBtManager; mCachedDevices = cachedDevices; }; void initCsipDeviceIfNeeded(CachedBluetoothDevice newDevice) { // Current it only supports the base uuid for CSIP and group this set in UI. final int groupId = getBaseGroupId(newDevice.getDevice()); if (isValidGroupId(groupId)) { log("initCsipDeviceIfNeeded: " + newDevice + " (group: " + groupId + ")"); // Once groupId is valid, assign groupId newDevice.setGroupId(groupId); } } private int getBaseGroupId(BluetoothDevice device) { final LocalBluetoothProfileManager profileManager = mBtManager.getProfileManager(); final CsipSetCoordinatorProfile profileProxy = profileManager .getCsipSetCoordinatorProfile(); if (profileProxy != null) { final Map<Integer, ParcelUuid> groupIdMap = profileProxy .getGroupUuidMapByDevice(device); if (groupIdMap == null) { return BluetoothCsipSetCoordinator.GROUP_ID_INVALID; } for (Map.Entry<Integer, ParcelUuid> entry: groupIdMap.entrySet()) { if (entry.getValue().equals(BluetoothUuid.BASE_UUID)) { return entry.getKey(); } } } return BluetoothCsipSetCoordinator.GROUP_ID_INVALID; } boolean setMemberDeviceIfNeeded(CachedBluetoothDevice newDevice) { final int groupId = newDevice.getGroupId(); if (isValidGroupId(groupId)) { final CachedBluetoothDevice CsipDevice = getCachedDevice(groupId); log("setMemberDeviceIfNeeded, main: " + CsipDevice + ", member: " + newDevice); // Just add one of the coordinated set from a pair in the list that is shown in the UI. // Once there is other devices with the same groupId, to add new device as member // devices. if (CsipDevice != null) { CsipDevice.setMemberDevice(newDevice); newDevice.setName(CsipDevice.getName()); return true; } } return false; } private boolean isValidGroupId(int groupId) { return groupId != BluetoothCsipSetCoordinator.GROUP_ID_INVALID; } private CachedBluetoothDevice getCachedDevice(int groupId) { for (int i = mCachedDevices.size() - 1; i >= 0; i--) { CachedBluetoothDevice cachedDevice = mCachedDevices.get(i); if (cachedDevice.getGroupId() == groupId) { return cachedDevice; } } return null; } // To collect all set member devices and call #onGroupIdChanged to group device by GroupId void updateCsipDevices() { final Set<Integer> newGroupIdSet = new HashSet<Integer>(); for (CachedBluetoothDevice cachedDevice : mCachedDevices) { // Do nothing if GroupId has been assigned if (!isValidGroupId(cachedDevice.getGroupId())) { final int newGroupId = getBaseGroupId(cachedDevice.getDevice()); // Do nothing if there is no GroupId on Bluetooth device if (isValidGroupId(newGroupId)) { cachedDevice.setGroupId(newGroupId); newGroupIdSet.add(newGroupId); } } } for (int groupId : newGroupIdSet) { onGroupIdChanged(groupId); } } // Group devices by groupId @VisibleForTesting void onGroupIdChanged(int groupId) { int firstMatchedIndex = -1; CachedBluetoothDevice mainDevice = null; for (int i = mCachedDevices.size() - 1; i >= 0; i--) { final CachedBluetoothDevice cachedDevice = mCachedDevices.get(i); if (cachedDevice.getGroupId() != groupId) { continue; } if (firstMatchedIndex == -1) { // Found the first one firstMatchedIndex = i; mainDevice = cachedDevice; continue; } log("onGroupIdChanged: removed from UI device =" + cachedDevice + ", with groupId=" + groupId + " firstMatchedIndex=" + firstMatchedIndex); mainDevice.setMemberDevice(cachedDevice); mCachedDevices.remove(i); mBtManager.getEventManager().dispatchDeviceRemoved(cachedDevice); break; } } // @return {@code true}, the event is processed inside the method. It is for updating // le audio device on group relationship when receiving connected or disconnected. // @return {@code false}, it is not le audio device or to process it same as other profiles boolean onProfileConnectionStateChangedIfProcessed(CachedBluetoothDevice cachedDevice, int state) { log("onProfileConnectionStateChangedIfProcessed: " + cachedDevice + ", state: " + state); switch (state) { case BluetoothProfile.STATE_CONNECTED: onGroupIdChanged(cachedDevice.getGroupId()); CachedBluetoothDevice mainDevice = findMainDevice(cachedDevice); if (mainDevice != null) { if (mainDevice.isConnected()) { // When main device exists and in connected state, receiving member device // connection. To refresh main device UI mainDevice.refresh(); return true; } else { // When both LE Audio devices are disconnected, receiving member device // connection. To switch content and dispatch to notify UI change mBtManager.getEventManager().dispatchDeviceRemoved(mainDevice); mainDevice.switchMemberDeviceContent(mainDevice, cachedDevice); mainDevice.refresh(); // It is necessary to do remove and add for updating the mapping on // preference and device mBtManager.getEventManager().dispatchDeviceAdded(mainDevice); return true; } } break; case BluetoothProfile.STATE_DISCONNECTED: mainDevice = findMainDevice(cachedDevice); if (mainDevice != null) { // When main device exists, receiving sub device disconnection // To update main device UI mainDevice.refresh(); return true; } final Set<CachedBluetoothDevice> memberSet = cachedDevice.getMemberDevice(); if (memberSet == null) { break; } for (CachedBluetoothDevice device: memberSet) { if (device.isConnected()) { // Main device is disconnected and sub device is connected // To copy data from sub device to main device mBtManager.getEventManager().dispatchDeviceRemoved(cachedDevice); cachedDevice.switchMemberDeviceContent(device, cachedDevice); cachedDevice.refresh(); // It is necessary to do remove and add for updating the mapping on // preference and device mBtManager.getEventManager().dispatchDeviceAdded(cachedDevice); return true; } } break; default: // Do not handle this state. } return false; } CachedBluetoothDevice findMainDevice(CachedBluetoothDevice device) { if (device == null || mCachedDevices == null) { return null; } for (CachedBluetoothDevice cachedDevice : mCachedDevices) { if (isValidGroupId(cachedDevice.getGroupId())) { Set<CachedBluetoothDevice> memberSet = cachedDevice.getMemberDevice(); if (memberSet == null) { continue; } for (CachedBluetoothDevice memberDevice: memberSet) { if (memberDevice != null && memberDevice.equals(device)) { return cachedDevice; } } } } return null; } private void log(String msg) { if (DEBUG) { Log.d(TAG, msg); } } } packages/SettingsLib/src/com/android/settingslib/bluetooth/CsipSetCoordinatorProfile.java +15 −0 File changed.Preview size limit exceeded, changes collapsed. Show changes Loading
packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothEventManager.java +5 −2 Original line number Diff line number Diff line Loading @@ -18,6 +18,7 @@ package com.android.settingslib.bluetooth; import android.bluetooth.BluetoothA2dp; import android.bluetooth.BluetoothAdapter; import android.bluetooth.BluetoothCsipSetCoordinator; import android.bluetooth.BluetoothDevice; import android.bluetooth.BluetoothHeadset; import android.bluetooth.BluetoothHearingAid; Loading Loading @@ -351,8 +352,10 @@ public class BluetoothEventManager { cachedDevice.onBondingStateChanged(bondState); if (bondState == BluetoothDevice.BOND_NONE) { /* Check if we need to remove other Hearing Aid devices */ if (cachedDevice.getHiSyncId() != BluetoothHearingAid.HI_SYNC_ID_INVALID) { // Check if we need to remove other Coordinated set member devices / Hearing Aid // devices if (cachedDevice.getGroupId() != BluetoothCsipSetCoordinator.GROUP_ID_INVALID || cachedDevice.getHiSyncId() != BluetoothHearingAid.HI_SYNC_ID_INVALID) { mDeviceManager.onDeviceUnpaired(cachedDevice); } int reason = intent.getIntExtra(BluetoothDevice.EXTRA_REASON, Loading
packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java +73 −0 Original line number Diff line number Diff line Loading @@ -18,6 +18,7 @@ package com.android.settingslib.bluetooth; import android.bluetooth.BluetoothAdapter; import android.bluetooth.BluetoothClass; import android.bluetooth.BluetoothCsipSetCoordinator; import android.bluetooth.BluetoothDevice; import android.bluetooth.BluetoothHearingAid; import android.bluetooth.BluetoothProfile; Loading @@ -41,7 +42,9 @@ import com.android.settingslib.Utils; import java.util.ArrayList; import java.util.Collection; import java.util.HashSet; import java.util.List; import java.util.Set; import java.util.concurrent.CopyOnWriteArrayList; /** Loading @@ -66,6 +69,7 @@ public class CachedBluetoothDevice implements Comparable<CachedBluetoothDevice> private final Object mProfileLock = new Object(); BluetoothDevice mDevice; private long mHiSyncId; private int mGroupId; // Need this since there is no method for getting RSSI short mRssi; // mProfiles and mRemovedProfiles does not do swap() between main and sub device. It is Loading Loading @@ -100,6 +104,8 @@ public class CachedBluetoothDevice implements Comparable<CachedBluetoothDevice> private boolean mIsA2dpProfileConnectedFail = false; private boolean mIsHeadsetProfileConnectedFail = false; private boolean mIsHearingAidProfileConnectedFail = false; // Group member devices for the coordinated set private Set<CachedBluetoothDevice> mMemberDevices = new HashSet<CachedBluetoothDevice>(); // Group second device for Hearing Aid private CachedBluetoothDevice mSubDevice; Loading Loading @@ -133,6 +139,7 @@ public class CachedBluetoothDevice implements Comparable<CachedBluetoothDevice> mDevice = device; fillData(); mHiSyncId = BluetoothHearingAid.HI_SYNC_ID_INVALID; mGroupId = BluetoothCsipSetCoordinator.GROUP_ID_INVALID; } /** Loading Loading @@ -317,6 +324,24 @@ public class CachedBluetoothDevice implements Comparable<CachedBluetoothDevice> return mIsCoordinatedSetMember; } /** * Get the coordinated set group id. * * @return the group id. */ public int getGroupId() { return mGroupId; } /** * Set the coordinated set group id. * * @param id the group id from the CSIP. */ public void setGroupId(int id) { mGroupId = id; } void onBondingDockConnect() { // Attempt to connect if UUIDs are available. Otherwise, // we will connect when the ACTION_UUID intent arrives. Loading Loading @@ -1191,4 +1216,52 @@ public class CachedBluetoothDevice implements Comparable<CachedBluetoothDevice> mSubDevice.mJustDiscovered = tmpJustDiscovered; fetchActiveDevices(); } /** * @return a set of member devices that are in the same coordinated set with this device. */ public Set<CachedBluetoothDevice> getMemberDevice() { return mMemberDevices; } /** * Store the member devices that are in the same coordinated set. */ public void setMemberDevice(CachedBluetoothDevice memberDevice) { mMemberDevices.add(memberDevice); } /** * Remove a device from the member device sets. */ public void removeMemberDevice(CachedBluetoothDevice memberDevice) { mMemberDevices.remove(memberDevice); } /** * In order to show the preference for the whole group, we always set the main device as the * first connected device in the coordinated set, and then switch the content of the main * device and member devices. * * @param prevMainDevice the previous Main device, it will be added into the member device set. * @param newMainDevie the new Main device, it will be removed from the member device set. */ public void switchMemberDeviceContent(CachedBluetoothDevice prevMainDevice, CachedBluetoothDevice newMainDevie) { // Backup from main device final BluetoothDevice tmpDevice = mDevice; final short tmpRssi = mRssi; final boolean tmpJustDiscovered = mJustDiscovered; // Set main device from sub device mDevice = newMainDevie.mDevice; mRssi = newMainDevie.mRssi; mJustDiscovered = newMainDevie.mJustDiscovered; setMemberDevice(prevMainDevice); mMemberDevices.remove(newMainDevie); // Set sub device from backup newMainDevie.mDevice = tmpDevice; newMainDevie.mRssi = tmpRssi; newMainDevie.mJustDiscovered = tmpJustDiscovered; fetchActiveDevices(); } }
packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceManager.java +103 −14 Original line number Diff line number Diff line Loading @@ -18,6 +18,7 @@ package com.android.settingslib.bluetooth; import android.bluetooth.BluetoothAdapter; import android.bluetooth.BluetoothDevice; import android.bluetooth.BluetoothProfile; import android.content.Context; import android.util.Log; Loading @@ -26,6 +27,7 @@ import com.android.internal.annotations.VisibleForTesting; import java.util.ArrayList; import java.util.Collection; import java.util.List; import java.util.Set; /** * CachedBluetoothDeviceManager manages the set of remote Bluetooth devices. Loading @@ -41,11 +43,14 @@ public class CachedBluetoothDeviceManager { final List<CachedBluetoothDevice> mCachedDevices = new ArrayList<CachedBluetoothDevice>(); @VisibleForTesting HearingAidDeviceManager mHearingAidDeviceManager; @VisibleForTesting CsipDeviceManager mCsipDeviceManager; CachedBluetoothDeviceManager(Context context, LocalBluetoothManager localBtManager) { mContext = context; mBtManager = localBtManager; mHearingAidDeviceManager = new HearingAidDeviceManager(localBtManager, mCachedDevices); mCsipDeviceManager = new CsipDeviceManager(localBtManager, mCachedDevices); } public synchronized Collection<CachedBluetoothDevice> getCachedDevicesCopy() { Loading Loading @@ -79,7 +84,16 @@ public class CachedBluetoothDeviceManager { if (cachedDevice.getDevice().equals(device)) { return cachedDevice; } // Check sub devices if it exists // Check the member devices for the coordinated set if it exists final Set<CachedBluetoothDevice> memberDevices = cachedDevice.getMemberDevice(); if (memberDevices != null) { for (CachedBluetoothDevice memberDevice : memberDevices) { if (memberDevice.getDevice().equals(device)) { return memberDevice; } } } // Check sub devices for hearing aid if it exists CachedBluetoothDevice subDevice = cachedDevice.getSubDevice(); if (subDevice != null && subDevice.getDevice().equals(device)) { return subDevice; Loading @@ -102,8 +116,10 @@ public class CachedBluetoothDeviceManager { newDevice = findDevice(device); if (newDevice == null) { newDevice = new CachedBluetoothDevice(mContext, profileManager, device); mCsipDeviceManager.initCsipDeviceIfNeeded(newDevice); mHearingAidDeviceManager.initHearingAidDeviceIfNeeded(newDevice); if (!mHearingAidDeviceManager.setSubDeviceIfNeeded(newDevice)) { if (!mCsipDeviceManager.setMemberDeviceIfNeeded(newDevice) && !mHearingAidDeviceManager.setSubDeviceIfNeeded(newDevice)) { mCachedDevices.add(newDevice); mBtManager.getEventManager().dispatchDeviceAdded(newDevice); } Loading @@ -114,13 +130,23 @@ public class CachedBluetoothDeviceManager { } /** * Returns device summary of the pair of the hearing aid passed as the parameter. * Returns device summary of the pair of the hearing aid / CSIP passed as the parameter. * * @param CachedBluetoothDevice device * @return Device summary, or if the pair does not exist or if it is not a hearing aid, * then {@code null}. * @return Device summary, or if the pair does not exist or if it is not a hearing aid or * a CSIP set member, then {@code null}. */ public synchronized String getSubDeviceSummary(CachedBluetoothDevice device) { final Set<CachedBluetoothDevice> memberDevices = device.getMemberDevice(); if (memberDevices != null) { for (CachedBluetoothDevice memberDevice : memberDevices) { if (!memberDevice.isConnected()) { return null; } } return device.getConnectionSummary(); } CachedBluetoothDevice subDevice = device.getSubDevice(); if (subDevice != null && subDevice.isConnected()) { return subDevice.getConnectionSummary(); Loading @@ -132,12 +158,22 @@ public class CachedBluetoothDeviceManager { * Search for existing sub device {@link CachedBluetoothDevice}. * * @param device the address of the Bluetooth device * @return true for found sub device or false. * @return true for found sub / member device or false. */ public synchronized boolean isSubDevice(BluetoothDevice device) { for (CachedBluetoothDevice cachedDevice : mCachedDevices) { if (!cachedDevice.getDevice().equals(device)) { // Check sub devices if it exists // Check the member devices of the coordinated set if it exists Set<CachedBluetoothDevice> memberDevices = cachedDevice.getMemberDevice(); if (memberDevices != null) { for (CachedBluetoothDevice memberDevice : memberDevices) { if (memberDevice.getDevice().equals(device)) { return true; } } continue; } // Check sub devices of hearing aid if it exists CachedBluetoothDevice subDevice = cachedDevice.getSubDevice(); if (subDevice != null && subDevice.getDevice().equals(device)) { return true; Loading @@ -156,6 +192,14 @@ public class CachedBluetoothDeviceManager { mHearingAidDeviceManager.updateHearingAidsDevices(); } /** * Updates the Csip devices; specifically the GroupId's. This routine is called when the * CSIS is connected and the GroupId's are now available. */ public synchronized void updateCsipDevices() { mCsipDeviceManager.updateCsipDevices(); } /** * Attempts to get the name of a remote device, otherwise returns the address. * Loading Loading @@ -185,6 +229,16 @@ public class CachedBluetoothDeviceManager { private void clearNonBondedSubDevices() { for (int i = mCachedDevices.size() - 1; i >= 0; i--) { CachedBluetoothDevice cachedDevice = mCachedDevices.get(i); final Set<CachedBluetoothDevice> memberDevices = cachedDevice.getMemberDevice(); if (memberDevices != null) { for (CachedBluetoothDevice memberDevice : memberDevices) { // Member device exists and it is not bonded if (memberDevice.getDevice().getBondState() == BluetoothDevice.BOND_NONE) { cachedDevice.removeMemberDevice(memberDevice); } } return; } CachedBluetoothDevice subDevice = cachedDevice.getSubDevice(); if (subDevice != null && subDevice.getDevice().getBondState() == BluetoothDevice.BOND_NONE) { Loading @@ -201,6 +255,13 @@ public class CachedBluetoothDeviceManager { for (int i = mCachedDevices.size() - 1; i >= 0; i--) { CachedBluetoothDevice cachedDevice = mCachedDevices.get(i); cachedDevice.setJustDiscovered(false); final Set<CachedBluetoothDevice> memberDevices = cachedDevice.getMemberDevice(); if (memberDevices != null) { for (CachedBluetoothDevice memberDevice : memberDevices) { memberDevice.setJustDiscovered(false); } return; } final CachedBluetoothDevice subDevice = cachedDevice.getSubDevice(); if (subDevice != null) { subDevice.setJustDiscovered(false); Loading @@ -214,12 +275,21 @@ public class CachedBluetoothDeviceManager { if (bluetoothState == BluetoothAdapter.STATE_TURNING_OFF) { for (int i = mCachedDevices.size() - 1; i >= 0; i--) { CachedBluetoothDevice cachedDevice = mCachedDevices.get(i); final Set<CachedBluetoothDevice> memberDevices = cachedDevice.getMemberDevice(); if (memberDevices != null) { for (CachedBluetoothDevice memberDevice : memberDevices) { if (memberDevice.getBondState() != BluetoothDevice.BOND_BONDED) { cachedDevice.removeMemberDevice(memberDevice); } } } else { CachedBluetoothDevice subDevice = cachedDevice.getSubDevice(); if (subDevice != null) { if (subDevice.getBondState() != BluetoothDevice.BOND_BONDED) { cachedDevice.setSubDevice(null); } } } if (cachedDevice.getBondState() != BluetoothDevice.BOND_BONDED) { cachedDevice.setJustDiscovered(false); mCachedDevices.remove(i); Loading @@ -229,13 +299,32 @@ public class CachedBluetoothDeviceManager { } public synchronized boolean onProfileConnectionStateChangedIfProcessed(CachedBluetoothDevice cachedDevice, int state) { cachedDevice, int state, int profileId) { if (profileId == BluetoothProfile.HEARING_AID) { return mHearingAidDeviceManager.onProfileConnectionStateChangedIfProcessed(cachedDevice, state); } if (profileId == BluetoothProfile.CSIP_SET_COORDINATOR) { return mCsipDeviceManager.onProfileConnectionStateChangedIfProcessed(cachedDevice, state); } return false; } public synchronized void onDeviceUnpaired(CachedBluetoothDevice device) { CachedBluetoothDevice mainDevice = mHearingAidDeviceManager.findMainDevice(device); CachedBluetoothDevice mainDevice = mCsipDeviceManager.findMainDevice(device); final Set<CachedBluetoothDevice> memberDevices = device.getMemberDevice(); if (memberDevices != null) { // Main device is unpaired, to unpair the member device for (CachedBluetoothDevice memberDevice : memberDevices) { memberDevice.unpair(); device.removeMemberDevice(memberDevice); } } else if (mainDevice != null) { // the member device unpaired, to unpair main device mainDevice.unpair(); } mainDevice = mHearingAidDeviceManager.findMainDevice(device); CachedBluetoothDevice subDevice = device.getSubDevice(); if (subDevice != null) { // Main device is unpaired, to unpair sub device Loading
packages/SettingsLib/src/com/android/settingslib/bluetooth/CsipDeviceManager.java 0 → 100644 +247 −0 Original line number Diff line number Diff line /* * Copyright (C) 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.settingslib.bluetooth; import android.bluetooth.BluetoothCsipSetCoordinator; import android.bluetooth.BluetoothDevice; import android.bluetooth.BluetoothProfile; import android.bluetooth.BluetoothUuid; import android.os.ParcelUuid; import android.util.Log; import com.android.internal.annotations.VisibleForTesting; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; /** * CsipDeviceManager manages the set of remote CSIP Bluetooth devices. */ public class CsipDeviceManager { private static final String TAG = "CsipDeviceManager"; private static final boolean DEBUG = BluetoothUtils.D; private final LocalBluetoothManager mBtManager; private final List<CachedBluetoothDevice> mCachedDevices; CsipDeviceManager(LocalBluetoothManager localBtManager, List<CachedBluetoothDevice> cachedDevices) { mBtManager = localBtManager; mCachedDevices = cachedDevices; }; void initCsipDeviceIfNeeded(CachedBluetoothDevice newDevice) { // Current it only supports the base uuid for CSIP and group this set in UI. final int groupId = getBaseGroupId(newDevice.getDevice()); if (isValidGroupId(groupId)) { log("initCsipDeviceIfNeeded: " + newDevice + " (group: " + groupId + ")"); // Once groupId is valid, assign groupId newDevice.setGroupId(groupId); } } private int getBaseGroupId(BluetoothDevice device) { final LocalBluetoothProfileManager profileManager = mBtManager.getProfileManager(); final CsipSetCoordinatorProfile profileProxy = profileManager .getCsipSetCoordinatorProfile(); if (profileProxy != null) { final Map<Integer, ParcelUuid> groupIdMap = profileProxy .getGroupUuidMapByDevice(device); if (groupIdMap == null) { return BluetoothCsipSetCoordinator.GROUP_ID_INVALID; } for (Map.Entry<Integer, ParcelUuid> entry: groupIdMap.entrySet()) { if (entry.getValue().equals(BluetoothUuid.BASE_UUID)) { return entry.getKey(); } } } return BluetoothCsipSetCoordinator.GROUP_ID_INVALID; } boolean setMemberDeviceIfNeeded(CachedBluetoothDevice newDevice) { final int groupId = newDevice.getGroupId(); if (isValidGroupId(groupId)) { final CachedBluetoothDevice CsipDevice = getCachedDevice(groupId); log("setMemberDeviceIfNeeded, main: " + CsipDevice + ", member: " + newDevice); // Just add one of the coordinated set from a pair in the list that is shown in the UI. // Once there is other devices with the same groupId, to add new device as member // devices. if (CsipDevice != null) { CsipDevice.setMemberDevice(newDevice); newDevice.setName(CsipDevice.getName()); return true; } } return false; } private boolean isValidGroupId(int groupId) { return groupId != BluetoothCsipSetCoordinator.GROUP_ID_INVALID; } private CachedBluetoothDevice getCachedDevice(int groupId) { for (int i = mCachedDevices.size() - 1; i >= 0; i--) { CachedBluetoothDevice cachedDevice = mCachedDevices.get(i); if (cachedDevice.getGroupId() == groupId) { return cachedDevice; } } return null; } // To collect all set member devices and call #onGroupIdChanged to group device by GroupId void updateCsipDevices() { final Set<Integer> newGroupIdSet = new HashSet<Integer>(); for (CachedBluetoothDevice cachedDevice : mCachedDevices) { // Do nothing if GroupId has been assigned if (!isValidGroupId(cachedDevice.getGroupId())) { final int newGroupId = getBaseGroupId(cachedDevice.getDevice()); // Do nothing if there is no GroupId on Bluetooth device if (isValidGroupId(newGroupId)) { cachedDevice.setGroupId(newGroupId); newGroupIdSet.add(newGroupId); } } } for (int groupId : newGroupIdSet) { onGroupIdChanged(groupId); } } // Group devices by groupId @VisibleForTesting void onGroupIdChanged(int groupId) { int firstMatchedIndex = -1; CachedBluetoothDevice mainDevice = null; for (int i = mCachedDevices.size() - 1; i >= 0; i--) { final CachedBluetoothDevice cachedDevice = mCachedDevices.get(i); if (cachedDevice.getGroupId() != groupId) { continue; } if (firstMatchedIndex == -1) { // Found the first one firstMatchedIndex = i; mainDevice = cachedDevice; continue; } log("onGroupIdChanged: removed from UI device =" + cachedDevice + ", with groupId=" + groupId + " firstMatchedIndex=" + firstMatchedIndex); mainDevice.setMemberDevice(cachedDevice); mCachedDevices.remove(i); mBtManager.getEventManager().dispatchDeviceRemoved(cachedDevice); break; } } // @return {@code true}, the event is processed inside the method. It is for updating // le audio device on group relationship when receiving connected or disconnected. // @return {@code false}, it is not le audio device or to process it same as other profiles boolean onProfileConnectionStateChangedIfProcessed(CachedBluetoothDevice cachedDevice, int state) { log("onProfileConnectionStateChangedIfProcessed: " + cachedDevice + ", state: " + state); switch (state) { case BluetoothProfile.STATE_CONNECTED: onGroupIdChanged(cachedDevice.getGroupId()); CachedBluetoothDevice mainDevice = findMainDevice(cachedDevice); if (mainDevice != null) { if (mainDevice.isConnected()) { // When main device exists and in connected state, receiving member device // connection. To refresh main device UI mainDevice.refresh(); return true; } else { // When both LE Audio devices are disconnected, receiving member device // connection. To switch content and dispatch to notify UI change mBtManager.getEventManager().dispatchDeviceRemoved(mainDevice); mainDevice.switchMemberDeviceContent(mainDevice, cachedDevice); mainDevice.refresh(); // It is necessary to do remove and add for updating the mapping on // preference and device mBtManager.getEventManager().dispatchDeviceAdded(mainDevice); return true; } } break; case BluetoothProfile.STATE_DISCONNECTED: mainDevice = findMainDevice(cachedDevice); if (mainDevice != null) { // When main device exists, receiving sub device disconnection // To update main device UI mainDevice.refresh(); return true; } final Set<CachedBluetoothDevice> memberSet = cachedDevice.getMemberDevice(); if (memberSet == null) { break; } for (CachedBluetoothDevice device: memberSet) { if (device.isConnected()) { // Main device is disconnected and sub device is connected // To copy data from sub device to main device mBtManager.getEventManager().dispatchDeviceRemoved(cachedDevice); cachedDevice.switchMemberDeviceContent(device, cachedDevice); cachedDevice.refresh(); // It is necessary to do remove and add for updating the mapping on // preference and device mBtManager.getEventManager().dispatchDeviceAdded(cachedDevice); return true; } } break; default: // Do not handle this state. } return false; } CachedBluetoothDevice findMainDevice(CachedBluetoothDevice device) { if (device == null || mCachedDevices == null) { return null; } for (CachedBluetoothDevice cachedDevice : mCachedDevices) { if (isValidGroupId(cachedDevice.getGroupId())) { Set<CachedBluetoothDevice> memberSet = cachedDevice.getMemberDevice(); if (memberSet == null) { continue; } for (CachedBluetoothDevice memberDevice: memberSet) { if (memberDevice != null && memberDevice.equals(device)) { return cachedDevice; } } } } return null; } private void log(String msg) { if (DEBUG) { Log.d(TAG, msg); } } }
packages/SettingsLib/src/com/android/settingslib/bluetooth/CsipSetCoordinatorProfile.java +15 −0 File changed.Preview size limit exceeded, changes collapsed. Show changes