diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceManager.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceManager.java index 7b4c86207a2a3..67e3e036cc3e5 100644 --- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceManager.java +++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceManager.java @@ -318,7 +318,10 @@ public class CachedBluetoothDeviceManager { return mHearingAidDeviceManager.onProfileConnectionStateChangedIfProcessed(cachedDevice, state); } - if (profileId == BluetoothProfile.CSIP_SET_COORDINATOR) { + if (profileId == BluetoothProfile.HEADSET + || profileId == BluetoothProfile.A2DP + || profileId == BluetoothProfile.LE_AUDIO + || profileId == BluetoothProfile.CSIP_SET_COORDINATOR) { return mCsipDeviceManager.onProfileConnectionStateChangedIfProcessed(cachedDevice, state); } diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CsipDeviceManager.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CsipDeviceManager.java index 356bb82a92e09..8269b56c425bb 100644 --- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CsipDeviceManager.java +++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CsipDeviceManager.java @@ -28,6 +28,7 @@ import androidx.annotation.ChecksSdkIntAtLeast; import com.android.internal.annotations.VisibleForTesting; +import java.util.ArrayList; import java.util.HashSet; import java.util.List; import java.util.Map; @@ -83,14 +84,14 @@ public class CsipDeviceManager { boolean setMemberDeviceIfNeeded(CachedBluetoothDevice newDevice) { final int groupId = newDevice.getGroupId(); if (isValidGroupId(groupId)) { - final CachedBluetoothDevice CsipDevice = getCachedDevice(groupId); - log("setMemberDeviceIfNeeded, main: " + CsipDevice + ", member: " + newDevice); + final CachedBluetoothDevice mainDevice = getCachedDevice(groupId); + log("setMemberDeviceIfNeeded, main: " + mainDevice + ", 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.addMemberDevice(newDevice); - newDevice.setName(CsipDevice.getName()); + if (mainDevice != null) { + mainDevice.addMemberDevice(newDevice); + newDevice.setName(mainDevice.getName()); return true; } } @@ -152,14 +153,7 @@ public class CsipDeviceManager { log("onGroupIdChanged: groupId is invalid"); return; } - log("onGroupIdChanged: mCachedDevices list =" + mCachedDevices.toString()); - List memberDevicesList = getMemberDevicesList(groupId); - CachedBluetoothDevice newMainDevice = - getPreferredMainDeviceWithoutConectionState(groupId, memberDevicesList); - - log("onGroupIdChanged: The mainDevice= " + newMainDevice - + " and the memberDevicesList of groupId= " + groupId + " =" + memberDevicesList); - addMemberDevicesIntoMainDevice(memberDevicesList, newMainDevice); + updateRelationshipOfGroupDevices(groupId); } // @return {@code true}, the event is processed inside the method. It is for updating @@ -168,61 +162,30 @@ public class CsipDeviceManager { 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(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 memberSet = cachedDevice.getMemberDevice(); - if (memberSet.isEmpty()) { - break; - } - for (CachedBluetoothDevice device : memberSet) { - if (device.isConnected()) { - log("set device: " + device + " as the main device"); - // 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.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. + if (state != BluetoothProfile.STATE_CONNECTED + && state != BluetoothProfile.STATE_DISCONNECTED) { + return false; } - return false; + return updateRelationshipOfGroupDevices(cachedDevice.getGroupId()); + } + + @VisibleForTesting + boolean updateRelationshipOfGroupDevices(int groupId) { + if (!isValidGroupId(groupId)) { + log("The device is not group."); + return false; + } + log("updateRelationshipOfGroupDevices: mCachedDevices list =" + mCachedDevices.toString()); + + // Get the preferred main device by getPreferredMainDeviceWithoutConectionState + List groupDevicesList = getGroupDevicesFromAllOfDevicesList(groupId); + CachedBluetoothDevice preferredMainDevice = + getPreferredMainDevice(groupId, groupDevicesList); + log("The preferredMainDevice= " + preferredMainDevice + + " and the groupDevicesList of groupId= " + groupId + + " =" + groupDevicesList); + return addMemberDevicesIntoMainDevice(groupId, preferredMainDevice); } CachedBluetoothDevice findMainDevice(CachedBluetoothDevice device) { @@ -262,114 +225,153 @@ public class CsipDeviceManager { return false; } - private List getMemberDevicesList(int groupId) { - return mCachedDevices.stream() - .filter(cacheDevice -> cacheDevice.getGroupId() == groupId) - .collect(Collectors.toList()); + @VisibleForTesting + List getGroupDevicesFromAllOfDevicesList(int groupId) { + List groupDevicesList = new ArrayList<>(); + if (!isValidGroupId(groupId)) { + return groupDevicesList; + } + for (CachedBluetoothDevice item : mCachedDevices) { + if (groupId != item.getGroupId()) { + continue; + } + groupDevicesList.add(item); + groupDevicesList.addAll(item.getMemberDevice()); + } + return groupDevicesList; } - private CachedBluetoothDevice getPreferredMainDeviceWithoutConectionState(int groupId, - List memberDevicesList) { - // First, priority connected lead device from LE profile - // Second, the DUAL mode device which has A2DP/HFP and LE audio - // Last, any one of LE device in the list. - if (memberDevicesList == null || memberDevicesList.isEmpty()) { + @VisibleForTesting + CachedBluetoothDevice getPreferredMainDevice(int groupId, + List groupDevicesList) { + // How to select the preferred main device? + // 1. The DUAL mode connected device which has A2DP/HFP and LE audio. + // 2. One of connected LE device in the list. Default is the lead device from LE profile. + // 3. If there is no connected device, then reset the relationship. Set the DUAL mode + // deviced as the main device. Otherwise, set any one of the device. + if (groupDevicesList == null || groupDevicesList.isEmpty()) { return null; } + CachedBluetoothDevice dualModeDevice = groupDevicesList.stream() + .filter(cachedDevice -> cachedDevice.getConnectableProfiles().stream() + .anyMatch(profile -> profile instanceof LeAudioProfile)) + .filter(cachedDevice -> cachedDevice.getConnectableProfiles().stream() + .anyMatch(profile -> profile instanceof A2dpProfile + || profile instanceof HeadsetProfile)) + .findFirst().orElse(null); + if (dualModeDevice != null && dualModeDevice.isConnected()) { + log("getPreferredMainDevice: The connected DUAL mode device"); + return dualModeDevice; + } + final LocalBluetoothProfileManager profileManager = mBtManager.getProfileManager(); final CachedBluetoothDeviceManager deviceManager = mBtManager.getCachedDeviceManager(); final LeAudioProfile leAudioProfile = profileManager.getLeAudioProfile(); - final BluetoothDevice mainBluetoothDevice = (leAudioProfile != null && isAtLeastT()) + final BluetoothDevice leAudioLeadDevice = (leAudioProfile != null && isAtLeastT()) ? leAudioProfile.getConnectedGroupLeadDevice(groupId) : null; - if (mainBluetoothDevice != null) { + if (leAudioLeadDevice != null) { log("getPreferredMainDevice: The LeadDevice from LE profile is " - + mainBluetoothDevice.getAnonymizedAddress()); + + leAudioLeadDevice.getAnonymizedAddress()); } - - // 1st - CachedBluetoothDevice newMainDevice = - mainBluetoothDevice != null ? deviceManager.findDevice(mainBluetoothDevice) : null; - if (newMainDevice != null) { - if (newMainDevice.isConnected()) { - log("getPreferredMainDevice: The connected LeadDevice from LE profile"); - return newMainDevice; - } else { - log("getPreferredMainDevice: The LeadDevice is not connect."); - } - } else { + CachedBluetoothDevice leAudioLeadCachedDevice = + leAudioLeadDevice != null ? deviceManager.findDevice(leAudioLeadDevice) : null; + if (leAudioLeadCachedDevice == null) { log("getPreferredMainDevice: The LeadDevice is not in the all of devices list"); + } else if (leAudioLeadCachedDevice.isConnected()) { + log("getPreferredMainDevice: The connected LeadDevice from LE profile"); + return leAudioLeadCachedDevice; } - - // 2nd - newMainDevice = memberDevicesList.stream() - .filter(cachedDevice -> cachedDevice.getConnectableProfiles().stream() - .anyMatch(profile -> profile instanceof A2dpProfile - || profile instanceof HeadsetProfile)) + CachedBluetoothDevice oneOfConnectedDevices = groupDevicesList.stream() + .filter(cachedDevice -> cachedDevice.isConnected()) .findFirst().orElse(null); - if (newMainDevice != null) { - log("getPreferredMainDevice: The DUAL mode device"); - return newMainDevice; + if (oneOfConnectedDevices != null) { + log("getPreferredMainDevice: One of the connected devices."); + return oneOfConnectedDevices; } - // last - if (!memberDevicesList.isEmpty()) { - newMainDevice = memberDevicesList.get(0); + if (dualModeDevice != null) { + log("getPreferredMainDevice: The DUAL mode device."); + return dualModeDevice; } - return newMainDevice; + // last + if (!groupDevicesList.isEmpty()) { + log("getPreferredMainDevice: One of the group devices."); + return groupDevicesList.get(0); + } + return null; } - private void addMemberDevicesIntoMainDevice(List memberDevicesList, - CachedBluetoothDevice newMainDevice) { - if (newMainDevice == null) { + @VisibleForTesting + boolean addMemberDevicesIntoMainDevice(int groupId, CachedBluetoothDevice preferredMainDevice) { + boolean hasChanged = false; + if (preferredMainDevice == null) { log("addMemberDevicesIntoMainDevice: No main device. Do nothing."); - return; + return hasChanged; } - if (memberDevicesList.isEmpty()) { - log("addMemberDevicesIntoMainDevice: No member device in list. Do nothing."); - return; - } - CachedBluetoothDevice mainDeviceOfNewMainDevice = findMainDevice(newMainDevice); - boolean isMemberInOtherMainDevice = mainDeviceOfNewMainDevice != null; - if (!memberDevicesList.contains(newMainDevice) && isMemberInOtherMainDevice) { - log("addMemberDevicesIntoMainDevice: The 'new main device' is not in list, and it is " - + "the member at other device. Do switch main and member."); + + // If the current main device is not preferred main device, then set it as new main device. + // Otherwise, do nothing. + BluetoothDevice bluetoothDeviceOfPreferredMainDevice = preferredMainDevice.getDevice(); + CachedBluetoothDevice mainDeviceOfPreferredMainDevice = findMainDevice(preferredMainDevice); + boolean hasPreferredMainDeviceAlreadyBeenMainDevice = + mainDeviceOfPreferredMainDevice == null; + + if (!hasPreferredMainDeviceAlreadyBeenMainDevice) { + // preferredMainDevice has not been the main device. + // switch relationship between the mainDeviceOfPreferredMainDevice and + // PreferredMainDevice + + log("addMemberDevicesIntoMainDevice: The PreferredMainDevice have the mainDevice. " + + "Do switch relationship between the mainDeviceOfPreferredMainDevice and " + + "PreferredMainDevice"); // To switch content and dispatch to notify UI change - mBtManager.getEventManager().dispatchDeviceRemoved(mainDeviceOfNewMainDevice); - mainDeviceOfNewMainDevice.switchMemberDeviceContent(newMainDevice); - mainDeviceOfNewMainDevice.refresh(); + mBtManager.getEventManager().dispatchDeviceRemoved(mainDeviceOfPreferredMainDevice); + mainDeviceOfPreferredMainDevice.switchMemberDeviceContent(preferredMainDevice); + mainDeviceOfPreferredMainDevice.refresh(); // It is necessary to do remove and add for updating the mapping on // preference and device - mBtManager.getEventManager().dispatchDeviceAdded(mainDeviceOfNewMainDevice); - } else { - log("addMemberDevicesIntoMainDevice: Set new main device"); - for (CachedBluetoothDevice memberDeviceItem : memberDevicesList) { - if (memberDeviceItem.equals(newMainDevice)) { + mBtManager.getEventManager().dispatchDeviceAdded(mainDeviceOfPreferredMainDevice); + hasChanged = true; + } + + // If the mCachedDevices List at CachedBluetoothDeviceManager has multiple items which are + // the same groupId, then combine them and also keep the preferred main device as main + // device. + List topLevelOfGroupDevicesList = mCachedDevices.stream() + .filter(device -> device.getGroupId() == groupId) + .collect(Collectors.toList()); + boolean haveMultiMainDevicesInAllOfDevicesList = topLevelOfGroupDevicesList.size() > 1; + // Update the new main of CachedBluetoothDevice, since it may be changed in above step. + final CachedBluetoothDeviceManager deviceManager = mBtManager.getCachedDeviceManager(); + preferredMainDevice = deviceManager.findDevice(bluetoothDeviceOfPreferredMainDevice); + if (haveMultiMainDevicesInAllOfDevicesList) { + // put another devices into main device. + for (CachedBluetoothDevice deviceItem : topLevelOfGroupDevicesList) { + if (deviceItem.getDevice() == null || deviceItem.getDevice().equals( + bluetoothDeviceOfPreferredMainDevice)) { continue; } - Set memberSet = memberDeviceItem.getMemberDevice(); - if (!memberSet.isEmpty()) { - for (CachedBluetoothDevice memberSetItem : memberSet) { - if (!memberSetItem.equals(newMainDevice)) { - newMainDevice.addMemberDevice(memberSetItem); - } + + Set memberSet = deviceItem.getMemberDevice(); + for (CachedBluetoothDevice memberSetItem : memberSet) { + if (!memberSetItem.equals(preferredMainDevice)) { + preferredMainDevice.addMemberDevice(memberSetItem); } - memberSet.clear(); } - - newMainDevice.addMemberDevice(memberDeviceItem); - mCachedDevices.remove(memberDeviceItem); - mBtManager.getEventManager().dispatchDeviceRemoved(memberDeviceItem); - } - - if (!mCachedDevices.contains(newMainDevice)) { - mCachedDevices.add(newMainDevice); - mBtManager.getEventManager().dispatchDeviceAdded(newMainDevice); + memberSet.clear(); + preferredMainDevice.addMemberDevice(deviceItem); + mCachedDevices.remove(deviceItem); + mBtManager.getEventManager().dispatchDeviceRemoved(deviceItem); + hasChanged = true; } } - log("addMemberDevicesIntoMainDevice: After changed, CachedBluetoothDevice list: " - + mCachedDevices); + if (hasChanged) { + log("addMemberDevicesIntoMainDevice: After changed, CachedBluetoothDevice list: " + + mCachedDevices); + } + return hasChanged; } private void log(String msg) { diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/CsipDeviceManagerTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/CsipDeviceManagerTest.java new file mode 100644 index 0000000000000..9b41bc4f8b00e --- /dev/null +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/CsipDeviceManagerTest.java @@ -0,0 +1,345 @@ +/* + * Copyright (C) 2023 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 static com.google.common.truth.Truth.assertThat; + +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.when; + +import android.bluetooth.BluetoothClass; +import android.bluetooth.BluetoothCsipSetCoordinator; +import android.bluetooth.BluetoothDevice; +import android.bluetooth.BluetoothProfile; +import android.content.Context; +import android.os.Parcel; + +import java.util.ArrayList; +import java.util.List; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.robolectric.RobolectricTestRunner; +import org.robolectric.RuntimeEnvironment; + +@RunWith(RobolectricTestRunner.class) +public class CsipDeviceManagerTest { + private final static String DEVICE_NAME_1 = "TestName_1"; + private final static String DEVICE_NAME_2 = "TestName_2"; + private final static String DEVICE_NAME_3 = "TestName_3"; + private final static String DEVICE_ALIAS_1 = "TestAlias_1"; + private final static String DEVICE_ALIAS_2 = "TestAlias_2"; + private final static String DEVICE_ALIAS_3 = "TestAlias_3"; + private final static String DEVICE_ADDRESS_1 = "AA:BB:CC:DD:EE:11"; + private final static String DEVICE_ADDRESS_2 = "AA:BB:CC:DD:EE:22"; + private final static String DEVICE_ADDRESS_3 = "AA:BB:CC:DD:EE:33"; + private final static int GROUP1 = 1; + private final BluetoothClass DEVICE_CLASS_1 = + createBtClass(BluetoothClass.Device.AUDIO_VIDEO_HEADPHONES); + private final BluetoothClass DEVICE_CLASS_2 = + createBtClass(BluetoothClass.Device.AUDIO_VIDEO_HANDSFREE); + + @Mock + private LocalBluetoothManager mLocalBluetoothManager; + @Mock + private LocalBluetoothProfileManager mLocalProfileManager; + @Mock + private BluetoothEventManager mBluetoothEventManager; + @Mock + private BluetoothDevice mDevice1; + @Mock + private BluetoothDevice mDevice2; + @Mock + private BluetoothDevice mDevice3; + @Mock + private HeadsetProfile mHfpProfile; + @Mock + private A2dpProfile mA2dpProfile; + @Mock + private LeAudioProfile mLeAudioProfile; + + private CachedBluetoothDevice mCachedDevice1; + private CachedBluetoothDevice mCachedDevice2; + private CachedBluetoothDevice mCachedDevice3; + private CachedBluetoothDeviceManager mCachedDeviceManager; + private CsipDeviceManager mCsipDeviceManager; + private Context mContext; + private List mCachedDevices = new ArrayList(); + + + private BluetoothClass createBtClass(int deviceClass) { + Parcel p = Parcel.obtain(); + p.writeInt(deviceClass); + p.setDataPosition(0); // reset position of parcel before passing to constructor + + BluetoothClass bluetoothClass = BluetoothClass.CREATOR.createFromParcel(p); + p.recycle(); + return bluetoothClass; + } + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + + mContext = RuntimeEnvironment.application; + when(mDevice1.getAddress()).thenReturn(DEVICE_ADDRESS_1); + when(mDevice2.getAddress()).thenReturn(DEVICE_ADDRESS_2); + when(mDevice3.getAddress()).thenReturn(DEVICE_ADDRESS_3); + when(mDevice1.getName()).thenReturn(DEVICE_NAME_1); + when(mDevice2.getName()).thenReturn(DEVICE_NAME_2); + when(mDevice3.getName()).thenReturn(DEVICE_NAME_3); + when(mDevice1.getAlias()).thenReturn(DEVICE_ALIAS_1); + when(mDevice2.getAlias()).thenReturn(DEVICE_ALIAS_2); + when(mDevice3.getAlias()).thenReturn(DEVICE_ALIAS_3); + when(mDevice1.getBluetoothClass()).thenReturn(DEVICE_CLASS_1); + when(mDevice2.getBluetoothClass()).thenReturn(DEVICE_CLASS_2); + when(mDevice3.getBluetoothClass()).thenReturn(DEVICE_CLASS_2); + when(mLocalBluetoothManager.getEventManager()).thenReturn(mBluetoothEventManager); + when(mLocalBluetoothManager.getProfileManager()).thenReturn(mLocalProfileManager); + when(mLocalProfileManager.getLeAudioProfile()).thenReturn(mLeAudioProfile); + when(mLocalProfileManager.getA2dpProfile()).thenReturn(mA2dpProfile); + when(mLocalProfileManager.getHeadsetProfile()).thenReturn(mHfpProfile); + + when(mLeAudioProfile.getConnectedGroupLeadDevice(anyInt())).thenReturn(null); + mCachedDeviceManager = new CachedBluetoothDeviceManager(mContext, mLocalBluetoothManager); + when(mLocalBluetoothManager.getCachedDeviceManager()).thenReturn(mCachedDeviceManager); + + mCachedDevices = mCachedDeviceManager.mCachedDevices; + mCsipDeviceManager = mCachedDeviceManager.mCsipDeviceManager; + + // Setup the default for testing + mCachedDevice1 = spy(new CachedBluetoothDevice(mContext, mLocalProfileManager, mDevice1)); + mCachedDevice2 = spy(new CachedBluetoothDevice(mContext, mLocalProfileManager, mDevice2)); + mCachedDevice3 = spy(new CachedBluetoothDevice(mContext, mLocalProfileManager, mDevice3)); + + mCachedDevice1.setGroupId(GROUP1); + mCachedDevice2.setGroupId(GROUP1); + mCachedDevice1.addMemberDevice(mCachedDevice2); + mCachedDevices.add(mCachedDevice1); + mCachedDevices.add(mCachedDevice3); + + List profiles = new ArrayList(); + profiles.add(mHfpProfile); + profiles.add(mA2dpProfile); + profiles.add(mLeAudioProfile); + when(mCachedDevice1.getConnectableProfiles()).thenReturn(profiles); + when(mCachedDevice1.isConnected()).thenReturn(true); + + profiles.clear(); + profiles.add(mLeAudioProfile); + when(mCachedDevice2.getConnectableProfiles()).thenReturn(profiles); + when(mCachedDevice2.isConnected()).thenReturn(true); + + profiles.clear(); + profiles.add(mHfpProfile); + profiles.add(mA2dpProfile); + when(mCachedDevice3.getConnectableProfiles()).thenReturn(profiles); + when(mCachedDevice3.isConnected()).thenReturn(true); + + } + + @Test + public void onProfileConnectionStateChangedIfProcessed_profileIsConnecting_returnOff() { + assertThat(mCsipDeviceManager.onProfileConnectionStateChangedIfProcessed(mCachedDevice1, + BluetoothProfile.STATE_CONNECTING)).isFalse(); + } + + @Test + public void onProfileConnectionStateChangedIfProcessed_profileIsDisconnecting_returnOff() { + assertThat(mCsipDeviceManager.onProfileConnectionStateChangedIfProcessed(mCachedDevice1, + BluetoothProfile.STATE_DISCONNECTING)).isFalse(); + } + + @Test + public void updateRelationshipOfGroupDevices_invalidGroupId_returnOff() { + assertThat(mCsipDeviceManager.updateRelationshipOfGroupDevices( + BluetoothCsipSetCoordinator.GROUP_ID_INVALID)).isFalse(); + } + + @Test + public void getGroupDevicesFromAllOfDevicesList_invalidGroupId_returnEmpty() { + assertThat(mCsipDeviceManager.getGroupDevicesFromAllOfDevicesList( + BluetoothCsipSetCoordinator.GROUP_ID_INVALID).isEmpty()).isTrue(); + } + + @Test + public void getGroupDevicesFromAllOfDevicesList_validGroupId_returnGroupDevices() { + List expectedList = new ArrayList<>(); + expectedList.add(mCachedDevice1); + expectedList.add(mCachedDevice2); + + assertThat(mCsipDeviceManager.getGroupDevicesFromAllOfDevicesList(GROUP1)) + .isEqualTo(expectedList); + } + + @Test + public void getPreferredMainDevice_dualModeDevice_returnDualModeDevice() { + CachedBluetoothDevice expectedDevice = mCachedDevice1; + + assertThat( + mCsipDeviceManager.getPreferredMainDevice(GROUP1, + mCsipDeviceManager.getGroupDevicesFromAllOfDevicesList(GROUP1))) + .isEqualTo(expectedDevice); + } + + @Test + public void getPreferredMainDevice_noConnectedDualModeDevice_returnLeadDevice() { + when(mCachedDevice1.isConnected()).thenReturn(false); + when(mLeAudioProfile.getConnectedGroupLeadDevice(anyInt())).thenReturn(mDevice2); + CachedBluetoothDevice expectedDevice = mCachedDevice2; + + assertThat( + mCsipDeviceManager.getPreferredMainDevice(GROUP1, + mCsipDeviceManager.getGroupDevicesFromAllOfDevicesList(GROUP1))) + .isEqualTo(expectedDevice); + } + + @Test + public void getPreferredMainDevice_noConnectedDualModeDeviceNoLeadDevice_returnConnectedOne() { + when(mCachedDevice1.isConnected()).thenReturn(false); + when(mCachedDevice2.isConnected()).thenReturn(true); + CachedBluetoothDevice expectedDevice = mCachedDevice2; + + assertThat( + mCsipDeviceManager.getPreferredMainDevice(GROUP1, + mCsipDeviceManager.getGroupDevicesFromAllOfDevicesList(GROUP1))) + .isEqualTo(expectedDevice); + } + + @Test + public void getPreferredMainDevice_noConnectedDevice_returnDualModeDevice() { + when(mCachedDevice1.isConnected()).thenReturn(false); + when(mCachedDevice2.isConnected()).thenReturn(false); + CachedBluetoothDevice expectedDevice = mCachedDevice1; + + assertThat( + mCsipDeviceManager.getPreferredMainDevice(GROUP1, + mCsipDeviceManager.getGroupDevicesFromAllOfDevicesList(GROUP1))) + .isEqualTo(expectedDevice); + } + + @Test + public void getPreferredMainDevice_noConnectedDeviceNoDualMode_returnFirstOneDevice() { + when(mCachedDevice1.isConnected()).thenReturn(false); + when(mCachedDevice2.isConnected()).thenReturn(false); + List profiles = new ArrayList(); + profiles.add(mLeAudioProfile); + when(mCachedDevice1.getConnectableProfiles()).thenReturn(profiles); + CachedBluetoothDevice expectedDevice = mCachedDevice1; + + assertThat( + mCsipDeviceManager.getPreferredMainDevice(GROUP1, + mCsipDeviceManager.getGroupDevicesFromAllOfDevicesList(GROUP1))) + .isEqualTo(expectedDevice); + } + + @Test + public void addMemberDevicesIntoMainDevice_noPreferredDevice_returnFalseAndNoChangeList() { + CachedBluetoothDevice preferredDevice = null; + List expectedList = new ArrayList<>(); + for (CachedBluetoothDevice item : mCachedDevices) { + expectedList.add(item); + } + + assertThat(mCsipDeviceManager.addMemberDevicesIntoMainDevice(GROUP1, preferredDevice)) + .isFalse(); + for (CachedBluetoothDevice expectedItem : expectedList) { + assertThat(mCachedDevices.contains(expectedItem)).isTrue(); + } + } + + @Test + public void addMemberDevicesIntoMainDevice_preferredDeviceIsMainAndNoOtherInList_noChangeList() + { + // Condition: The preferredDevice is main and no other main device in top list + // Expected Result: return false and the list is no changed + CachedBluetoothDevice preferredDevice = mCachedDevice1; + List expectedList = new ArrayList<>(); + for (CachedBluetoothDevice item : mCachedDevices) { + expectedList.add(item); + } + + assertThat(mCsipDeviceManager.addMemberDevicesIntoMainDevice(GROUP1, preferredDevice)) + .isFalse(); + for (CachedBluetoothDevice expectedItem : expectedList) { + assertThat(mCachedDevices.contains(expectedItem)).isTrue(); + } + } + + @Test + public void addMemberDevicesIntoMainDevice_preferredDeviceIsMainAndTwoMain_returnTrue() { + // Condition: The preferredDevice is main and there is another main device in top list + // Expected Result: return true and there is the preferredDevice in top list + CachedBluetoothDevice preferredDevice = mCachedDevice1; + mCachedDevice1.getMemberDevice().clear(); + mCachedDevices.clear(); + mCachedDevices.add(preferredDevice); + mCachedDevices.add(mCachedDevice2); + mCachedDevices.add(mCachedDevice3); + + + assertThat(mCsipDeviceManager.addMemberDevicesIntoMainDevice(GROUP1, preferredDevice)) + .isTrue(); + assertThat(mCachedDevices.contains(preferredDevice)).isTrue(); + assertThat(mCachedDevices.contains(mCachedDevice2)).isFalse(); + assertThat(mCachedDevices.contains(mCachedDevice3)).isTrue(); + assertThat(preferredDevice.getMemberDevice()).contains(mCachedDevice2); + } + + @Test + public void addMemberDevicesIntoMainDevice_preferredDeviceIsMember_returnTrue() { + // Condition: The preferredDevice is member + // Expected Result: return true and there is the preferredDevice in top list + CachedBluetoothDevice preferredDevice = mCachedDevice2; + BluetoothDevice expectedMainBluetoothDevice = preferredDevice.getDevice(); + + assertThat(mCsipDeviceManager.addMemberDevicesIntoMainDevice(GROUP1, preferredDevice)) + .isTrue(); + // expected main is mCachedDevice1 which is the main of preferredDevice, since system + // switch the relationship between preferredDevice and the main of preferredDevice + assertThat(mCachedDevices.contains(mCachedDevice1)).isTrue(); + assertThat(mCachedDevices.contains(mCachedDevice2)).isFalse(); + assertThat(mCachedDevices.contains(mCachedDevice3)).isTrue(); + assertThat(mCachedDevice1.getMemberDevice()).contains(mCachedDevice2); + assertThat(mCachedDevice1.getDevice()).isEqualTo(expectedMainBluetoothDevice); + } + + @Test + public void addMemberDevicesIntoMainDevice_preferredDeviceIsMemberAndTwoMain_returnTrue() { + // Condition: The preferredDevice is member and there are two main device in top list + // Expected Result: return true and there is the preferredDevice in top list + CachedBluetoothDevice preferredDevice = mCachedDevice2; + BluetoothDevice expectedMainBluetoothDevice = preferredDevice.getDevice(); + mCachedDevice3.setGroupId(GROUP1); + + assertThat(mCsipDeviceManager.addMemberDevicesIntoMainDevice(GROUP1, preferredDevice)) + .isTrue(); + // expected main is mCachedDevice1 which is the main of preferredDevice, since system + // switch the relationship between preferredDevice and the main of preferredDevice + assertThat(mCachedDevices.contains(mCachedDevice1)).isTrue(); + assertThat(mCachedDevices.contains(mCachedDevice2)).isFalse(); + assertThat(mCachedDevices.contains(mCachedDevice3)).isFalse(); + assertThat(mCachedDevice1.getMemberDevice()).contains(mCachedDevice2); + assertThat(mCachedDevice1.getMemberDevice()).contains(mCachedDevice3); + assertThat(mCachedDevice1.getMemberDevice()).contains(mCachedDevice3); + assertThat(mCachedDevice1.getDevice()).isEqualTo(expectedMainBluetoothDevice); + } +}