Add CachedBluetoothDeviceManager test case for csip set member device logic

The patch contains two topic.
1. Add test case for csip set member device add/remove logic
2. Fix clearNonBondedDevices issue. Remove object from set as iterating, it would cause ConcurrentModificationException, make a copy as iterating to achieve the safe remove.

Bug: 205507889
Bug: 150670922
Bug: 178981521
Test: make -j50 RunSettingsLibRoboTests ROBOTEST_FILTER=CachedBluetoothDeviceManagerTest
Change-Id: I3fa3fd9a9e13e011ce2ce01a8142511c86ccbf1c
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceManager.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceManager.java
index a602866..3debd9a 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceManager.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceManager.java
@@ -230,9 +230,10 @@
     private void clearNonBondedSubDevices() {
         for (int i = mCachedDevices.size() - 1; i >= 0; i--) {
             CachedBluetoothDevice cachedDevice = mCachedDevices.get(i);
-            final Set<CachedBluetoothDevice> memberDevices = cachedDevice.getMemberDevice();
+            Set<CachedBluetoothDevice> memberDevices = cachedDevice.getMemberDevice();
             if (!memberDevices.isEmpty()) {
-                for (CachedBluetoothDevice memberDevice : memberDevices) {
+                for (Object it : memberDevices.toArray()) {
+                    CachedBluetoothDevice memberDevice = (CachedBluetoothDevice) it;
                     // Member device exists and it is not bonded
                     if (memberDevice.getDevice().getBondState() == BluetoothDevice.BOND_NONE) {
                         cachedDevice.removeMemberDevice(memberDevice);
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceManagerTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceManagerTest.java
index 2c4f57f..d53a3e8 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceManagerTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceManagerTest.java
@@ -26,7 +26,9 @@
 
 import android.bluetooth.BluetoothClass;
 import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothUuid;
 import android.content.Context;
+import android.os.ParcelUuid;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -37,6 +39,8 @@
 import org.robolectric.RuntimeEnvironment;
 
 import java.util.Collection;
+import java.util.HashMap;
+import java.util.Map;
 
 @RunWith(RobolectricTestRunner.class)
 public class CachedBluetoothDeviceManagerTest {
@@ -51,6 +55,10 @@
     private final static String DEVICE_ADDRESS_3 = "AA:BB:CC:DD:EE:33";
     private final static long HISYNCID1 = 10;
     private final static long HISYNCID2 = 11;
+    private final static Map<Integer, ParcelUuid> CAP_GROUP1 =
+            Map.of(1, BluetoothUuid.CAP);
+    private final static Map<Integer, ParcelUuid> CAP_GROUP2 =
+            Map.of(2, BluetoothUuid.CAP);
     private final BluetoothClass DEVICE_CLASS_1 =
         new BluetoothClass(BluetoothClass.Device.AUDIO_VIDEO_HEADPHONES);
     private final BluetoothClass DEVICE_CLASS_2 =
@@ -70,6 +78,8 @@
     @Mock
     private HearingAidProfile mHearingAidProfile;
     @Mock
+    private CsipSetCoordinatorProfile mCsipSetCoordinatorProfile;
+    @Mock
     private BluetoothDevice mDevice1;
     @Mock
     private BluetoothDevice mDevice2;
@@ -105,8 +115,12 @@
         when(mA2dpProfile.isProfileReady()).thenReturn(true);
         when(mPanProfile.isProfileReady()).thenReturn(true);
         when(mHearingAidProfile.isProfileReady()).thenReturn(true);
+        when(mCsipSetCoordinatorProfile.isProfileReady())
+                .thenReturn(true);
         doAnswer((invocation) -> mHearingAidProfile).
                 when(mLocalProfileManager).getHearingAidProfile();
+        doAnswer((invocation) -> mCsipSetCoordinatorProfile)
+                .when(mLocalProfileManager).getCsipSetCoordinatorProfile();
         mCachedDeviceManager = new CachedBluetoothDeviceManager(mContext, mLocalBluetoothManager);
         mCachedDevice1 = spy(new CachedBluetoothDevice(mContext, mLocalProfileManager, mDevice1));
         mCachedDevice2 = spy(new CachedBluetoothDevice(mContext, mLocalProfileManager, mDevice2));
@@ -298,7 +312,7 @@
      * Test to verify OnDeviceUnpaired() for main hearing Aid device unpair.
      */
     @Test
-    public void onDeviceUnpaired_unpairMainDevice() {
+    public void onDeviceUnpaired_unpairHearingAidMainDevice() {
         when(mDevice1.getBondState()).thenReturn(BluetoothDevice.BOND_BONDED);
         CachedBluetoothDevice cachedDevice1 = mCachedDeviceManager.addDevice(mDevice1);
         CachedBluetoothDevice cachedDevice2 = mCachedDeviceManager.addDevice(mDevice2);
@@ -398,4 +412,153 @@
         when(mDevice1.getBondState()).thenReturn(BluetoothDevice.BOND_NONE);
         assertThat(mCachedDeviceManager.onDeviceDisappeared(cachedDevice1)).isTrue();
     }
+
+     /**
+     * Test to verify getMemberDevice(), new device has the same group id.
+     */
+    @Test
+    public void addDevice_sameGroupId_validMemberDevice() {
+        doAnswer((invocation) -> CAP_GROUP1).when(mCsipSetCoordinatorProfile)
+                .getGroupUuidMapByDevice(mDevice1);
+        doAnswer((invocation) -> CAP_GROUP1).when(mCsipSetCoordinatorProfile)
+                .getGroupUuidMapByDevice(mDevice2);
+        doAnswer((invocation) -> CAP_GROUP1).when(mCsipSetCoordinatorProfile)
+                .getGroupUuidMapByDevice(mDevice3);
+        CachedBluetoothDevice cachedDevice1 = mCachedDeviceManager.addDevice(mDevice1);
+        CachedBluetoothDevice cachedDevice2 = mCachedDeviceManager.addDevice(mDevice2);
+        CachedBluetoothDevice cachedDevice3 = mCachedDeviceManager.addDevice(mDevice3);
+
+        assertThat(cachedDevice1.getMemberDevice()).contains(cachedDevice2);
+        assertThat(cachedDevice1.getMemberDevice()).contains(cachedDevice3);
+    }
+
+    /**
+     * Test to verify getMemberDevice(), new device has the different group id.
+     */
+    @Test
+    public void addDevice_differentGroupId_validMemberDevice() {
+        doAnswer((invocation) -> CAP_GROUP1).when(mCsipSetCoordinatorProfile)
+                .getGroupUuidMapByDevice(mDevice1);
+        doAnswer((invocation) -> CAP_GROUP2).when(mCsipSetCoordinatorProfile)
+                .getGroupUuidMapByDevice(mDevice2);
+        CachedBluetoothDevice cachedDevice1 = mCachedDeviceManager.addDevice(mDevice1);
+        CachedBluetoothDevice cachedDevice2 = mCachedDeviceManager.addDevice(mDevice2);
+
+        assertThat(cachedDevice1.getMemberDevice()).isEmpty();
+    }
+
+    /**
+     * Test to verify addDevice(), new device has the same group id.
+     */
+    @Test
+    public void addDevice_sameGroupId_validCachedDevices_mainDevicesAdded_memberDevicesNotAdded() {
+        doAnswer((invocation) -> CAP_GROUP1).when(mCsipSetCoordinatorProfile)
+                .getGroupUuidMapByDevice(mDevice1);
+        doAnswer((invocation) -> CAP_GROUP1).when(mCsipSetCoordinatorProfile)
+                .getGroupUuidMapByDevice(mDevice2);
+        CachedBluetoothDevice cachedDevice1 = mCachedDeviceManager.addDevice(mDevice1);
+        CachedBluetoothDevice cachedDevice2 = mCachedDeviceManager.addDevice(mDevice2);
+
+        Collection<CachedBluetoothDevice> devices = mCachedDeviceManager.getCachedDevicesCopy();
+        assertThat(devices).contains(cachedDevice1);
+        assertThat(devices).doesNotContain(cachedDevice2);
+    }
+
+    /**
+     * Test to verify addDevice(), new device has the different group id.
+     */
+    @Test
+    public void addDevice_differentGroupId_validCachedDevices_bothAdded() {
+        doAnswer((invocation) -> CAP_GROUP1).when(mCsipSetCoordinatorProfile)
+                .getGroupUuidMapByDevice(mDevice1);
+        doAnswer((invocation) -> CAP_GROUP2).when(mCsipSetCoordinatorProfile)
+                .getGroupUuidMapByDevice(mDevice2);
+        CachedBluetoothDevice cachedDevice1 = mCachedDeviceManager.addDevice(mDevice1);
+        CachedBluetoothDevice cachedDevice2 = mCachedDeviceManager.addDevice(mDevice2);
+
+        Collection<CachedBluetoothDevice> devices = mCachedDeviceManager.getCachedDevicesCopy();
+        assertThat(devices).contains(cachedDevice1);
+        assertThat(devices).contains(cachedDevice2);
+    }
+
+    /**
+     * Test to verify clearNonBondedDevices() for csip set member device.
+     */
+    @Test
+    public void clearNonBondedDevices_nonBondedMemberDevice() {
+        when(mDevice1.getBondState()).thenReturn(BluetoothDevice.BOND_BONDED);
+        when(mDevice2.getBondState()).thenReturn(BluetoothDevice.BOND_NONE);
+        when(mDevice3.getBondState()).thenReturn(BluetoothDevice.BOND_BONDED);
+        CachedBluetoothDevice cachedDevice1 = mCachedDeviceManager.addDevice(mDevice1);
+        CachedBluetoothDevice cachedDevice2 = mCachedDeviceManager.addDevice(mDevice2);
+        CachedBluetoothDevice cachedDevice3 = mCachedDeviceManager.addDevice(mDevice3);
+        cachedDevice1.setMemberDevice(cachedDevice2);
+        cachedDevice1.setMemberDevice(cachedDevice3);
+
+        assertThat(cachedDevice1.getMemberDevice()).contains(cachedDevice2);
+        assertThat(cachedDevice1.getMemberDevice()).contains(cachedDevice3);
+        mCachedDeviceManager.clearNonBondedDevices();
+
+        assertThat(cachedDevice1.getMemberDevice().contains(cachedDevice2)).isFalse();
+        assertThat(cachedDevice1.getMemberDevice().contains(cachedDevice3)).isTrue();
+    }
+
+    /**
+     * Test to verify OnDeviceUnpaired() for csip device unpair.
+     */
+    @Test
+    public void onDeviceUnpaired_unpairCsipMainDevice() {
+        when(mDevice1.getBondState()).thenReturn(BluetoothDevice.BOND_BONDED);
+        CachedBluetoothDevice cachedDevice1 = mCachedDeviceManager.addDevice(mDevice1);
+        CachedBluetoothDevice cachedDevice2 = mCachedDeviceManager.addDevice(mDevice2);
+        cachedDevice1.setGroupId(1);
+        cachedDevice2.setGroupId(1);
+        cachedDevice1.setMemberDevice(cachedDevice2);
+
+        // Call onDeviceUnpaired for the one in mCachedDevices.
+        mCachedDeviceManager.onDeviceUnpaired(cachedDevice1);
+        verify(mDevice2).removeBond();
+    }
+
+    /**
+     * Test to verify OnDeviceUnpaired() for csip device unpair.
+     */
+    @Test
+    public void onDeviceUnpaired_unpairCsipSubDevice() {
+        when(mDevice1.getBondState()).thenReturn(BluetoothDevice.BOND_BONDED);
+        CachedBluetoothDevice cachedDevice1 = mCachedDeviceManager.addDevice(mDevice1);
+        CachedBluetoothDevice cachedDevice2 = mCachedDeviceManager.addDevice(mDevice2);
+        cachedDevice1.setGroupId(1);
+        cachedDevice2.setGroupId(1);
+        cachedDevice1.setMemberDevice(cachedDevice2);
+
+        // Call onDeviceUnpaired for the one in mCachedDevices.
+        mCachedDeviceManager.onDeviceUnpaired(cachedDevice2);
+        verify(mDevice1).removeBond();
+    }
+
+    /**
+     * Test to verify isSubDevice_validSubDevice().
+     */
+    @Test
+    public void isSubDevice_validMemberDevice() {
+        doReturn(CAP_GROUP1).when(mCsipSetCoordinatorProfile).getGroupUuidMapByDevice(mDevice1);
+        mCachedDeviceManager.addDevice(mDevice1);
+
+        // Both device are not sub device in default value.
+        assertThat(mCachedDeviceManager.isSubDevice(mDevice1)).isFalse();
+        assertThat(mCachedDeviceManager.isSubDevice(mDevice2)).isFalse();
+
+        // Add Device-2 as device with Device-1 with the same group id, and add Device-3 with
+        // the different group id.
+        doReturn(CAP_GROUP1).when(mCsipSetCoordinatorProfile).getGroupUuidMapByDevice(mDevice2);
+        doReturn(CAP_GROUP2).when(mCsipSetCoordinatorProfile).getGroupUuidMapByDevice(mDevice3);
+        mCachedDeviceManager.addDevice(mDevice2);
+        mCachedDeviceManager.addDevice(mDevice3);
+
+        // Verify Device-2 is sub device, but Device-1, and Device-3 is not.
+        assertThat(mCachedDeviceManager.isSubDevice(mDevice1)).isFalse();
+        assertThat(mCachedDeviceManager.isSubDevice(mDevice2)).isTrue();
+        assertThat(mCachedDeviceManager.isSubDevice(mDevice3)).isFalse();
+    }
 }